目录遍历、越权、信息泄露这三类漏洞,在我做安全测试的这几年里,几乎是出场率最高的“老三样”。很多系统表面上看风平浪静:登录有验证码、接口有签名、后台有权限控制,但一旦把注意力放到那些不起眼的边缘功能上——一个文件下载接口、一个订单查询参数、一个调试开关,问题就像竹筒倒豆子一样全出来了。某个客户系统就是这么翻车的:下载附件的地方能用../跳到服务器任意目录,订单查询的接口改一下ID就能看到别人的订单,一个没关掉的Actuator端点直接把JVM内存里的数据库密码和Token全部暴露了出来。这篇东西我就把这三大类问题的原理、利用姿势、真实案例和修复方案摊开来讲,内容偏实战,适合刚接触Web安全的测试人员,也适合写代码的时候想少踩几个坑的开发朋友。
1. 三类漏洞的共同底色:都在突破“信任边界”
很多人第一次接触这三个漏洞,会觉得它们风马牛不相及:一个和文件路径有关,一个和权限逻辑有关,一个和数据暴露有关。但如果你把它们放在“信任边界”的框架下去看,会发现它们的底层逻辑出奇地一致。
1.1 信任边界:从用户输入到资源访问
先问一个问题:一个Web应用,到底在信任什么?
它在信任用户的输入——参数里给什么文件名,就去拼接路径找文件;它在信任会话的身份——只要登录过,就认为你有权限访问某些数据;它也在信任自己的运行环境——认为内存中的数据不会被外部窥探,认为网络传输是安全的。
这三份信任恰恰是攻击者最想打破的东西。目录遍历打破的是“输入信任”,攻击者往文件路径参数里塞入../,就能逃出web目录的围栏去读取服务器上的任意文件;越权打破的是“身份信任”,攻击者明明只是个普通用户,却因为接口只校验了“是否登录”而没校验“能否操作这份数据”,把别人的订单、文档、配置看个精光;信息泄露打破的则是“环境信任”,应用开发者默认调试接口不会被人发现,默认内网数据不会传到公网,默认协议加密就万事大吉,结果一个HeapDump文件把密钥送给攻击者,一条3DES弱加密套件让通讯内容在理论上有被破解的风险。
用一个生活化的例子来理解:这就好比一栋写字楼,目录遍历相当于你拿着任意楼层的门禁卡刷开了机房的门——本来只该让你进入一层的接待区,但门禁系统压根没检查你会刷到哪一层;越权相当于你虽然是普通员工,却跑到别的工位拉开了抽屉翻别人的文件;信息泄露则相当于公司的玻璃幕墙没贴隐私膜,外面路过的人能把会议室屏幕上的PPT看得一清二楚。
1.2 为什么这三类漏洞常常被放在一起讨论
把这三类漏洞放到一起,不仅仅是“它们都是Web安全经典问题”。实际做渗透测试的时候,你很容易发现它们是彼此串联的:目录遍历读到了数据库配置文件,里面躺着数据库账号和Redis密码;顺着这些凭据连进了内网系统,发现前端页面里到处是用户ID,改一下ID就直接水平越权拿到了管理员的个人数据;再翻一翻系统日志,发现日志里居然打印了完整的请求参数,包括Token和验证码。这一套组合拳打下来,一条完整的高危漏洞链就成型了。
从防御方的角度看,这三类漏洞也有一个共同特点——它们都不是“防不住”的漏洞,更准确地说,是“想当然”导致的漏洞。开发者觉得路径会过滤、权限会校验、敏感接口不会被发现,这种想当然比技术栈本身的老旧更加致命。理解了这一层,再看后面每一类漏洞的具体细节,就会有一种“看山不是山”的感觉:你不再只是记住../怎么打、ID怎么改、端点怎么找,而是能站在更高的视角去预判一个系统哪里最容易突破。
2. 目录遍历:从历史经典打法到CVE-2024-38819的演变
目录遍历有另一个流传更广的名字叫“路径穿越”,英文叫Path Traversal。它的危害我想不用多强调——轻则读取/etc/passwd或者web应用的源码,重则配合文件写入功能直接拿服务器权限。它的起源其实非常古老,Apache、IIS、Tomcat这些老牌中间件在二十年前就出过一堆路径穿越漏洞,但直到今天,它依然活跃在各种自研系统里,而且随着框架越来越复杂,出现了新的变种。
2.1 目录遍历的经典成因:路径拼接与过滤缺失
经典目录遍历的成因,简化到极致就一句话:把用户输入直接拼到文件路径里,却没有校验最终路径是否还在允许的目录内。
最常见的代码长这个样子(Java伪代码):
String fileName = request.getParameter("fileName"); String basePath = "/data/files/"; File file = new File(basePath + fileName); // 直接读取文件并返回内容如果用户传进来的fileName是../../../../etc/passwd,拼出来的完整路径就变成了/data/files/../../../../etc/passwd。操作系统解析路径的时候,..会逐级向上跳转,最终读到的就是/etc/passwd。
这类漏洞在真实的系统里通常出现在这些位置:
- 文件下载接口:
?fileName=、?path=、?url=这类参数最容易出问题 - 文件预览接口:在线预览图片、PDF、Office文档时,如果通过文件名直接定位文件
- 导出/导入功能:导出模板、导入回显、批量打包下载等场景
- 主题/模板切换:有些系统允许通过参数切换前端模板、语言包,参数值可能就是目录名
- 压缩包解压功能:压缩包里的文件名如果带
../,解压时会逃逸到指定目录之外(Zip Slip)
经典的手法也相对固定。Linux下用../../逐级上跳,Windows下既有../../也有..\和盘符跳转(比如..\..\boot.ini)。过滤不严的时候还可以结合URL编码绕过:把.编码成%2e,把/编码成%2f,把../整体编码成%2e%2e%2f,甚至二次编码%252e%252e%252f。这些绕过技巧本质上是在利用服务器对URL解码次数和层级的理解差异——你解码一次,前端可能过滤前解码后的结果,但如果过滤器和解码器各处理一层,就会出现绕过的缝隙。
2.2 CVE-2024-38819:Spring Framework静态资源解析的新盲区
如果说老的目录遍历是“裸奔”级的漏洞——代码里压根没过滤——那么近几年的目录遍历漏洞则更隐蔽,它们往往藏在一众知名框架的核心逻辑里。2024年下半年披露的CVE-2024-38819就是一个典型的例子。
这个漏洞出在Spring Framework的静态资源处理模块。简单说,当应用通过WebMvc或者WebFlux配置了静态资源映射时(比如把/static/**映射到classpath:/static/目录),攻击者可以通过构造特殊格式的请求URL,绕过Spring的资源路径校验规则,读取到映射目录之外的文件。
如果你了解Spring处理静态资源的内部机制,就知道它有一个PathResourceResolver,负责把请求URL“翻译”成真实的资源文件。这个解析器本身做了路径规范化处理,会拦截包含..的穿越请求。但问题在于,Servlet容器(比如Tomcat)对URL路径的解析方式和Spring存在差异——当URL中包含分号、参数、特殊编码时,容器和框架各自会先把路径处理一遍,处理结果可能截然不同。攻击者利用这种“解析差异”,可以让容器认为请求的是一个合法静态资源,而让Spring在解析时实际跳出了允许访问的目录,这就绕过了防护。
具体来说,受影响的版本大致包括Spring Framework 5.3.x系列、6.0.x系列和6.1.0到6.1.12之间的版本,官方在5.3.40、6.0.24、6.1.13等修复版本中对资源解析逻辑做了加固。从本质上看,这个漏洞和CVE-2024-38816(HTTP请求走私)的根因有相似之处,都与容器和框架之间对URI各部分的解析差异有关。
这类“框架级目录遍历”的可怕之处在于:业务代码一行没改,防御做得再好,框架底层一出现绕过点,所有依赖该框架的应用全部中招。这也解释了为什么你在做安全监控时,看到扫描器报出这个CVE标识的时候,第一反应不应该是质疑“这个系统没做路径过滤吗”,而是立刻去核对Spring的版本号是否在受影响区间内。
2.3 修复目录遍历的防御要点与绕过套路清单
针对目录遍历,防御方的思路经历了从“黑名单过滤”到“白名单校验”再到“框架级修复”的演进。这里我整理一份实战中验证过有效的防御要点和绕过思路对照:
| 防御手段 | 为什么有效 | 攻击者思考的对应绕过方式 |
|---|---|---|
过滤../、..\等关键字符 | 最基础的黑名单,挡住脚本小子 | URL编码、双编码、Unicode编码绕过 |
使用Path.normalize()规范化后再校验前缀 | 从路径语义上消除.. | 寻找校验与读取之间的路径理解差异(如符号链接) |
| 白名单校验文件后缀/文件名规则 | 从根本上限制输入范围 | 寻找白名单内的敏感文件(如.properties、.log) |
| 把真实文件名保存在数据库,用ID下载 | 用户根本不接触路径 | 遍历ID批量下载所有文件,等于没做访问控制 |
| 升级框架版本 | 修复框架层解析盲区 | 寻找其他框架或容器的同类解析问题 |
我自己在做代码审计的时候,判断一个路径操作是否安全,最有效的办法不是去看过滤逻辑写得多么严密,而是顺着参数追问一句话:最后这个文件的绝对路径,到底有没有被严格限制在一个预设目录里?只要限制住了,过滤不过滤反而是次要问题。反过来,如果只是简单地replace("../", "")而不做最终路径校验,那我几乎可以断定,找一个双写绕过或编码绕过的 payload 就能打穿。
实操中还有一个非常容易被忽略的目录遍历出口——压缩包解压。很多系统有导入功能,接受用户上传的zip并自动解压。如果解压时没校验压缩包内每个条目的文件名,攻击者可以构造一个包含../../shell.jsp的压缩包,解压后文件直接落到了web目录下的可执行目录。这就是业内常说的Zip Slip漏洞,危害不亚于任意文件写入,实际遇到的概率比普通的路径穿越参数还要高。
3. 越权漏洞:水平越权与垂直越权,以及Sa-Token场景下的思考
越权漏洞,用安全圈的行话说叫IDOR(Insecure Direct Object References,不安全的直接对象引用),但在中文语境下,大家更习惯叫它“越权”。它属于逻辑漏洞里最高发的一类,几乎不需要花里胡哨的利用姿势,只要会改参数就能测出来。我见过太多系统,安全测试报告里其他的问题都能整改,唯独越权修复起来最让开发团队头疼,因为它不是打补丁能解决的,而是整个权限模型的架构性问题。
3.1 越权的本质:认证与授权的边界混淆
把越权拆开,通常分两种:水平越权和垂直越权。
水平越权,指的是一个普通用户通过修改请求参数,访问到了另一个同级别用户的私有资源。最典型的就是订单查询:接口长这样/api/order/detail?id=12345,登录状态校验也做了,但ID是连续的数字,把id改成12346,看到的就是别人的订单信息。这类问题通常出在“只校验了登录状态,没校验数据归属”的开发思维上。
垂直越权则是纵向的:低权限用户(比如普通员工)通过构造请求,执行了高权限操作(比如管理员才能调用的接口)。这个在Web应用里也很常见,按钮在前端隐藏不代表后端接口不存在,普通用户直接构造一个POST请求打向/api/admin/deleteUser,如果后端没有做角色校验,同样能执行成功。
为什么这两类问题在真实业务系统中如此普遍?归根结底,是因为很多开发团队把“认证”和“授权”混为一谈了。认证解决的是“你是谁”,登录之后给你发一个Token,之后的每个请求带着Token就默认通过了;但授权解决的是“你能干什么”,这一步需要在每个涉及资源访问的接口里去校验当前用户对目标资源是否有操作权限。很多时候开发只做了前者,后者完全依赖前端隐藏按钮来“实现”——这在安全视角下等于没有防护。
| 对比维度 | 水平越权 | 垂直越权 |
|---|---|---|
| 攻击者身份 | 普通用户 | 低权限用户 |
| 越权方向 | 横向(访问同级他人数据) | 纵向(执行高权限操作) |
| 常见场景 | 订单、用户信息、文件、消息 | 管理后台、审核接口、系统配置接口 |
| 根因 | 缺数据级权限校验 | 缺功能级角色校验 |
| 典型修复 | 校验数据归属,或引入数据权限 | 接口级增加角色/权限鉴权 |
3.2 Sa-Token横纵越权的典型场景复现
之所以要单独提Sa-Token,是因为它是目前Java生态里非常流行的轻量级权限认证框架,大量新项目都在使用。很多开发选型的时候有个错觉:“用了Sa-Token,权限安全就没问题了。”实际上,Sa-Token解决的是会话管理和角色权限控制,它本身不背越权的锅,越权漏洞依然大面积出现在用了Sa-Token的系统中。
我在评估一个基于Sa-Token的业务系统时,碰到过这样一个场景:系统里有一个用户信息查询接口,代码大致是:
@GetMapping("/user/info") public Result getUserInfo(@RequestParam Integer userId) { // 从Session中获取当前登录用户 StpUtil.checkLogin(); // 直接使用前端传入的userId查询 User user = userService.getById(userId); return Result.ok(user); }攻击者登录自己的账号后,把userId改成别人的ID,就查到了别人的手机号、身份证号、家庭住址。这就是教科书级别的水平越权。而另一个管理端接口:
@PostMapping("/admin/resetPassword") public Result resetPassword(@RequestBody ResetDTO dto) { // 只校验登录,没校验角色 StpUtil.checkLogin(); adminService.resetPassword(dto.getUserId()); return Result.ok(); }普通用户登录后直接POST这个接口,就能重置任意用户的密码。这就是垂直越权。从测试的角度看,这两个漏洞发现起来毫无难度,但它们的出现恰恰说明了一个问题:开发用了Sa-Token的checkLogin()checkRole()这类API,但并没有在每一个涉及敏感操作的接口上把该做的权限校验做全。框架给了你武器,但你没有在全部防御点上使用它。
另一个Sa-Token场景里容易出现的“横纵”交织情况,是开发在一个接口里结合了用户身份和数据操作,但没有区分“当前登录用户”和“操作目标用户”。比如一个修改资料的接口,预期是用户只能改自己的资料,但代码从参数里取了userId而不是从会话里取,就造成了水平越权。如果再配合请求另一个管理接口,就又升级成了垂直越权。这就是为什么热词里会把“横纵越权”并列——它们在同一个系统里往往是结伴出现的,检测到了一个,另一个大概率也存在。
3.3 越权检测思路与数据级权限的修复方案
越权漏洞的检测,说到底就是两件事:遍历ID和换角色重放。
遍历ID很好理解——找到一个带数值参数的接口,按顺序或按规律修改参数值,观察响应内容是否包含了不属于当前用户的数据。这里我分享一个提高效率的技巧:不要只盯着数字ID,很多系统用UUID、雪花ID这样的非连续标识,你以为没法遍历,但接口可能同时支持用手机号、邮箱、用户名甚至订单号查询。另外,修改参数的时候要注意观察响应的“间接变化”,有时候接口本身不直接回显数据,但响应时间、报错信息、重定向目标会有差异,这些差异同样可以作为判断依据。
换角色重放则是垂直越权的标准测法:准备普通用户A和管理员用户B两个账号,先用B登录抓取高权限接口的请求包,退掉再换A登录,把请求包原样重放。如果A也能成功执行,那就是垂直越权。这个过程听起来简单,但实际测试中有一个很容易踩的坑——高权限请求包里往往带有管理员专属的Token、Cookie或者自定义Header字段,比如X-User-Role: admin,换账号重放时如果不把这些身份标识一并替换掉,判断结果就会被干扰。
修复方案的层面,我始终坚持一个观点:越权问题要用数据权限模型去解决,而不是靠过滤器去堵。具体有三个层级:
第一层,接口级鉴权,用Sa-Token自带的@SaCheckRole@SaCheckPermission注解或拦截器,把接口对应的角色、权限点写清楚,这一步解决垂直越权。
第二层,数据级校验,在涉及查询和操作的Service层里,把“当前登录用户ID”作为必要条件拼进查询条件或更新条件。比如查询订单,应该用WHERE id = ? AND user_id = ?,而不是WHERE id = ?。这一步解决水平越权。
第三层,统一的数据权限组件,如果在复杂的业务系统里,每个Service手写数据归属校验很容易遗漏,那就需要抽出统一的数据权限框架——比如把数据操作都收敛到DataScope这样的业务查询范围类中,由统一方法附加当前用户的可访问范围条件。
在代码审计中,我见过最不负责任的修复方式,是在接口里判断“如果ID等于当前用户ID才放行”。这种修法等于告诉攻击者:你只需要找到一个别人账号的ID,再想个办法绕过前端校验,就能照样越权。
4. 信息泄露:从HeapDump到传输层泄露的完整视图
信息泄露是整个安全领域里最宽泛的一个话题,从HTTP响应头里带出来的服务器版本号,到数据库里几千万条用户数据被脱裤,都算信息泄露。在这篇文章里,我重点讲两个在2024-2025年热度非常高、而且和前面两类漏洞经常联动的具体场景:Spring Boot HeapDump泄露,以及SSL/TLS弱加密套件导致的协议层信息泄露。
4.1 Spring Boot HeapDump泄露:从端点到密钥
Spring Boot是目前Java后端的事实标准,而Spring Boot Actuator提供了一组用于监控和管理的生产级端点。问题在于,很多团队上线的时候忘记做安全配置,导致这些端点赤裸裸地暴露在公网。其中最有“含金量”的端点之一就是/actuator/heapdump——它能导出当前JVM进程的堆内存快照。
你可能会问,一个堆内存快照能泄露什么?我在分析一次授权测试的heapdump时,用Eclipse MAT打开一个大概1.2GB的堆转储文件,然后做了几步简单操作:
先看字符串信息。HeapDump里存了所有对象的字符串值,直接用MAT的OQL查询,搜索包含password、secret、token、key的字符串,几乎一搜一个准。最常见的泄露内容包括:
- 数据库连接串:里面的
DataSource配置对象会保存JDBC URL、用户名和明文密码 - Redis等中间件密码:Jedis/Lettuce连接池里同样能挖到
- OAuth Token / JWT密钥:内存里往往缓存了调用第三方接口用的access_token、refresh_token以及JWT的签名私钥
- 用户敏感信息:搜索
phone、email、idCard,经常能捞出大量真实用户数据 - SSH/HTTPS私钥:读取配置时加载进来的私钥对象也留在堆里
实操中会发现,heapdump文件动辄几百MB甚至几个GB,直接全量分析非常消耗时间和内存。我的经验是先别急着打开MAT,而是用命令行工具快速过滤:
# 先用strings提取可打印字符串,再用grep过滤关键词 strings heapdump.hprof | grep -E "(jdbc:|password|secretKey|token)" | head -100这一步能快速定位疑似敏感信息的位置和上下文,然后再针对性地用MAT做对象追踪,效率会高很多。还有一种思路是把HeapDump文件丢给专门的分析脚本,很多SRC平台上都有现成的heapdump敏感信息扫描工具,原理其实就是对字符串做规则匹配,比手工看快得多。
这类泄露的修复方案很直接:生产环境禁止暴露/actuator/heapdump、/actuator/env、/actuator/mappings等敏感端点,或者给Actuator端点加Spring Security认证。再往前走一步,还可以在网络安全组层面限制/actuator/*路径只允许内网/堡垒机IP访问,形成双重保险。这里想强调一个理念:敏感端点的暴露面控制,不只是开关一个配置项,而是要在网络层、应用层、运维层各设一道闸。
4.2 SSL/TLS协议信息泄露:CVE-2016-2183原理扫描与验证
第二个信息泄露场景来自传输层。CVE-2016-2183对应的就是SWEET32攻击——一个针对64位分组密码(主要是3DES)的生日攻击。这里的关键点在于“3DES”这类算法使用的是64位固定分组长度的加密,而现代推荐的AES使用128位分组。在分组加密的CBC模式下,当同一密钥加密的数据量达到一定规模时,由于生日悖论,两个密文分组大概率会发生碰撞,攻击者通过对比碰撞块的异或值,可以逐步推算出明文的敏感信息。理论上有趣,实际攻击门槛也很高,因为它要求攻击者能观察大量同密钥密文——差不多要32GB的数据量,而且在现代网络环境下获取如此大规模的密文需要长时间被动监听。
但为什么这个漏洞在扫描报告中如此常见?因为它通常是“原理扫描”——扫描器(比如Nessus、OpenVAS或者各类云安全中心的基线检查)只要发现服务端支持3DES系列的密码套件,比如TLS_RSA_WITH_3DES_EDE_CBC_SHA,就直接报漏洞。它不需要真的监听32GB流量来验证,而是“你配置了弱算法,我就认为你有风险”。这是一种偏稳妥的风险评估思路:你不一定马上就会被攻击,但你的“安全水位”不合格。
验证一个服务端是否支持弱加密套件,可以用OpenSSL手动探测:
# 查看服务端支持的TLS密码套件 openssl s_client -connect target.com:443 -cipher "3DES" </dev/null如果服务端能握手成功,说明它确实支持3DES套件。还可以用nmap --script ssl-enum-ciphers -p 443 target.com做一次全面的套件枚举,输出的结果里会明确列出每类算法的评分。
修复手段就是在Nginx、Apache、负载均衡或者云SLB上,调整TLS配置文件,把3DES从密码套件列表里剔除。比如Nginx是这么配置的:
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';配置完记得用上面的openssl s_client命令反向再测一遍,确认3DES已经彻底关闭。这类问题看起来简单,实际在老旧业务系统里经常被忽略,因为老客户端(比如Windows Server 2008上的IE、老版微信内置浏览器)可能并不支持新的加密套件,运维为了兼容性保留了3DES。这里我给的实操建议是:可以先通过监控确认线上客户端对TLS版本和套件的实际使用分布,如果3DES的使用量已经趋近于零,放心关闭;如果还有零星的兼容性需求,至少要在WAF或网关层针对性地做限制,而不是让弱套件长期暴露。
4.3 信息泄露的“量化”评估:从海量数据里筛选真正的高危对象
在没有出漏洞之前,很多团队对“信息泄露”的理解停留在“哪里有敏感词”的层面,等真正的泄露发生时才慌了神。这就是为什么我强烈建议——安全团队在做日常风险评估的时候,应该用“量化”的思维去看一个系统里有哪些值得保护的信息,以及如果泄露,对业务的影响是几级。
这个“量化”思路在实战中的操作方式大概是:把信息资产按敏感程度分档。第一档被称为“直接危害类”,比如数据库密码、云厂商AK/SK、JWT签名私钥、SSH私钥,这类信息一旦泄露,攻击者可以直接接管系统;第二档是“间接危害类”,比如用户手机号、身份证号、真实姓名、地址,这类信息能辅助社工或者撞库;第三档是“基础泄露类”,比如服务器Banner信息、绝对路径、框架版本号,这类信息单独看危害不大,但能给攻击者做后续攻击提供线索。
有了这个量化的分级之后,再去看一条信息泄露线索,优先处理顺序就非常清晰了——先解决第一档,再逐步收敛第二档,第三档作为常态化治理的范畴。这个思路放在前面的目录遍历和越权场景里同样适用:目录遍历读到了什么文件,决定了漏洞的真实严重性,读到静态页面是一回事,读到配置文件是另一个量级的风险;越权接口能访问什么数据,决定了影响范围,只能看别人昵称是低危,能看到身份证和银行卡那就是高危中的高危。给“泄露”“越权”“遍历”都加上定级维度之后,你写出的渗透测试报告就不再是漏洞名词的罗列,而是一份业务风险管理报告。
5. 一条真实漏洞链:三个问题的组合利用与排查复盘
单独看每个漏洞,可能都让人觉得“不过是几行代码的问题”,但在真实的攻防对抗中,这三个漏洞经常被攻击者串成一条完整的链条。我在一次授权范围内的漏洞挖掘中遇到过一个系统,印象非常深刻,这里把它拆开复盘,你会更清楚攻击者是怎么利用“小问题”撬动“大权限”的。
5.1 组合利用的典型链条:从路径穿越到管理员数据
那是一个政府类的信息管理系统,前端是Vue,后端是标准的Spring Boot + MyBatis Plus。常规的参数注入、未授权访问测了一圈,都没发现明显的入口。随后我把注意力放到了一个不起眼的“附件预览”功能上,请求长这样:
GET /file/preview?fileName=2024/report_001.pdf习惯性地把fileName参数改成../../../../etc/passwd,返回内容前几行直接是root:x:0:0:root:/root:/bin/bash。目录遍历漏洞确认,而且文件内容回显在响应体里。
拿到目录遍历之后,我没有停在这里,而是开始思考一个问题:这个接口能读哪些文件?系统的部署路径能通过报错信息推断出来,于是顺着路径往下读,先是读了application.yml,里面配置了数据库地址、账号、密码以及Redis的密码。我像开盲盒一样又读了一份application-prod.yml,里面的数据库密码是明文,而且能通过公网IP直连。
到这里,攻击者本来已经可以直接连数据库了。但更戏剧性的事情在后面:我在代码包里翻到了一个定时任务类,里面读取的是用户表的统计信息,而用户姓名、身份证号、手机号在开发阶段为了测试方便,全部以明文存了进去。这意味着数据库一旦被攻入,级联泄露的就是所有实名用户信息。
这一个链条走下来,起点仅仅是一个“低危”的目录遍历参数,终点却是数据库拖库级别的信息泄露。如果当时我停在了“验证了目录遍历就收工”,这个问题可能最终只被当作一个“中危”缺陷来修复——说不定还因为找不到具体业务影响被驳回。但实际上它的真实危害已经达到了“严重”级别。这就是为什么我一直强调,做渗透测试一定要有“链式思维”,漏洞的价值在于它能让你够到多远的地方,而不是漏洞本身的名字有多吓人。
5.2 排查链路复盘:越权接口如何加快漏洞定级
再往前推一步,分析一下这套系统里为什么越权和高危泄露会如影随形。
在利用目录遍历读取到应用源码后,我把注意力放在了用户管理模块。通过分析Controller代码,我找到了一个管理端接口,可以查询所有用户的基本资料。按照垂直越权的测试思路,我用一个普通测试账号登录系统,直接构造请求访问这个接口,管理端并没有做角色校验,响应包里把所有用户信息全部吐了出来。这个接口的鉴权逻辑和前端路由里的“管理员菜单”完全绑定——前端把入口隐藏了,后端却连最基本的@RequiresRoles("admin")都没加。
这一个越权接口的问题让整个漏洞链的定级从“敏感信息泄露”直接升级为“大规模用户数据泄露风险”:目录遍历让攻击者拿到了数据库口令,垂直越权让攻击者在不需要任何口令的情况下直接批量获取所有用户信息。两个漏洞互为备份,攻击者甚至可以二选一。从这里你就可以看出一套系统如果没有做好纵深防御,问题之间是如何相互放大的。
5.3 复盘之后的反向检查清单
遇到同类系统的时候,我现在会拿着这样一份清单去做反向检查,这里也分享给读者:
- 文件下载/预览/上传接口,参数里是否直接包含文件名、路径、URL?这些参数经过路径规范化后,是否严格限制在指定目录内?
- 所有涉及用户私有数据的查询接口,请求参数里是否存在用户ID、订单号、文件ID等对象标识?后端是否校验了对象归属?
- 管理端接口,除了前端按钮隐藏,后端有没有做角色/权限注释或拦截器校验?换一个低权限账号重放请求能否成功?
/actuator系列端点、Druid监控页、Swagger文档页面是否对外开放?测试环境代码里配置的数据库口令是否有残留?- 系统日志/报错信息里会不会打印完整的SQL、请求参数、堆栈路径、服务器绝对路径?
每一条如果答案是“没有”或“不确定”,都值得顺着往下挖一挖。这一套检查下来,基本能把系统里目录遍历、越权、信息泄露的高发区扫了个七七八八。
6. 防御体系搭建:从单点加固到全局收敛
单点修复能解决眼前的一个漏洞,但如果不想每次安全测试都返工,建立一套体系化的防御逻辑才是正路。这里我主要从安全设计的层面,聊一聊如何把前面提到的三类漏洞从“被动堵漏”变成“主动设防”。
6.1 输入与路径的标准化治理
目录遍历的修复单点方案很多,但从体系化的角度,根本思路是两条:一是路径白名单化,二是资源ID化。
路径白名单化意味着,与其去写各种过滤规则和正则去对抗无穷无尽的编码绕过,不如直接校验“解析后的完整路径是否以允许的基目录作为前缀”。Java里的标准做法是:
Path basePath = Paths.get("/data/files/").toRealPath(); Path targetPath = Paths.get(basePath.toString(), fileName).normalize(); if (!targetPath.startsWith(basePath)) { throw new SecurityException("非法路径访问"); }注意这里一定要用toRealPath()解析掉所有符号链接和相对路径,然后再做前缀判断。只做normalize()其实还不够,因为攻击者可以利用系统内的符号链接来绕过目录限制。
资源ID化则是更优雅的方案——用户在下载文件时,不传文件名,只传一个数据库里的文件ID,后端根据ID查表拿到真实路径。这样用户完全接触不到文件路径,也就谈不上穿越。这套方案在实际开发里更推荐,但前提是文件表本身要做好权限控制,否则攻击者可以通过遍历文件ID批量下载所有文件——绕过目录穿越,但撞上了越权。这也再次印证了,在复杂系统里,没有“一招鲜”的防御,每个环节都要联动。
6.2 权限与身份的全局管控
越权问题的体系化防御,核心是把权限管理从“接口层”下沉到“数据层”。接口层的鉴权由Sa-Token、Spring Security这类框架统一处理,通过注解或拦截器把每个接口对应的权限标识配置好。数据层的权限则需要在业务查询中统一追加数据范围条件。
我见过一个相对成功的权限模型是这样设计的:系统里有一个全局的@CurrentUser注解,任何接口需要操作数据时,从当前会话中解析出当前用户实体,而不是从前端参数里取身份信息。业务代码里的数据访问,必须通过一个统一的DataPermissionService来包一层,这个服务会在底层把当前用户可访问的组织、角色、数据范围自动拼接成SQL条件。这样一来,开发人员基本不可能再写出“用户A能查用户B数据”的代码,因为身份信息的获取不依赖参数,数据范围又由统一组件兜底。
6.3 泄露面收敛与监控响应
信息泄露的收敛,本质上是一个“暴露面管理”问题。从暴露面的视角看,需要持续回答三个问题:系统对外开放了哪些端口和端点?这些端口和端点是否都是业务必须的?如果被非授权人员访问到,能看到什么敏感信息?
落地到具体行动上,一方面是对中间件和框架的默认端点做“最小化”配置:Actuator只开启必要的健康检查端点,并加认证;Swagger文档在预发环境关闭;报错页面统一返回通用错误页,不打印堆栈。另一方面是给敏感接口的访问行为增加监控和告警:比如一个接口在短时间内被遍历大量ID参数,或者在非工作时间收到大量来自同一IP的请求,这些都应该能通过WAF或者日志分析平台感知到并进行拦截。
SSL/TLS层面的信息泄露也是一样的思路。定期用扫描器检查对外开放的HTTPS服务,确认密码套件的配置是否合理,TLS版本是否足够新。即使业务系统因为历史原因暂时无法升级,也要把弱套件的使用情况纳入监控——一旦发现某个服务开始使用弱套件且流量异常增长,立刻就能定位到问题服务并做定向加固。
最后说一点个人体会吧。目录遍历、越权、信息泄露这三类漏洞,你说它们是“高危漏洞”吗?从评分上看,它们往往徘徊在中危和高危之间,单独出现的杀伤力似乎并不惊人。但放在真实的业务系统里,它们就像几块歪歪斜斜的积木——单独推倒任何一块都无伤大雅,可一旦有人找到正确的顺序,就能拼出一条通往核心资产的路径。我在写了这么多年的安全测试报告之后,最大的一个感受是:没有“小漏洞”,只有被低估的影响。把每一个看着不起眼的路径参数、ID参数、调试端点都认真对待,你会少踩很多坑,也能在安全这条路上走得更稳。