一、反序列化漏洞
1.序列化是指将对象的状态信息转化为可存储或可传输的过程,反序列化即序列化的逆过程,其目的是方便数据传输。
2.PHP序列化举例:
例如:序列化前的对象为
<?php
class person{
public $name;
public $age=19;
public $sex;
?>
通过serialize()函数序列化后:
![在这里插入图片描述](https://img-blog.csdnimg.cn/3ba15157ce344e45ba7ac0b46b21cc14.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAQ0NfX0xMX19DQw==,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
这里的O表示这是一个对象,6表示对象名的长度,person则是序列化的对象名称,3表示对象中存在3个属性。第一个属性s表示是字符串,4表示属性名的长度,后面说明属性名称是name,它的值为N(空);第二个属性是age,它的值是整数型19;第三个属性是sex,它的值也是空。
3.利用反序列化攻击?
PHP中存在魔术方法,即PHP自动调用,如:
__construct 当一个对象创建时自动调用
__destruct 当对象被销毁时自动调用 (php绝大多数情况下会自动调用销毁对象)
__sleep() 使**用serialize()函数时触发
__wakeup 使用unserialse()**函数时会自动调用
__toString 当一个对象被当作一个字符串被调用。
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据//调用私有属性时使用
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象调用为函数时触发
3.反序列化利用:
<?php
class test{
function __destruct(){
echo "destruct...<br>";
eval($_GET['cmd']);}
}
unserialize($_GET['u'];
?>
这个代码存在一个test类,__destruct()魔术函数中存在eval($_GET[‘cmd’]);然后通过参数u来接受序列化的字符。因此可以通过__destruct()在对象销毁时自动调用此方法,然后通过cmd传入PHP代码,达到任意代码执行。
有时候魔术方法中不存在可利用代码,即没有eval($_GET[‘cmd’]);可调用其他类方法的代码,如寻找其他有相同名称方法的类。
4.在实际的挖洞过程中若没有合适的利用链,就需要利用PHP自身的原生类。
1>__call方法:在调用不存在的类时触发,PHP存在内置类SoapClient:__Call,进行__call魔术方法时,可以进行SSRF攻击。
2>__toString:当对象作为字符串处理时自动触发。主要利用了Exception类对错误消息没有过滤,导致最终反序列化的内容在网页中造成XSS,构写Exploit时,将XSS代码作为Exception类的参数即可。
3>__construct:通常在反序列化中无法触发__construct,但在开发者魔改后可以存在任意类实例化的情况。
5.Phar反序列化
二.反序列化漏洞题目中的小技巧
1.__wakeup失效,从而绕过其中可能存在的限制,进而触发漏洞。
原因:当属性个数不正确时,process_nested_data函数会返回0,导致后面的call_user_function_ex函数不会执行,则在PHP中就不会调用__wakeup()。
例题:极客大挑战 2019]PHP
通过dirsearch扫描得到www.zip,解压分析代码可以看出这是一个反序列化的题目,解题的关键在于绕过__wakeup,这就用到__wakeup失效这个方法。
2.bypass反序列化正则:在执行反序列化时,有时会遇到正则“/[oc]:\d+:/i”进行拦截,以下面字符为例:
O:4:"demo":1:{s:5:"demoa";a:0:{}}
通过对PHP的unserialize()函数进行分析,发现PHP内核中最后使用php_var_unserialize进行解析
case '0': goto yy13;
yy13:
yych=*(YYMARKER=++YYCURSOR);
if(yych==':') goto yy17;
goto yy3;
yy17:
yych=*++YYCURSOR;
if(yybm[0+yych]&128){
goto yy20;
}
if(yych=='+') goto yy19;
yy19:
yych=*++YYCURSOR;
if(yybm[0+yych]&128){
goto yy20;
}
改代码主要解析“‘O’:”语句段,跟如yy17,还会存在“+”判断。所以在上述序列化字符中“O:”后加上“+”,就会从yy17调到yy19处理,然后继续对“+”后面的数字判断,就说明支持“+”来表示数字,从而对什么的正则进行绕过。
3.反序列化字符逃逸
原因:在PHP序列化数据中,如果序列化的是字符串,就会保留该字符串的长度,然后将长度写入序列化后的数据,反序列化时就会按照长度读取,并且PHP底层实现上是以“;”作为分隔,以“}”作为结尾。类中不存在的属性也会被反序列化,就会引起逃逸问题,而导致对象注入。
<php?
function filter($string){
$str=str_replace('x','hi',$string);
return $str;
}
$fruits=array("apple","orange");
echo (serialize($fruits));
echo "\n";
$r=filter(serialize($fruits));
echo ($r);
echo "\n";
var_dump(unserialize($r));
?>
这里的输出结果为:a :2:{i:0;s:5:“apple”;i:1;s:6:“orange”;}
但将orange改为orangex时,输出则为a:2:{i:0;s:5:“apple”;i:1;s:7:“orangehi”;}。
可以看出长度由6变为了7,而实际长度则增加了两个,这样反序列化肯定会失败。假设利用过滤函数提供的一个字符变成两个的功能来逃逸可用的字符串,从而逃逸可用的字符串,注入想要修改的属性。
4.Session反序列化:
5.PHP引用:题目存在just4fun类,其中有enter,secret属性,由于KaTeX parse error: Expected 'EOF', got '&' at position 20: …et是未知的,则利用PHP中"&̲",其中“&a”引用了“$a”的值,即在内存中是指向变量的地址,在序列号字符串中则用R表示引用类型。
题目代码:
class just4fun{
var $enter;
var $secret;
}
$o=unserialize($_GET['d']);
$o->secret="lose";
if($o->secret===$o->enter){
echo "win";
}
利用代码:
class just4fun{
var $enter;
var $secret;
function just4fun(){
$this->enter=&$this->secret;
}
}
echo serialize(new just4fun());
在初始化时利用“&”将enter指向secret的地址,最终生成可利用的字符串:
O:8:“just4fun”:2:{s:5:“enter”;N;s:6:“secret”;R:2;}
s:6:“secret”;R:2; ------------------即通过引用的方式将两者的属性值成为同一个值。
6.exception绕过:有时遇到throw问题,由于报错导致后面代码无法执行
$line=trim(fgets(STDIN));
$flag=file_get_connect('/flag');
class B{
function __destruct(){
global $flag;
echo $flag;
}
}
$a=@unserialize($line);
throw new Exception('.......');
echo $a;
B类的__destruct会输出全局的flag变量,反序列化点在throw前。正常情况下throw报错导致destruct不会执行,但改变属性为“O:1:“B”:1:{1}”,解析错误但类名是正确的,就会调用该类名的 __destruct,从而在throw前执行 __destruct