
1. 先给结论这道题考的是 Apache Shiro 的身份认证绕过春秋云境上标注为CVE-2016-6802的这道靶机题核心是 Java Web 安全里非常经典的一类问题Apache Shiro 的 URL 过滤器链与实际 Servlet 容器解析的路径不一致导致身份认证被绕过。靶机里给的是一个基于 Spring Boot 或传统 Servlet 的 Web 应用Shiro 负责登录认证和权限过滤但它的版本停留在 1.3.2 之前正好落在漏洞影响范围内。如果你第一次接触这个 CVE先不用急着背 PoC。我先把攻击目标说清楚正常情况下没有登录的用户访问/admin这类管理接口会被 Shiro 拦截并重定向到登录页。但利用..;这个特殊路径组合可以让请求在 Shiro 的眼皮底下“改头换面”最终由 Tomcat 解析成另一个受保护路径从而绕过 Shiro 的认证拦截直接拿到管理页面的内容。整个过程只需要一次精心构造的 HTTP 请求。这类漏洞在真实业务里的杀伤力很大因为 Shiro 是 Java 生态里占有率很高的安全框架凡是用了旧版本、并且权限配置只保护了一部分 URL 的应用都可能被打穿。春秋云境把这题单独拎出来也是因为它足够典型不涉及反序列化、不需要爆破密码、不打复杂的 Java 内存马纯粹是“框架对 URL 的处理方式”出了问题。理解它对后面理解 Shiro 系列的所有路径绕过漏洞都很有帮助。下面我会从漏洞根因、靶场复现、修复方式三个层面展开最后再把 Shiro 后续几年出现的同类绕过问题串起来讲一遍。我不打算只给一个 curl而是想让你真正搞清楚为什么..;能骗过 Shiro为什么又是 Tomcat 放它进去的。2. 根因拆解同一个 URLShiro 和 Tomcat 为什么会得出两个结果2.1 Shiro 的 URL 过滤器链是怎么工作的先回顾一下 Shiro 在 Web 应用里的基本工作方式。通常开发者会在配置里写类似这样的过滤链[urls] /index.html anon /login anon /admin/** authc /** anon这段配置的意思是/login和静态页面可以匿名访问/admin/**必须认证其他路径默认放行。Shiro 启动时会用PathMatchingFilterChainResolver这个类把每个请求的 URI 和上面的 URL 模式做匹配找到对应的过滤器链然后执行。这里的关键点是Shiro 用来匹配的“请求路径”不是容器最终解析完的那个路径而是它自己根据request.getRequestURI()、request.getServletPath()、request.getPathInfo()这些信息拼出来的一段字符串。然后它再用AntPathMatcher去做模式匹配。AntPathMatcher是 Ant 风格的通配符规则*匹配一段路径**匹配多层路径逻辑上很直观。问题在于Shiro 做匹配的时候对 URI 里的分号;处理得不够到位。;在 Servlet 规范里是路径参数的分隔符比如/admin;foobar里的;foobar是路径参数Tomcat 最终会把这段参数剥掉只把/admin交给后面的 Servlet 逻辑。但旧版 Shiro 在拿 URI 做 URL 模式匹配时没有像 Tomcat 那样先剥离路径参数于是/admin;foobar在 Shiro 这里就是一段完整的字符串和/admin/**这种模式比较时预期结果自然就不一样了。2.2 Servlet 容器对路径参数的解析规则再看 Tomcat 这一侧。Tomcat 处理请求 URL 时有几个步骤先做字符解码和路径规范化。识别分号;之后的路径参数并在真正执行 Servlet 映射时移除。对.和..做目录层级折叠。举个例子假设应用的上下文路径是根路径请求是GET /hello/..;/adminTomcat 先看到/hello/..;/admin其中..;这个段比较特殊它先把分号后面的内容视为路径参数移除之后剩下的就是..。然后hello/..折叠到上一级目录最后路径变成/admin。也就是说真正被分发到 Spring MVC 或者某个 Servlet 的请求是/admin。而 Shiro 呢Shiro 从request.getRequestURI()拿到的还是/hello/..;/admin这个原始形态它不会做和 Tomcat 完全一样的规范化。它拿着这段字符串去匹配 URL 模式时发现/hello/..;/admin根本匹配不上/admin/**于是放行。攻击就这样完成了。2.3..;组合之所以有效原因在于两个解析器的不一致你可能会想Shiro 好歹也是安全框架怎么连..都不处理其实问题比这更深Shiro 作为一个 Filter在 Java 容器里执行顺序靠前它拿到的 request 对象还没经过最终的路由决策。而 Tomcat 对路径的处理分为好几个阶段Shiro 实现getPathWithinApplication时依赖的是request.getRequestURI()或request.getServletPath()不同容器、不同版本的返回值都会有差异。..;这种写法正是利用了“Shiro 读到的 URI 和 Tomcat 路由时使用的规范化 URI 不一样”这个缝隙。我用一句话总结漏洞本质过滤器链权限判断的对象和真正执行业务逻辑的对象不是同一个路径所以权限判断失去了意义。如果你理解了这个本质就会发现它不只影响..;这一种形式。分号路径参数、URL 编码、;与..的组合、上下文路径的差异、Spring 容器路径匹配方式都可能出现类似的绕过。后面 2020 年爆出的一连串 Shiro CVE其实都是这个本质在不同容器、不同版本下的变种。3. 春秋云境靶场完整复现从信息收集到拿到受限接口3.1 启动环境和信息收集先在春秋云境平台上创建对应题目启动后会给一个实验地址长这样http://192.168.x.x:8080自己本机开着 Burp Suite、curl 或者浏览器都行。我先用浏览器访问首页看到的是一个普通 Java Web 应用可能是登录页、主页或者某个管理入口。直接用 curl 看响应头curl -i http://192.168.x.x:8080/正常情况下如果应用用了 Shiro 的默认会话机制会在响应头看到Set-Cookie: JSESSIONID...。如果页面上还有/login、/logout这类路径基本可以判断是 Shiro 在做认证。再从页面框架、报错信息、版本号等线索进一步确定它使用的是老版本 Shiro。3.2 验证正常访问受保护路由常见的靶场设计里/admin通常是被保护的管理后台。直接敲curl -i http://192.168.x.x:8080/admin/返回状态码一般是302Location 指向登录页/login或者直接返回401 Unauthorized。这一步是为了建立基线正常情况下这个路径必须认证才能访问。我在实际测试时还会顺手确认一下/admin是否真的存在比如访问后是否跳到别的接口还是返回 404。如果 404说明这个应用里/admin不是有效路由后面绕过也就没有意义。靶机里的路径可能不叫/admin也可能是/manager、/panel、/console复现时留意界面上的功能入口即可。3.3 构造 PoC 请求并验证绕过生效关键一步来了。我把公开目录和受保护目录之间的路径用..;串起来curl -i http://192.168.x.x:8080/index/..;/admin/这个请求发给 Tomcat 后Tomcat 会这样理解/index/..;/admin/先剥掉;之后的路径参数于是/index/../admin//index/..折叠到根最终变成/admin/也就是说容器最终执行的是/admin/这个路径对应的 Controller。但 Shiro 拿到的 URI 还是/index/..;/admin/。它用这段路径去和配置里的/admin/** authc匹配自然匹配不上。再加上配置里通常有一个兜底规则/** anon或者默认放行策略于是这个请求被当成匿名请求放行最终由 Spring MVC 的 DispatcherServlet 转发到/admin对应的处理方法上。我实际测试时返回结果往往是HTTP/1.1 200 OK响应正文里就是管理后台页面、敏感配置或者题目要求的 flag 字符串。而直接访问/admin/得到的却是 302 跳登录。3.4 别的几个 PoC 变体可以都试一下..;的位置不同效果也可能不同。我在靶场上试过下面几种都值得记录curl -i http://192.168.x.x:8080/aaa/..;/admin curl -i http://192.168.x.x:8080/aaa/..;/admin/ curl -i http://192.168.x.x:8080/aaa/bbb/..;/../admin原理上都是制造“Shiro 看成普通路径、Tomcat 解析成另一个路径”的差异。如果你用 Burp Suite 重放建议把请求发到 Repeater 里方便观察响应状态码和 Location 的变化。不过有一点要提醒如果靶场应用把/**统统配置成了authc也就是所有请求都必须登录那么/index/..;/admin虽然匹配不上/admin/**却可能匹配上/**照样会被 Shiro 拦截。这是很常见的现象不代表 CVE 不存在而是靶场环境把全局规则保护得比较严。真正存在漏洞的配置场景是只保护了/admin/**等部分路径其他路径走匿名放行。这恰恰说明学习这个漏洞时不能只复现 PoC还要学会看过滤链的完整配置。3.5 拿到受限接口之后怎么确认漏洞影响如果只是拿到一个空的管理页面可能还不直观。我会再去找后台里的“功能点”比如用户管理页面系统配置页面文件上传/文件读取接口操作日志查询接口如果能读取到这些敏感内容说明绕过认证之后后续的所有权限控制也一并缺失。这也是这类漏洞在真实环境里的实际危害它绕过的不是“某一个 URL 的认证”而是整个 Shiro 过滤链对会话状态的判断。攻击者不需要拿到任何账号密码就能把一个只能匿名访问的请求直接变成管理员身份才允许的操作。4. 修复与加固升级之外还需要做哪些防护4.1 升级 Apache Shiro 版本CVE-2016-6802 在 Shiro 1.3.2 版本中修复官方在WebUtils相关的路径处理逻辑里增加了对分号路径参数的剥离和路径规范化逻辑。所以最直接的修复动作就是升级dependency groupIdorg.apache.shiro/groupId artifactIdshiro-web/artifactId version1.3.2/version /dependency如果项目还在用更老的 1.x 系列至少升到 1.3.2 对应分支的最新版本。如果业务可以接受建议直接升到 1.10 以上甚至 2.x因为后面还有多个路径绕过漏洞被修复单纯停在 1.3.2 只能解决 2016 年之前的已知问题。升级之后不能光看 pom 文件要回归一遍所有涉及 URL 权限控制的路径尤其是带分号、带编码字符、带..的边界请求。我在实际项目里遇到过升级之后部分用户自定义路径匹配失效的情况因为新版对路径参数的剥离规则更严格之前能匹配上的旧规则可能不再匹配。回归测试至少要把以下请求都跑一遍curl -i http://target:8080/admin curl -i http://target:8080/admin/; curl -i http://target:8080/admin;/test curl -i http://target:8080/aaa/..;/admin curl -i http://target:8080/%2e%2e;/admin4.2 配置层面补齐默认拒绝所有未知路径大量 Shiro 路径绕过漏洞出现的第二个原因是开发者习惯把过滤链写成“只保护部分路径”其余全部用anon放行。比如[urls] /admin/** authc /** anon这种写法看着简洁安全边界其实非常脆弱。任何针对/admin的路径匹配绕过都会直接打到未受保护的区域。更稳妥的写法是[urls] /login anon /static/** anon /** authc只把明确的公开资源匿名放行其余全部要求认证。如果业务上确实有些接口允许匿名也应该在代码里用注解或接口级逻辑做二次控制而不是依赖过滤器链的“排除法”。4.3 在网关层统一剥离分号路径参数Java 容器里的过滤器链可能因为版本、组合不同而出现细微差异所以我更建议在反向代理、API 网关或者统一入口 Filter 里提前把分号和..这类风险字符做规范化处理。例如在 Nginx 层可以配置规则拒绝包含分号的请求或者在自定义 Filter 里对requestURI做二次处理String uri request.getRequestURI(); if (uri.indexOf(;) ! -1) { // 剥掉分号及后续内容 }但这里有个坑不同容器解析分号的方式不完全一样比如 Tomcat 和 Jetty 对/admin;foo的理解就可能有差异。你要在自己的目标容器上验证剥离逻辑是否一致否则网关处理了一层到了 Shiro 那里又拿到另一个值仍然可能产生绕过的缝隙。4.4 防御纵深不要只依赖 URL 过滤器Java Web 应用的安全边界不能只压在 Shiro 这一层。即使 Shiro 的路径匹配已经修复得很好Spring MVC 自身的 URL 匹配、Servlet 映射方式、Framework 版本差异还是会带来新的不一致。我在做加固建议时会把权限验证下推到方法级使用 Spring Security 的PreAuthorize、PostAuthorize或者在业务代码入口里再判断一次“当前用户是否有权访问该资源”。这样即使上层过滤器被绕过业务代码仍然能拦住非法访问。5. 由这道题延伸到整个 Shiro 路径绕过系列复现完 CVE-2016-6802你会发现它的核心思想可以套用到后面好几个漏洞上CVE-2020-1957Shiro 1.5.2 之前对路径中..的处理不完整攻击者构造/xxx/../admin这类路径同样可以绕过过滤链。CVE-2020-11989影响 Shiro 1.5.2 至 1.5.3 之前利用 Spring 对路径的额外规范化形成另一种绕过。CVE-2020-13933影响 Shiro 1.5.3 及更早版本漏洞根因还是路径规范化和分号处理不一致攻击者用分号路径参数绕过了/*这类匹配规则。这几个 CVE 的触发前提高度相似Web 应用容器是 Tomcat、Jetty 这类严格遵循 Servlet 规范的容器Shiro 的过滤器链配置没有把/**全局锁定开发者只保护了一部分 URL。所以你在春秋云境上看到的“CVE-2016-6802”这道题完全可以作为后续几个 CVE 的引子做成一整套“Shiro 路径绕过专项训练”。我在实际练习时喜欢用下面的表格把这几个漏洞放在一起对比CVE影响版本核心绕过点CVE-2016-68021.3.2 之前分号路径参数..;CVE-2020-19571.5.2 之前..路径遍历CVE-2020-119891.5.2 至 1.5.3 之前Spring 路径规范化差异CVE-2020-139331.5.3 及更早分号配合特殊字符练完 2016 这个版本后再去看 2020 年的几个你会发现原理没有变只是绕过的写法更刁钻、影响范围更大。对我来说真正有价值的技术经验不是记住某个 PoC而是能从一个漏洞里总结出判断思路拿到一个 Java Web 应用时先确认它用的安全框架是什么版本再确认 URL 过滤链的配置形态最后用路径规范化差异去测试边界。这套方法论比单一 CVE 的利用方法值钱得多。最后分享一个我自己的做题习惯在春秋云境这种可重复启动的靶场里我会故意把请求改成各种畸形形态逐步观察 Shiro 返回的状态码差异以此来推断过滤链的匹配规则。比如先用curl -i看 302再改路径参数看是不是变成 200再把/admin换成真实管理接口的可疑子路径一步一步缩小范围。这种“黑盒推断过滤链”的思路在真实渗透测试里同样管用而且远比死记硬背某一条漏洞利用命令更能应对环境差异。