Shiro认证绕过漏洞深度解析:从CVE-2010-3863到CVE-2020-1957的路径处理逻辑剖析
2026/7/4 17:10:13 网站建设 项目流程

1. 项目概述:一次对Shiro认证逻辑的深度“外科手术”

在Java Web应用安全领域,Apache Shiro是一个绕不开的名字。它以其简洁、易用的特性,成为了众多开发者进行身份认证、授权、加密和会话管理的首选安全框架。然而,正如任何复杂的软件系统一样,Shiro在追求功能强大的同时,其内部复杂的URL路径处理逻辑也潜藏着风险。今天,我们不谈泛泛的安全概念,而是拿起“手术刀”,深入剖析两个极具代表性的Shiro认证绕过漏洞:CVE-2010-3863和CVE-2020-1957。这两个漏洞的触发点看似简单——一个是路径中的“/./”,另一个是URL末尾的“;”分号——但它们却像两把精准的钥匙,巧妙地绕过了Shiro精心构建的认证防线,直击其路径匹配逻辑的核心缺陷。

理解这两个漏洞,远不止是记住两个CVE编号和Payload那么简单。它是一次对Shiro过滤器链、Ant风格路径匹配器、以及Servlet容器(如Tomcat)默认行为之间微妙交互的绝佳学习机会。对于安全研究人员,这是挖掘逻辑漏洞的经典案例;对于开发人员,这是理解框架“黑盒”、编写更健壮代码的必修课;对于运维和架构师,这是审视现有系统安全水位、制定有效修复策略的关键参考。我们将从漏洞的触发现象出发,层层剥开表象,直抵其底层逻辑根源,并最终给出从代码层到架构层的立体化修复与防御方案。这趟旅程,将让你对Web安全有一个更深刻、更落地的认识。

2. 漏洞背景与核心概念解析

在深入漏洞细节之前,我们必须先搭建起必要的基础知识舞台。Shiro的安全模型核心是它的过滤器链。当你在web.xml或通过Shiro的ShiroFilterFactoryBean(在Spring环境中)配置了Shiro后,所有到达应用的请求都会首先经过一个名为ShiroFilter的入口。

2.1 Shiro过滤器链与路径匹配

这个过滤器的核心工作之一,就是根据当前请求的URL,决定应该应用哪一条安全规则。这些规则在Shiro的INI配置文件或Java配置中定义,通常形如:

[urls] /index.html = anon /admin/** = authc, roles[admin] /api/** = authc /** = authc

这里的/admin/**就是一个典型的Ant风格路径表达式。authc表示需要认证,anon表示可以匿名访问。当请求到来时,Shiro会拿着请求的URI(注意,是经过Servlet容器处理后的request.getServletPath()request.getPathInfo()的组合,具体取决于配置),按照配置列表中从上到下的顺序,与这些Ant模式进行匹配。一旦找到第一个匹配的模式,就应用该模式对应的过滤器链,后续的模式不再检查。

这里就引出了第一个关键点:匹配的顺序性和首次匹配原则。如果你的配置是/** = authc写在最前面,那么所有请求都会被要求认证,后面的规则就形同虚设了。因此,规则的顺序至关重要。

2.2 Ant风格路径匹配的“陷阱”

Ant匹配规则很强大,支持?(单字符)、*(单层路径)、**(多层路径)。但它的匹配逻辑是基于规范化后的路径字符串进行的。所谓规范化,通常包括将//合并为/,解析.(当前目录)和..(上级目录)等。然而,规范化发生的位置和时机,成为了漏洞滋生的温床。

2.3 Servlet容器的路径解析

另一个关键角色是Servlet容器(如Tomcat、Jetty)。容器在将请求交给Shiro(或者说你的Web应用)之前,会对请求的URI进行自己的解析和处理。例如,Tomcat默认会解码URL编码,并进行一定程度的路径规范化。但容器对某些特殊字符的处理方式,可能与Shiro的预期存在差异,这种差异就是“缝隙”。

理解了这三个角色(Shiro过滤器链、Ant路径匹配器、Servlet容器)及其交互,我们就可以开始审视那两个经典的漏洞了。它们本质上都是利用了路径信息在传递和匹配过程中,在不同组件间的不一致,从而让Shiro的匹配逻辑“看错了”目标路径,匹配到了一个权限更宽松的规则上。

3. CVE-2010-3863:被“/./”撕裂的防线

这是Shiro早期一个非常经典的目录遍历型认证绕过漏洞。它的影响范围是Apache Shiro 1.0.0之前的版本(是的,这是一个上古漏洞,但原理永不过时)。

3.1 漏洞触发场景还原

假设我们有一个简单的Web应用,其Shiro安全配置如下:

[urls] /public/** = anon /secure/** = authc

我们的目标是访问一个受保护的资源/secure/admin.jsp,但我们没有登录,按道理会被重定向到登录页。

攻击者构造了这样一个请求:

GET /secure/./admin.jsp HTTP/1.1 Host: vulnerable-app.com

或者使用URL编码的形式:

GET /secure/%2e%2f/admin.jsp HTTP/1.1 Host: vulnerable-app.com

神奇的事情发生了:用户在没有登录的情况下,直接看到了/secure/admin.jsp的内容。

3.2 底层逻辑深度拆解

这个漏洞的根源在于Shiro的路径匹配逻辑与Servlet容器路径规范化逻辑的顺序错位

  1. 请求进入:攻击者发送请求/secure/./admin.jsp
  2. 容器处理:Servlet容器(如Tomcat)接收到请求后,会首先对请求URI进行规范化(Normalization)。规范化的规则之一,就是解析路径中的..././在Unix和URL路径中代表“当前目录”。因此,容器会将/secure/./admin.jsp规范化/secure/admin.jsp。这是符合RFC标准的正确行为。
  3. Shiro获取路径:漏洞版本的Shiro,在获取请求路径用于匹配时,可能没有直接使用容器规范化后的路径(/secure/admin.jsp),或者是在匹配逻辑的某个环节,错误地处理了包含.的路径。一种典型的情况是,Shiro的路径匹配器在匹配前,没有进行与容器同等严格的规范化,或者规范化规则不同。
  4. 路径匹配错位:关键在于Shiro配置的匹配顺序。它拿着可能还未被完全规范化的路径(或经过不同规则处理后的路径)去匹配/public/**/secure/**
    • 当路径是/secure/./admin.jsp时,Ant匹配器可能不认为它匹配/secure/**,因为/secure/./admin.jsp这个字符串,在简单的字符串匹配或某些Ant实现看来,并不完全符合/secure/**的模式(**期望的是/secure/后直接跟内容,而这里跟的是./)。
    • 由于/secure/./admin.jsp没有匹配到/secure/**,匹配过程继续向下。在很多默认配置中,最后会有一个兜底的规则,比如/** = anon(允许所有匿名访问)。于是,这个请求就匹配到了匿名访问规则上,绕过了认证。
  5. 请求最终路由:尽管Shiro因为匹配错误而放行了请求,但当请求真正被路由到Servlet或JSP进行处理时,容器使用的已经是规范化后的路径/secure/admin.jsp。因此,应用最终正确返回了受保护资源的内容,造成了权限绕过。

核心要点:漏洞的本质是路径规范化的一致性问题。Shiro用于做安全决策的路径,和最终处理请求的路径,在...的解析上出现了分歧。安全框架“看”到的路径(认为不安全,但匹配了宽松规则)和应用程序实际执行的路径(受保护资源)不是同一个。

3.3 修复方案与代码层面的思考

Apache Shiro在后续版本中修复了此漏洞。修复的核心思想是:确保Shiro用于路径匹配的URI,是经过完全、正确规范化后的URI,并且与Servlet容器处理请求时使用的URI保持一致。

具体修复措施包括:

  1. 统一使用HttpServletRequest的标准方法:在PathMatchingFilter(Shiro中负责URL匹配的过滤器基类)中,修复代码确保通过request.getServletPath()request.getPathInfo()来获取路径,并对其进行合并和规范化。这两个方法返回的是容器已经处理过的路径。
  2. 强化路径规范化函数:Shiro内部增强了路径规范化逻辑,确保在处理路径匹配前,能正确移除/./..这样的序列,使其标准化。例如,实现一个类似org.apache.shiro.web.util.WebUtils#getPathWithinApplication的方法,它负责返回一个规范化后的、用于匹配的请求路径。
  3. 配置层面的防御:开发者应避免使用过于宽泛的兜底规则(如/** = anon)。更安全的做法是采用“默认拒绝”策略:明确列出所有允许匿名访问的路径,最后用/** = authc要求认证所有其他请求。这样即使匹配逻辑出现偏差,未明确放行的路径默认也是受保护的。

给开发者的启示:在编写任何与路径处理相关的代码时(不仅是安全框架),都要明确一点:路径字符串必须在使用前进行规范化。Java中可以使用java.nio.file.Path#normalize()org.springframework.util.StringUtils#cleanPath(Spring提供)等工具方法。永远不要相信用户输入的原始路径。

4. CVE-2020-1957:分号“;”带来的意外之旅

时间来到2020年,Shiro再次曝出一个认证绕过漏洞,影响版本为Apache Shiro < 1.5.2。这个漏洞的原理与CVE-2010-3863异曲同工,但利用的“道具”换成了分号;

4.1 漏洞触发场景还原

同样的安全配置:

[urls] /public/** = anon /secure/** = authc

攻击者这次构造的请求是:

GET /secure/admin.jsp;xxx HTTP/1.1 Host: vulnerable-app.com

或者

GET /secure/;xxx/admin.jsp HTTP/1.1 Host: vulnerable-app.com

这里的xxx可以是任意字符,比如jsessionid=ABCD(虽然这不是标准的Session ID传递方式),或者干脆就是;test

结果再次令人意外:认证被绕过,/secure/admin.jsp被直接访问。

4.2 底层逻辑深度拆解

这个漏洞的根源更加微妙,涉及Servlet规范、容器实现和Shiro匹配逻辑的三方博弈。

  1. 分号在URL中的角色:在URL中,分号;传统上被用作“参数”的分隔符,这些参数被称为“路径参数”(Path Parameters)或“矩阵参数”(Matrix Parameters),格式如/path;param1=value1;param2=value2/segment。它们与查询字符串(?key=value)不同,是附着在路径片段(Path Segment)上的。Servlet规范规定,容器在调用request.getServletPath()request.getPathInfo()时,应该去除路径参数部分。
  2. 容器处理:以Tomcat为例,当它收到请求/secure/admin.jsp;xxx时,它会将;xxx识别为路径参数,并在获取Servlet路径时将其剥离。因此,request.getServletPath()返回的值是/secure/admin.jsp
  3. Shiro的匹配逻辑(漏洞点):在存在漏洞的Shiro版本中,用于构建匹配路径的代码可能没有完全遵循Servlet规范的这个细节。它可能在获取路径后,没有正确地处理或剔除分号及其后面的内容。或者,在Ant风格路径匹配器的实现中,分号;被当作了普通字符。
  4. 匹配再次错位:Shiro拿着可能包含;xxx的路径字符串(例如/secure/admin.jsp;xxx)去匹配配置的Ant模式/secure/**
    • Ant模式/secure/**期望匹配以/secure/开头的所有路径。字符串/secure/admin.jsp;xxx是否匹配/secure/**?这取决于Ant匹配器的具体实现。在某些实现中,***通配符可能不会将分号视为特殊分隔符,因此/secure/admin.jsp;xxx这个整体字符串是匹配/secure/**的(因为它确实以/secure/开头)。
    • 但是!关键点在于,攻击者可能利用一个顺序匹配的配置陷阱。假设配置是:
      [urls] /secure/*.jsp = authc /secure/* = authc /public/** = anon /** = anon
      如果Shiro用于匹配的路径是/secure/admin.jsp;xxx,它可能不匹配/secure/*.jsp(因为模式期望以.jsp结尾,而这里是.jsp;xxx)。它也可能因为分号的存在而不匹配/secure/**通常匹配一个路径段,而admin.jsp;xxx可能被视作一个段,但模式/secure/*期望/secure/后只有一个段,而admin.jsp;xxx包含分号,逻辑复杂)。如果它没有匹配到任何/secure/开头的规则,它就会继续向下匹配,最终可能落到/** = anon上,导致绕过。
  5. 请求路由:当容器将请求路由给真正的Servlet或JSP时,它使用的是剥离了路径参数的/secure/admin.jsp。因此,受保护的资源被正确找到并返回,再次完成绕过。

核心要点:这个漏洞的本质是路径参数处理的差异性。Shiro在安全决策时对路径中分号的处理方式,与Servlet容器在路由请求时对分号的剥离行为,存在不一致。安全框架“看到”的路径(带分号,可能匹配了错误或宽松的规则)和应用程序“执行”的路径(不带分号,是受保护资源)再次不同。

4.3 修复方案与最佳实践

Shiro官方在1.5.2版本中修复了此漏洞。

修复的核心思路确保Shiro在进行路径匹配时,使用的路径与Servlet容器路由请求时使用的路径完全一致,即必须剔除路径参数(分号及之后的内容)。

具体技术实现:修复主要发生在PathMatchingFilter或相关的路径解析工具类(如WebUtils)中。代码会显式地检查获取到的路径,并移除第一个分号;及其之后的所有字符,然后再用于Ant模式匹配。这模拟了Servlet容器的标准行为。

例如,在修复后的代码中,可能会看到这样的逻辑:

String requestUri = WebUtils.getPathWithinApplication(request); // 修复:移除路径参数 int semicolonIndex = requestUri.indexOf(';'); if (semicolonIndex != -1) { requestUri = requestUri.substring(0, semicolonIndex); } // 然后使用清理后的requestUri进行路径匹配

给开发者和运维的启示:

  1. 及时升级:这是最直接有效的方法,确保使用的Shiro版本 >= 1.5.2。
  2. 审查安全配置:再次强调规则的顺序。采用“白名单”+“默认拒绝”策略。优先放行明确的公开资源,最后用严格的规则保护所有其他资源。
  3. WAF/网关层防护:在应用前端部署Web应用防火墙(WAF)或API网关,可以配置规则拦截或规范化包含可疑序列(如/./;)的请求。但这只是纵深防御的一环,不能替代代码修复。
  4. 自定义过滤器:如果因故无法立即升级,可以考虑编写一个自定义的Servlet过滤器,放置在Shiro过滤器之前,对传入的HttpServletRequest进行路径清洗,主动移除或规范化.;,为下游的Shiro提供一个“干净”的路径。但这种方法需要谨慎测试,避免影响正常业务。

5. 漏洞的深层共性与防御体系构建

分析完两个具体的CVE,我们可以跳出细节,总结一些更高层次的规律和防御思路。

5.1 漏洞的共通模式

无论是“/./”还是“;”,它们都利用了同一种攻击模式:“语义不一致攻击”

  • 输入多样性:攻击者提供一个“畸形”或“非标准”的输入(包含特殊字符的URL)。
  • 组件解析差异:请求流经的系统组件(负载均衡器、WAF、Servlet容器、Web框架、安全框架、业务代码)对这个输入的解析方式存在细微差别。
  • 安全决策与执行分离:某个组件(这里是Shiro)基于它解析后的视图做出了安全决策(允许访问),而另一个组件(Servlet容器)基于它解析后的视图执行了实际操作(访问受保护资源)。由于视图不同,导致了权限绕过。

5.2 构建多维度的防御体系

修复一个CVE是治标,建立稳固的防御体系才是治本。

  1. 框架层加固(治本之策)

    • 持续更新:密切关注所使用的安全框架、Web框架、容器和依赖库的安全公告,及时打补丁。
    • 安全配置:采用最小权限原则和默认拒绝策略配置Shiro规则。仔细检查规则的顺序,避免宽松规则意外覆盖严格规则。
    • 自定义匹配逻辑:对于关键路径,可以考虑放弃Ant风格匹配,使用精确路径匹配或正则表达式匹配(需注意性能和安全),减少模糊匹配带来的不确定性。
  2. 代码层防御(深度防御)

    • 输入净化:在业务逻辑开始处理请求之前,对URL路径、参数进行严格的验证和规范化。使用标准库函数进行规范化,确保整个应用层对路径的理解是一致的。
    • 权限校验前置:不要完全依赖Web框架层的安全控制。在关键的业务方法、Service层甚至数据访问层,可以再次进行权限断言(例如使用Spring Security的方法级注解@PreAuthorize或自定义AOP拦截),实现多层校验。
  3. 架构与运维层(纵深防御)

    • WAF部署:配置合理的WAF规则,虽然不能防住所有逻辑漏洞,但可以拦截大量已知攻击模式的扫描和自动化攻击。
    • API网关:在微服务架构中,API网关可以作为统一的安全入口,进行身份认证、流量清洗和路由转发,将一些畸形请求拦截在业务系统之外。
    • 安全测试与代码审计:将安全性测试纳入CI/CD流程。定期进行代码审计,特别关注路径处理、URL跳转、权限校验等相关代码。使用SAST(静态应用安全测试)工具辅助发现潜在问题。
    • 监控与告警:建立异常访问监控,例如对频繁访问敏感路径、使用异常User-Agent或含有特殊字符的请求进行日志记录和告警。

5.3 实战排查技巧与心得

在实际工作中,如果怀疑系统存在类似的路径处理漏洞,可以尝试以下排查思路:

  1. 差异测试法:对于同一个资源,分别用正常路径和疑似绕过路径(添加/./,;,//,..等)进行访问,观察响应是否一致。如果一致,则可能存在绕过。
  2. 日志分析法:在Shiro和业务代码中增加详细日志,打印出用于匹配的requestURIservletPathpathInfo以及最终匹配到的安全规则。对比正常请求和恶意请求下这些日志的差异,是定位问题的最直接方法。
  3. 调试跟踪法:在开发或测试环境,通过调试器跟踪org.apache.shiro.web.filter.PathMatchingFilter#getPathWithinApplicationorg.apache.shiro.util.AntPathMatcher#doMatch等关键方法的执行过程,观察路径字符串的变化和匹配结果。
  4. 配置检查清单
    • 检查shiro.ini或Java配置中[urls]部分的规则顺序。
    • 检查是否有过于宽泛的/**规则被过早匹配。
    • 检查是否使用了authcBasic(HTTP基本认证)等不适用于Web浏览器的认证方式,它们可能更容易受到这类绕过的影响。

6. 从漏洞分析到安全开发思维的转变

回顾CVE-2010-3863和CVE-2020-1957,它们不仅仅是两个需要打补丁的漏洞编号。它们像两面镜子,映照出我们在软件开发和安全设计过程中容易忽视的角落。

首先,是对“标准化”和“一致性”的敬畏。Web开发建立在如Servlet规范、HTTP RFC等一系列标准之上。漏洞往往源于对标准理解的偏差或实现的不完整。作为开发者,在处理像URL、头信息、编码这些基础元素时,必须严格遵循标准,并使用经过广泛验证的库函数,而不是自己臆造解析逻辑。

其次,是树立“不信任原则”。安全的第一要义是永远不要信任用户输入。这包括URL路径、查询参数、请求头、Cookie等一切来自客户端的数据。Shiro的这两个漏洞,正是攻击者通过精心构造的“非标准但可能被某些组件接受”的路径输入,钻了系统组件间信任传递的空子。任何来自外部的数据,在参与安全决策、数据库查询、文件操作、系统命令执行之前,都必须经过严格的验证、净化和规范化。

最后,是培养“攻击者思维”。在设计和评审系统时,多问自己几个问题:如果我是攻击者,我会从哪里下手?各个组件对接的边界是否清晰?对同一份数据的理解是否绝对一致?是否有默认放行的“后门”?这种思维转换,能帮助我们在漏洞被利用之前就发现潜在的风险点。

安全是一个持续的过程,而非一劳永逸的状态。通过对这些经典漏洞的深入剖析,我们收获的不仅是修复特定问题的方案,更是一套如何思考系统安全、如何构建防御纵深的方法论。下次当你编写或审查一段处理用户请求的代码时,不妨想想这个“/./”和“;”,它们提醒我们,魔鬼往往藏在最基础的细节之中。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询