PHP反序列化
一、什么是反序列化?
首先我们要知道什么是序列化,我们在php里定义一个class,这个class里存储着一些变量和数据,如果这个class一直不销毁,就会浪费系统资源,在一些大型项目里可能就会产生问题,因此,我们可以把这个对象序列化(serialize),以字符串的形式存储起来,当需要用的时候再将它进行反序列化(unserialize)回来就可以了。
例如:
1 |
|
运行结果为:
1 | O:4:"demo":2:{s:4:"name";s:3:"bob";s:3:"age";i:22;} |
这里的各个字母分别的含义为:
1 | a - array 数组 |
二、漏洞的产生
1、修改对象属性
在篡改数据的时候,只要攻击者保留一个有效的序列化对象,反序列化过程就会创建一个带有修改后的属性值的服务器端对象。
例如:有一个使用序列化User对象将用户会话数据存储在cookie中的网站,如果我们能在HTTP请求中发现这个序列化对象,将其解码找到这个序列化后的数据:
O:4:"User":2:{s:8:"Username";s:3:"Bob";s:5:"Admin";b:0;}
可以看到"Admin"属性是一个bool类型,我们就可以直接将属性值更改为1,重新编码该对象并覆盖原来的cookie,此时如果网站是通过该cookie来检查当前用户是否拥有管理员权限,那么我们就可以得到管理员权限。
2、修改对象数据类型
PHP的逻辑运算符"=="比较不同的数据类型时,只会比较它们的值而不会比较类型,因此这就意味着1 == '1'
为true。更为不同的是,这也适用与任何以数字开头的字母数字字符串,这时,PHP会将整个字符串转换成该字符串开头的整数值,其余部分被完全忽略,因此1 == '1 aabbcc'
会被PHP视为1 == 1
,甚至当字符串没有任何数字时,0 == 'aabbcc'
的计算结果是true。
1 | $login = unserialize($_COOKIE) |
我们通过修改password属性,使其为整数0而不是预期的字符串,只要存储的密码不以数字开头,条件就会始终返回true,从而绕过验证登录
3、Magic Method
php里存在一种叫magic method的函数,这类函数在满足某些条件的时候就会被触发
__construct()
:当一个对象被创建时被调用__destruct()
:当一个对象被销毁时被调用__sleep()
:在序列化即使用之后被调用__wakeup()
:在反序列化之后被调用- …
如果服务器能够接收反序列化过的字符串,并且未经过滤就把其中的变量直接放进这些函数中,就可能造成漏洞
三、实例分析
1、修改对象属性
BurpSuite里的一个靶场:https://portswigger.net/web-security/all-labs
进来之后用它提供的用户名和密码进行登录
截获该HTTP请求:
可以看到这里cookie的session值进行了base64的加密,将其解密后就是一个User对象的序列化字符串
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:0;}
只要我们将admin的值更改为1,再重新编码并替换掉原先的session值,我们就可以以管理员的身份登入,并可以将用户删除,删除即可完成该实验
2、修改对象数据类型
同样,用题目所给的用户名进行登录,然后抓包,得到cookie的session值:Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJmOGhla3N2YzlncXl4OHBzY25ldmg0Z2toeHhzbHZrNCI7fQ==
解码为:O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"f8heksvc9gqyx8pscnevh4gkhxxslvk4";}
我们要伪造管理员登录,并且绕过token的验证,因此将该字符串更改为:O:4:"User":2:{s:8:"username";s:13:"adminitrator";s:12:"access_token";i:0;}
将其编码后更换原先的session,就能成功的以管理员的身份进入
3、Magic Method
一道攻防世界的题目:
源码如下:
1 |
|
这里涉及到一个__wakeup()
函数的漏洞,也就是CVE-2016-7124这个漏洞,通过修改对象的属性数目使其大于实际数目就可以绕过该magic函数的调用,涉及的PHP版本:
PHP5:<5.6.25
PHP7:<7.0.10
然后构造payload:
1 |
|
生成序列化后的字符串:
1 | O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";} |
注意:
这里真正的字符串是这个
因为php的对象里的属性有几种:
1 | public 公共属性 |
而其中的private和protected定义的变量在序列化之后会有两个空字符,复制时是没办法复制这个空字符的,因此需要在php里继续构造:
1 |
|
生成payload:
1 | TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ== |
再传参即可
至于为什么加一个+号就可以绕过正则,这是PHP的一个bug,详情可以看看这篇文章 https://www.phpbug.cn/archives/32.html
1 |
|