近日,Apache Tomcat爆出两个安全绕过漏洞 ,Apache Tomcat 7、8、9多个版本受到影响。攻击者可以利用这个问题,绕过某些安全限制来执行未经授权的操作。本篇文章只对CVE-2018-1305进行简要分析。
两个 CVE
- CVE-2018-1305
- CVE-2018-1304
从造成的影响方面来讲,都是鸡肋。从原理上讲有点像,基本可以认为是同一个。从分析角度来讲,这两个都比较简单。
这里只对 CVE-2018-1305 进行简要分析。
背景知识
Java EE 提供了类似 ACL 权限检查的注解,可以直接用于修饰 Java Servlet,用于对 Servlet 进行 ACL 保护。
简要分析
现假设有两个 Servlet:
- Servlet1,访问路径为 /servlet1/*
- Servlet2,访问路径为 /servlet1/servlet2/*
Servlet1 上有如下图的 Security Constraint(简单理解就是 ACL):
Servlet2 上并没有 ACL。
然而因为 Servlet2 的访问 url 位于 Servlet1 的下一级(/servlet1/servlet2 是 /servlet1/ 的 “子目录”),所以 Tomcat 中正常的代码逻辑应该是,虽然 Servlet2 上面没有 ACL,但是 Servlet2 应该继承 Servlet1 的 ACL。
正常情况应该是:
- 先访问 /servlet1,返回 403,因为请求被 ACL 拦截了
- 再访问 /servlet1/servlet2,返回 403,因为请求被 ACL 拦截
那么实际的问题在哪里?
Tomcat 在接收到 Servlet 访问请求后,在实例化 Servlet 对象之前,会先扫描被访问的 Servlet 上注册的 ACL。如果存在 ACL,则将 ACL 规则添加到一个 Context 唯一的列表中。随后再检查当前访问的 Servlet 是否被 ACL 保护。
ACL 扫描 org.apache.catalina.authenticator.AuthenticatorBase#invoke:
ACL 检查 org.apache.catalina.authenticator.AuthenticatorBase#invoke:
问题出在,如果在 Servlet1 (/servlet1)被访问之前,有人先访问了 Servlet2(/servlet1/servlet2)。流程如下:
- Tomcat 接收到 Servlet2 的访问请求
- Tomcat 扫描 Servlet2 上注册的 ACL,发现 Servlet2 上没有注册 ACL
- Tomcat 检查 Servlet2 是否被 ACL 保护。这一步,因为 Servlet2 在 Servlet1 之前被访问,所以导致 Servlet1 上注册的 ACL 规则还没有被 Tomcat 发现。所以,原本应该被 Servlet1 的 ACL 规则保护的 Servlet2,就处在了未受保护的状态。
实例
- 访问 http://localhost:8080/CVE-2018-1305/servlet1/servlet2,此时 servlet1 的 ACL 没被 Tomcat 加载,所以访问成功
- 访问 http://localhost:8080/CVE-2018-1305/servlet1,被拒绝访问了,此时 Tomcat 加载了 servlet1 的 ACL
- 再访问 http://localhost:8080/CVE-2018-1305/servlet1/servlet2,被拒绝访问了,因为 Tomcat 已经加载了 servlet1 的 ACL
Tomcat 的修复
Tomcat 的修复了 ACL 注册的方式。以前是采用,在 Servlet 真正被访问之前才去注册此 Servlet 上的 ACL。现在改成了,在 Context 启动的时候,就去扫描该 Context下所有的 Servlet 上注册的 ACL,并添加到 acl 列表中。
在任何一个 Context 启动时,Tomcat 都会自动调用:
org.apache.catalina.startup.WebAnnotationSet#loadApplicationServletAnnotations
而其中,添加了如下 ACL 相关代码:
影响范围
只有当你使用的 Tomcat 处于受影响的 Tomcat 版本,而且你的应用依赖于 Java EE Constraints 来进行 ACL 保护的时候,才受影响。