0x01 基本概念
POP:Property-Oriented Programming 面向属性编程
POP链:通过多个属性/对象之前的调用关系形成的一个可利用链(如有错误请指正)
PHP魔法函数:在php的语法中,有一些系统自带的方法名,均以双下划线开头,它会在特定的情况下被调用,即所谓的魔法函数
PHP序列化:将PHP变量或对象转换成字符串
PHP反序列化:将字符串转换成PHP变量或对象
0x02 PHP序列化与反序列化
写一个简单的demo,类man中有name喝age属性,__construct是魔法函数,在创建对象的时候会调用,serialize()函数就是序列化,var_dump会打印序列化后的字符串
<?php class man{ public $name; public $age; function __construct($name,$age){ $this->name = $name; $this->age = $age; } } $man=new man("Bob",5); var_dump(serialize($man)); $uman='O:3:"man":2:{s:4:"name";s:3:"Bob";s:3:"age";i:5;};1234:666'; var_dump(unserialize($uman)); ?> |
打印结果如下:
O:3:"man":2:{s:4:"name";s:3:"Bob";s:3:"age";i:5;},从前往后说,其中O表示对象,3是长度,man是类名,2表示2个属性,大括号内表示属性名和值。
在demo中也可以看到,字符串O:3:"man":2:{s:4:"name";s:3:"Bob";s:3:"age";i:5;};1234:666 依然可以反序列成man对象,且属性与O:3:"man":2:{s:4:"name";s:3:"Bob";s:3:"age";i:5;}相同,说明在规定语法外添加字符串不影响反序列化的结果。
0x03 __wakeup()魔术方法绕过(CVE-2016-7124)
unserialize()会检查类是否有__wakeup()魔术方法,有的话会先调用该方法,稍微改一下上面的demo,可以看到在反序列化的时候调用了__wakeup魔术方法:
<?php class man{ public $name; public $age; function __construct($name,$age){ $this->name = $name; $this->age = $age; } function __wakeup(){ echo 'Its __wakeup'; } } $man=new man("Bob",5); var_dump(unserialize((serialize($man)) )); ?> |
而CVE-2016-7124漏洞是当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 __wakeup 函数的执行。也就是说,对字符串O:3:"man":3:{s:4:"name";s:3:"Bob";s:3:"age";i:5;}反序列化时,会不执行__wakeup,较低版本的PHP会有这个漏洞。
<?php class man{ public $name; public $age; function __construct($name,$age){ $this->name = $name; $this->age = $age; } function __wakeup(){ echo 'Its __wakeup'; } } $man=new man("Bob",5); var_dump(unserialize('O:3:"man":3:{s:4:"name";s:3:"Bob";s:3:"age";i:5;}') )); ?> |
0x04 PHP序列化与反序列化逃逸
看下面的demo
<?php function filter($string){ $a = str_replace('11','2',$string); return $a; } $username = '111111'; $password="abcdef"; $user = array($username, $password); $a=(serialize($user)); echo $a;echo "n"; $r = filter($a); echo $r;echo "n"; var_dump(unserialize($r)); ?> |
输出如下:
a:2:{i:0;s:6:"111111";i:1;s:6:"abcdef";}
a:2:{i:0;s:6:"222";i:1;s:6:"abcdef";}
bool(false)
看到在第一个参数找6个字符的时候,第二个字符串反序列化会报错,是由于剩下的字符串不符合语法规则了。
如果说password只能输入字符串类型,但我想将password变成int类型呢?
需要将代码修改如下:
<?php function filter($string){ $a = str_replace('11','2',$string); return $a; } $username = '111111111111111111111111'; $password='";i:1;i:1;}xxx'; $user = array($username, $password); $a=(serialize($user)); echo $a;echo "n"; $r = filter($a); echo $r;echo "n"; var_dump(unserialize($r)); ?> |
逃逸本质上是由于序列化的时候字符串长度固定了,但是在反序列化之前,会由于各种原因改变字符串的长度,导致反序列化时读取的数据发生了变化,如果经过精心构造格式正确的payload,那么就可以达到逃逸的效果。
0x05 强网杯题目---Web辅助
这个题目是依靠PHP序列化与反序列化逃逸、__wakeup绕过、POP链构造几个点来最终获取flag的。
题目如下:
index.php
...
if (isset($_GET['username']) && isset($_GET['password'])){ ... |
common.php
<?php |
评论