[CISCN2021 Quals]upload题解

考点

  • 文件上传
  • mb_strtolower绕过特定字符检测

难度

  • 简单题

解题思路

首页提供了源码,分析代码发现该站支持文件上传功能,上传限制了文件大小和文件名。主要是需要对文件名的限制进行绕过,来达到getshell的目的。

<?php
if (!isset($_GET["ctf"])) {
    highlight_file(__FILE__);
    die();
}
if(isset($_GET["ctf"]))
    $ctf = $_GET["ctf"];

if($ctf=="upload") {
    if ($_FILES['postedFile']['size'] > 1024*512) {
        die("这么大个的东西你是想d我吗?");
    }
    $imageinfo = getimagesize($_FILES['postedFile']['tmp_name']);
    if ($imageinfo === FALSE) {
        die("如果不能好好传图片的话就还是不要来打扰我了");
    }
    if ($imageinfo[0] !== 1 && $imageinfo[1] !== 1) {
        die("东西不能方方正正的话就很讨厌");
    }
    $fileName=urldecode($_FILES['postedFile']['name']);
    if(stristr($fileName,"c") || stristr($fileName,"i") || stristr($fileName,"h") || stristr($fileName,"ph")) {
        die("有些东西让你传上去的话那可不得了");
    }
    $imagePath = "image/" . mb_strtolower($fileName);
    if(move_uploaded_file($_FILES["postedFile"]["tmp_name"], $imagePath)) {
        echo "upload success, image at $imagePath";
    } else {
        die("传都没有传上去");
    }
}

从代码中可以看出,文件名不能出现c、i、h和ph。

if(stristr($fileName,"c") || stristr($fileName,"i") || stristr($fileName,"h") || stristr($fileName,"ph")) {
        die("有些东西让你传上去的话那可不得了");
    }
$imagePath = "image/" . mb_strtolower($fileName);

绕过上述限制可以借助下面的mb_strtolower函数,有关该函数的文档在https://www.php.net/manual/zh/function.mb-strtolower.php。可以看到,该函数可以处理一些比较奇怪的unicode字符,比如各个国家的一些比较特殊的字母。

在文档下方还提到了土耳其的字符İ。İ经过该函数处理可以变成i。

注意,该函数在不同的环境下有不同的表现,目前经过测试,PHP5.6是可以成功变成i的,7.x的版本,会变成另一个类似i的长度为3的字符。具体实现有兴趣可以查阅PHP各个版本的mb_strtolower函数代码。

到这里,i的限制就可以绕过了。c,h,ph并没有找到类似的字符。有兴趣可以查阅https://www.compart.com/en/unicode/。

因为ph、h和c的限制,直接上传php文件或者.htaccess文件应该是不太现实了。

后来经过扫描发现该站还有一个example.php,会解压zip文件,因此可以上传一个zip。

下面只展示一些关键代码:

if($ctf=="poc") {
    $zip = new \ZipArchive();
    $name_for_zip = "example/" . $_POST["file"];
    if(explode(".",$name_for_zip)[count(explode(".",$name_for_zip))-1]!=="zip") {
        die("要不咱们再看看?");
    }
    if ($zip->open($name_for_zip) !== TRUE) {
        die ("都不能解压呢");
    }

    echo "可以解压,我想想存哪里";
    $pos_for_zip = "/tmp/example/" . md5($_SERVER["REMOTE_ADDR"]);
    $zip->extractTo($pos_for_zip);
    $zip->close();
    unlink($name_for_zip);
    $files = glob("$pos_for_zip/*");
    foreach($files as $file){
        if (is_dir($file)) {
            continue;
        }
        $first = imagecreatefrompng($file);
        $size = min(imagesx($first), imagesy($first));
        $second = imagecrop($first, ['x' => 0, 'y' => 0, 'width' => $size, 'height' => $size]);
        if ($second !== FALSE) {
            $final_name = pathinfo($file)["basename"];
            imagepng($second, 'example/'.$final_name);
            imagedestroy($second);
        }
        imagedestroy($first);
        unlink($file);
    }
}

阅读代码可以看到,该部分会解压上传的zip文件,并读取zip中的图片文件(没有限制文件名,也就是可以用PHP),重设大小后在保存。这部分会对把木马写在图片末尾的图片马造成影响,木马不会被写入新的文件。因此需要把木马写入正常的数据块中。

具体操作

首先自己写一个文件上传的表单,这个没啥难度,注意file的name字段设为postedFile即可。

随后上传一个zip文件,并设置文件的width和height,以及文件名。

上传的文件可以用https://github.com/huntergregal/PNG-IDAT-Payload-Generator/项目来在数据块中写入payload。

上传成功后,访问example.php,设置好相关参数触发解压操作,之后蚁剑连接/example/a.php,即可找到flag。

posted @ 2022-03-05 22:37  b1u3s  阅读(464)  评论(0编辑  收藏  举报