一、前 言
二、路由转发导致权限认证绕过
三、漏洞分析
四、总 结
在 Web 应用程序的开发过程中,安全问题始终是至关重要的。权限认证作为保障系统安全的重要防线,能够确保只有经过授权的用户才能访问特定的资源。然而,由于开发人员的疏忽或者对某些技术细节的理解不够深入,可能会引发一些安全漏洞,导致权限认证机制被绕过。
本文将介绍一种基于转发机制的权限认证绕过漏洞模式。在实际的 Web 应用中,常常会使用过滤器(Filter
)或者Authenticator
做权限认证。但当应用中存在一些可以未授权访问的代码允许用户通过请求参数指定转发的目标 URL 时,就可能会出现权限认证被绕过的情况。这种漏洞可能会使未授权的用户访问到本应受保护的资源,从而对系统的安全性造成严重威胁。接下来,我们将详细探讨这种漏洞模式的具体细节。
Filter机制
Java Filter 是一个实现了 javax.Servlet.Filter 接口的 Java 类,它可以对客户端的请求进行拦截,在请求到达目标资源(如 Servlet、JSP等)之前进行一些预处理操作,也可以在服务器返回响应给客户端之前对响应进行后处理操作。常用在权限认证 、字符编码处理、日志记录等场景。
一般在web.xml中配置如下:
关键点在于dispatcher选项,作用是指定过滤器的触发条件(即请求的转发类型)。可选值如下:
-
REQUEST(默认配置):当客户端直接发送请求时触发过滤器。
-
FORWARD:当请求被转发(使用 RequestDispatcher#forward 方法)时触发过滤器。
-
INCLUDE:当请求被包含(使用 RequestDispatcher#include 方法)时触发过滤器。
-
ASYNC:当异步请求触发时(用于 Servlet 3.0+ 的异步处理场景)。
-
ERROR:当发生错误并进入错误页面时触发过滤器。
Authenticator机制
Authenticator不是Servlet标准,是Tomcat 和 Jetty 各自私有实现中的一个概念,用于实现 Servlet 容器的认证机制。
Servlet容器一般会根据 web.xml 中的配置(login-config 和 security-constraint)来决定何时对请求进行认证。
认证方式可以自定义,也可以使用自带的认证方式:
-
BASIC:浏览器弹出用户名密码框,使用 HTTP Basic 认证头。
-
FORM:自定义表单登录页,用户名密码提交到服务器。
-
DIGEST:HTTP Digest 认证(不常用)。
-
CLIENT-CERT:基于客户端证书的 SSL 认证。
如下是个简单的配置例子,限制了/admin/*资源必须先通过FORM认证:
Tomcat中的Authenticator都继承AuthenticatorBase实现org.apache.catalina.Authenticator接口,以valve(阀门)的形式存在于Pipeline(管道)中。每一层容器中都有自己的Pipeline,它将一系列的 Valve接在一起,形成一个链式调用。当请求到达顶层容器 Engine 后,沿着 Engine → Host → Context → Wrapper 传递,进入某层容器时该容器的 Pipeline 中的 Valve 就会依次被调用对请求和响应进行特定的处理。FormAuthenticator属于context层的valve。当请求到达Authenticator时就会在AuthenticatorBase根据配置决定是否需要认证。
Jetty中的Authenticator都继承LoginAuthenticator实现org.eclipse.Jetty.security.Authenticator接口。启动时解析 web.xml 中 <login-config>或者代码中的配置,自动为 WebApp 配置 SecurityHandler,创建对应的 Authenticator。Jetty 中所有的请求处理逻辑都以 Handler 的形式组织和串联在一起,构成一条请求处理链(Handler Chain),当请求到达SecurityHandler时,就会根据配置决定是否调用对应的Authenticator进行认证。
Authenticator和Filter对比:
特性 |
Authenticator |
Filter(Java EE 标准) |
所属层级 |
Servlet 容器层 |
Web 应用层 |
启用方式 |
web.xml 中配置 <login-config> |
web.xml 或注解 @WebFilter |
认证触发时机 |
容器处理请求初期,早于 Filter 执行 |
请求进入应用后、在 Servlet 执行前执行 |
路由转发机制
RequestDispatcher 是 Java Servlet API 中的一个接口,内部有forward和include两个方法:
它的主要作用是:在服务器内部将一个请求跳转(forward)或包含(include)到另一个资源(如 Servlet、JSP、HTML)中进行处理。区别于重定向,它不会让客户端发起新的请求,属于服务器端的“内部转发”机制。
两者的区别在于forward由目标资源直接生成响应,include后会将目标资源生成的响应包含在当前的Servlet响应中一起发送给客户端。
认证绕过
在转发到目标资源前,需要根据 url-pattern和 DispatcherType类型匹配重新组装FilterChain以便于对请求进行预处理。
Tomcat中只经过FilterChain的处理,并不会经过比Filter更底层的valve等处理。
Jetty中checkSecurity对于forward和include类型的请求默认返回false,所以会直接调用handler.handle,导致根本不用过Authenticator校验。ASYNC和REQUEST才会为true,从而进入if分支进行看是否需要Authenticator认证校验。
因此存在两种路由转发导致的权限认证绕过的模式。
Filter类型认证绕过
Filter的dispatcher选项是经常被开发忽略的点,常常是不配置,或者只配置REQUEST方式,这就导致存在安全隐患。当同时满足如下条件时则可以权限认证绕过:
-
权限认证是在Filter进行,且dispatcher选项中未配置FORWARD或者INCLUDE;
-
存在未授权的代码通过未配置选项对应的forward或者include方法向某个url转发请求且url可控。
Authenticator等更底层的权限认证绕过
-
权限认证使用比Filter等更底层的认证方式,如Tomcat的Valve或者Jetty的Authenticator;
-
存在未授权的代码通过forward或者include方法向某个url转发请求且url可控。
一个Filter类型的小例子
DspServlet代码如下,service方法使用req.getRequestDispatcher方法根据url获取RequestDispatcher对象后调用forward进行路由转发,url可控。
AdminServlet代码如下:
当直接访问/admin时,会被AuthFilter拦截:
但是通过DspServlet路由转发的方式访问则能绕过AuthFilter拦截,此时从DspServlet#service到AdminServlet#service的调用栈中可以看到不经过AuthFilter#Filter。
根据上述例子可知,路由转发时需要先根据url匹配某个Servlet-mapping的url-pattern的获取javax.Servlet.RequestDispatcher,那么如何获取呢?通常有如下几种常用的方式:
-
javax.servlet.ServletRequest#getRequestDispatcher
-
javax.servlet.ServletContext#getRequestDispatcher
-
javax.servlet.ServletContext#getNamedDispatcher
-
jsp中运行时包含和转发的形式:
在Tomcat跟踪调用流程就能发现:
-
javax.Servlet.ServletRequest#getRequestDispatcher最终还是通过调用javax.Servlet.ServletContext#getRequestDispatcher去获取RequestDispatcher。
-
jsp的运行时包含和转发的形式其实是调用javax.Servlet.ServletRequest#getRequestDispatcher方法去完成的
接下来看Tomcat中对path进行处理的关键方法:
org.apache.catalina.connector.Request#getRequestDispatcher中判断path中有#,则直接截断。
org.apache.catalina.core.ApplicationContext#getRequestDispatcher中去除path中的参数部分后调用stripPathParams和normalize进行处理:
org.apache.catalina.core.ApplicationContext#stripPathParams的作用就是清除path中;xxx/(不包括/)的部分:
org.apache.Tomcat.util.http.RequestUtil#normalize方法的作用就是如果path以/.或者/..结尾,则在末尾添加/再对//、/../ 、/./做规范化处理:
org.apache.catalina.core.ApplicationContext#getNamedDispatcher中通过name获取当前context的子容器:
在Tomcat的中结构如下,所以context的子容器是Wrapper,每个Wrapper负责管理一个具体的Servlet类的生命周期。Wrapper的name对应Servlet-name:
根据上述分析可知:
-
getNamedDispatcher是根据Servlet-name进行转发的。
-
其他getRequestDispatcher是通过对一个path经过处理后能匹配一个Servlet-mapping的url-pattern进行转发的。Request类和ApplicationContext类的处理规则表如下:
原始path |
Request |
ApplicationContext |
---|---|---|
/a/./admin#xxx |
/a/./admin |
/a/admin |
/a/./admin;yyy#xxx |
/a/./admin;yyy |
/a/admin |
/a;xxx/admin.jsp;yyy |
/a;xxx/admin.jsp;yyy |
/a/admin.jsp |
/a/../admin;yyy#xxx |
/a/../admin;yyy |
/admin |
/a/./admin/.. |
/a/./admin/.. |
/a |
/a/./admin/. |
/a/./admin/. |
/a/admin |
/a;xxx/admin.jsp |
/a;xxx/admin.jsp |
/a/admin.jsp |
/abc;xxx/yy |
/abc;xxx/yy |
/abc/yy |
CVE-2022-31692 Spring Security认证绕过漏洞
5.7.0 <Spring Security<5.7.5和 5.6.0 <Spring Security<5.6.9 版本,可以通过forward或include的dispatcher类型绕过授权规则。
如果一个应用的spring security配置如下,定义了一个有ADMIN角色的用户,并配置/admin需要有ADMIN角色才能访问。
对于如下controller,当不登录或者用户没权限时访问/admin会提示未授权,访问/dsp时却能访问到/admin资源:
其核心原因是进行权限认证关键类AuthorizationFilter继承自OncePerRequestFilter ,而OncePerRequestFilter 的作用就是保证其子类在每次请求中只执行一次,所以当访问/dsp时AuthorizationFilter已经触发一次,forward:/admin时将不再触发认证流程导致认证绕过。
CVE-2022-40664 Apache Shiro身份认证绕过漏洞
Apache Shiro<1.10.0时,通过RequestDispatcher include或forward可以造成身份验证绕过漏洞。
一个应用的Shiro配置部分代码如下,指定/admin需要认证,/dsp不需要:
当不登录时访问/admin会提示未授权,访问/dsp时却能访问到/admin资源:
该漏洞核心原因和Spring security类似,当Forward时request已经有alreadyFilteredAttributeName标志了,所以就不进入Shiro内部Filter链检查当前url是否需要认证,所以导致认证绕过。
CVE-2021-44515 ManageEngine Desktop Central身份认证绕过漏洞
ManageEngine Desktop Central<10.1.2127.18容易受到身份验证绕过攻击,导致在服务器上远程执行代码。
该漏洞的关键点在于StateFilter的路由转发。web.xml的配置如下:
doFilter方法中获取path后通过request.getRequestDispatcher(path).forward进行转发。
getForwardPath中截取requestURI的一部分返回,因此是可控的。
ChangeAmazonPasswordServlet可以直接修改用户密码。
在web.xml配置如下:
同时在<security-constraint>中配置/changeDefaultAmazonPassword资源不限制角色但需要登录才能访问,认证是在CUSTOMFORM856中做的。
CUSTOMFORM856是自定义的Authenticator,以valve的形式生效于Filter之前。
<security-constrain>中没有配置/STATE_ID/*资源,不会经过CUSTOMFORM856认证校验,Forward方式转发到/changeDefaultAmazonPassword只会经过相应的Filter,所以可以权限认证绕过。对应的部分调用栈如下:
CVE-2024-33535 zimbra本地文件包含漏洞
Zimbra Collaboration(ZCS)9.0和10.0中发现了一个问题。该漏洞涉及web应用程序中未经身份验证的本地文件包含(LFI),具体影响了packages参数的处理。攻击者可以利用此漏洞在没有身份验证的情况下包含任意本地文件,从而可能导致对敏感信息的未经授权的访问。该漏洞仅限于特定目录中的文件。
该漏洞是由于/public/admin.jsp文件使用<jsp:include>标签进行动态包含page,其中pname可控,虽然zimbra的Servlet容器是Jetty,但也可以../目录穿越和;截断psuffix,从而达到访问非jsp类型的url。
web.xml中配置的/downloads/*路径会被org.eclipse.Jetty.Servlet.DefaultServlet处理,但需要经过ZimbraAuth的认证。
Jetty-env.xml使用 ZimbraAuthenticatorFactory 创建自定义认证器ZimbraAuthenticator,只对某些路径(如 /downloads/*)启用认证。
当不提供用户凭证访问时会被zimbra自定义的ZimbraAuthenticator拦截,提示未授权。
当通过public/admin.jsp路由转发时则会绕过ZimbraAuthenticator拦截。
本文介绍了Java中Filter机制、Tomcat以及Jetty中的Authenticator机制,深入剖析了路由转发导致权限认证绕过的原理,并分析了Tomcat中对于要转发的url的处理特性,举了几个CVE的例子对该漏洞模式进行详细说明。
【版权说明】
本作品著作权归Mmuz所有
未经作者同意,不得转载
天工实验室安全研究员
专注于代码审计、Web安全
原文始发于微信公众号(奇安信天工实验室):基于路由转发导致的权限认证绕过漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论