zoukankan      html  css  js  c++  java
  • 攻防世界-web-filemanager(源码泄漏、二次注入)

    题目来源:XDCTF 2015
    题目描述:暂无

    进入界面

    这题看似文件上传,其实主要是利用sql注入修改指定文件在数据库中的后缀名为空。

    首先,/www.tar.gz 下载源码

    源码审计

    数据库的字段结构为

    SET NAMES utf8;
    SET FOREIGN_KEY_CHECKS = 0;
    
    DROP DATABASE IF EXISTS `xdctf`;
    CREATE DATABASE xdctf;
    USE xdctf;
    
    DROP TABLE IF EXISTS `file`;
    CREATE TABLE `file` (
      `fid` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `filename` varchar(256) NOT NULL,
      `oldname` varchar(256) DEFAULT NULL,
      `view` int(11) DEFAULT NULL,
      `extension` varchar(32) DEFAULT NULL,
      PRIMARY KEY (`fid`)
    ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
    
    SET FOREIGN_KEY_CHECKS = 1;

    common

    • 对传入的参数进行了addslashes()转义
    • 数据库连接和遍历数组
    • 基本没有直接的注入漏洞
    <?php
    /**
     * Created by PhpStorm.
     * User: phithon
     * Date: 15/10/14
     * Time: 下午7:58
     */
    
    $DATABASE = array(
    
        "host" => "127.0.0.1",
        "username" => "root",
        "password" => "ayshbdfuybwayfgby",
        "dbname" => "xdctf",
    );
    
    $db = new mysqli($DATABASE['host'], $DATABASE['username'], $DATABASE['password'], $DATABASE['dbname']);
    $req = array();
    
    foreach (array($_GET, $_POST, $_COOKIE) as $global_var) {
        foreach ($global_var as $key => $value) {
            is_string($value) && $req[$key] = addslashes($value);
        }
    }
    
    define("UPLOAD_DIR", "upload/");
    
    function redirect($location) {
        header("Location: {$location}");
        exit;
    }
    common.inc.php

    upload

    • 白名单限制了后缀名
    • 查询文件名是否存在,进行了addslashes()转义
    • 也不存在直接注入漏洞
    <?php
    /**
     * Created by PhpStorm.
     * User: phithon
     * Date: 15/10/14
     * Time: 下午8:45
     */
    
    require_once "common.inc.php";
    
    if ($_FILES) {
        $file = $_FILES["upfile"]; //PHP $_FILES 是一个预定义的数组,用来获取通过 POST 方法上传文件的相关信息(比如name、type、tmp_name、error、size等)。如果为单个文件上传,那么 $_FILES 为二维数组;如果为多个文件上传,那么 $_FILES 为三维数组。
        if ($file["error"] == UPLOAD_ERR_OK) { //PHP文件上传完毕之后,返回了UPLOAD_ERR_OK错误代码,这表示文件上传成功
            $name = basename($file["name"]); //basename() 函数返回路径中的文件名部分。
            $path_parts = pathinfo($name); //pathinfo() 返回一个关联数组包含有 path 的信息。包括以下的数组元素:[dirname]: 目录路径、[basename]: 文件名、[extension]: 文件后缀名、[filename]: 不包含后缀的文件名
    
            //上传文件扩展名的白名单
            if (!in_array($path_parts["extension"], array("gif", "jpg", "png", "zip", "txt"))) {
                exit("error extension");
            }
            $path_parts["extension"] = "." . $path_parts["extension"];
    
            $name = $path_parts["filename"] . $path_parts["extension"];
    
            // $path_parts["filename"] = $db->quote($path_parts["filename"]);
            // Fix
            $path_parts['filename'] = addslashes($path_parts['filename']); //文件名转义
    
            //根据上传文件的文件名和扩展名判断数据库中该文件是否exist
            $sql = "select * from `file` where `filename`='{$path_parts['filename']}' and `extension`='{$path_parts['extension']}'";
    
            $fetch = $db->query($sql);
    
            if ($fetch->num_rows > 0) {
                exit("file is exists");
            }
    
            //move_uploaded_file() 函数将上传的文件移动到新位置。若成功,则返回 true,否则返回 false。这里将上传的文件移动到upload/下
            if (move_uploaded_file($file["tmp_name"], UPLOAD_DIR . $name)) {
    
                //数据库插入新上传文件的文件名和扩展名
                $sql = "insert into `file` ( `filename`, `view`, `extension`) values( '{$path_parts['filename']}', 0, '{$path_parts['extension']}')";
                $re = $db->query($sql);
                if (!$re) {
                    print_r($db->error);
                    exit;
                }
                $url = "/" . UPLOAD_DIR . $name;
                echo "Your file is upload, url:
                    <a href="{$url}" target='_blank'>{$url}</a><br/>
                    <a href="/">go back</a>";
            } else {
                exit("upload error");
            }
    
        } else {
            print_r(error_get_last());
            exit;
        }
    }
    upload.php

    delete

    • 就是删除,没什么好讲
    <?php
    /**
     * Created by PhpStorm.
     * User: phithon
     * Date: 15/10/14
     * Time: 下午9:39
     */
    
    require_once "common.inc.php";
    
    if(isset($req['filename'])) {
        $result = $db->query("select * from `file` where `filename`='{$req['filename']}'");
        if ($result->num_rows>0){
            $result = $result->fetch_assoc();
        }
    
        $filename = UPLOAD_DIR . $result["filename"] . $result["extension"];
        if ($result && file_exists($filename)) {
            $db->query('delete from `file` where `fid`=' . $result["fid"]);
            unlink($filename);
            redirect("/");
        }
    }
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <title>file manage</title>
        <base href="/">
        <meta charset="utf-8" />
    </head>
    <h3>Delete file</h3>
    <body>
        <form method="post">
            <p>
                <span>delete filename(exclude extension):</span>
                <input type="text" name="filename">
            </p>
            <p>
                <input type="submit" value="delete">
            </p>
        </form>
    </body>
    </html>
    delete.php

    rename

    • filename=$req['oldname']是从数据库查询输入的oldname是否在于filename字段,然后进行update修改
    • oldname={$result['filename']}将之前从数据库中查询出的filename更新到oldname当中,再次入库造成二次注入
    • 可以通过sql注入,影响其extension为空,再修改文件时加上.php后缀
    • 绕过file_exists()只需要再次上传一个与数据库当中filename的值相同的文件名即可
    <?php
    /**
     * Created by PhpStorm.
     * User: phithon
     * Date: 15/10/14
     * Time: 下午9:39
     */
    
    require_once "common.inc.php";
    
    //isset函数是检测变量是否设置
    if (isset($req['oldname']) && isset($req['newname'])) {
        //先查询数据库中是否已存在旧文件名
        $result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
        if ($result->num_rows > 0) {
            $result = $result->fetch_assoc();
        } else {
            exit("old file doesn't exists!");
        }
    
        if ($result) {
    
            //basename() 函数返回路径中的包含后缀的文件名部分。
            $req['newname'] = basename($req['newname']); 
            
            //更新数据库中的旧文件名(不包含后缀的文件名)为新文件名(包含后缀的文件名)
            $re = $db->query("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}");
            if (!$re) {
                print_r($db->error);
                exit;
            }
            //更新服务器上的旧文件名为:数据库中的新文件名(包含后缀)+数据库中的旧扩展名
            //更新服务器上的新文件名为:客户端输入的新文件名(包含后缀)+数据库中的旧扩展名
            //因此,在这里,我们可以先把数据库中的旧扩展名置为空,然后rename时,把新文件名设置为test.php
            $oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
            $newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
            if (file_exists($oldname)) {
                rename($oldname, $newname);
            }
            $url = "/" . $newname;
            echo "Your file is rename, url:
                    <a href="{$url}" target='_blank'>{$url}</a><br/>
                    <a href="/">go back</a>";
        }
    }
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <title>file manage</title>
        <base href="/">
        <meta charset="utf-8" />
    </head>
    <h3>Rename</h3>
    <body>
    <form method="post">
        <p>
            <span>old filename(exclude extension):</span>
            <input type="text" name="oldname">
        </p>
        <p>
            <span>new filename(exclude extension):</span>
            <input type="text" name="newname">
        </p>
        <p>
            <input type="submit" value="rename">
        </p>
    </form>
    </body>
    </html>

    最重要的就是标红的这5行。 Oldname和newname,有几个特点:

    后缀相同,都是$result['extension'] oldname的文件名来自数据库,newname的文件名来自用户输入 首先后缀相同这个特点,就导致getshell似乎难以完成,如果要getshell那么一定要将“非.php”后缀的文件重命名成“.php”的文件。后缀相同怎么重命名? 除非后缀为空! 所以我们的update型注入就开始派上用场了。通过update型注入,我们可以将数据库中extension字段的值改为空,同时也可以控制filename的值,那么等于说我能控制rename函数的两个参数的值,这样getshell就近在咫尺了。

    但还有个坑,这里改名的时候检查了文件是否存在:if(file_exists($oldname))我虽然通过注入修改了filename的值,但我upload目录下上传的文件名是没有改的。 因为我利用注入将extension改为空了,那么实际上数据库中的filename总比文件系统中真是的文件名少一个后缀。 那么这里的file_exists就验证不过。怎么办? 简单啊,再次上传一个新文件,这个文件名就等于数据库里的filename的值就好了。

    所以最后整个getshell的流程,实际上是一个二次注入 + 二次操作getshell。

    解题过程

    根据源码审计结果,我们知道,代码中由于白名单限制,所以无法上传恶意文件,由于版本限制也不能用%00截断,但有一个rename功能,只能修改文件名,但可以通过sql注入,影响其extension为空,再修改文件时加上.php后缀。

    先上传一个用来sql注入的空文件,文件名为 ',extension='.txt

     

    修改文件名

     

    新的文件名为test.txt.txt,但是数据库中经过update语句

    update `file` set `filename`='test.txt', `oldname`='',extension='' where `fid`={$result['fid']}

    实际上,新文件的filename为test.txt,extension为空

    再上传一个和上面newname文件名相同的木马文件

    rename

    下面直接菜刀连接即可。

     

    参考:

    https://blog.csdn.net/weixin_44604541/article/details/108917121

    https://blog.csdn.net/weixin_43610673/article/details/107503183

    攻防世界官方writeup

  • 相关阅读:
    使用poi读写excel文件
    视频云全球创新挑战赛 — 视频目标分割经典算法解析
    阿里云 RTC QoS 弱网对抗之变分辨率编码
    用 WebRTC 打造一个音乐教育 App,要解决哪些音质难题?
    “蚂蚁呀嘿” 刷屏的背后:算法工程师带你理性解构神曲
    白话解读 WebRTC 音频 NetEQ 及优化实践
    未来直播 “神器”,像素级视频分割是如何实现的 | CVPR 冠军技术解读
    「 视频云大赛 — 大咖驾到 」驱动下一代技术浪潮,我们更专注价值落地
    「 视频云大赛 — 大咖驾到 」下一代技术新浪潮,正由视频云驱动
    视频云大赛|视频目标分割,下一个视频算法技术爆发点?
  • 原文地址:https://www.cnblogs.com/zhengna/p/14025694.html
Copyright © 2011-2022 走看看