如何更加精确的检测Tomcat AJP文件包含漏洞(CVE-2020-1938)
通过上篇文章《CVE-2020-1938:Tomcat AJP协议文件包含漏洞分析》,我们知道这个漏洞出现在Tomcat默认的两个Servlet
,一个是DefaultServelt
,可以任意文件读取。第二个是JspServlet
,可以用于文件读取和代码执行。所以我们漏洞利用的关键是让精心构造的数据包最终让这两个Servlet
处理。但是在真实环境下的Web项目情况很复杂,会添加自定义的Servlet
和Filter
,使用各种框架和组件。它们的Servlet
和Filter
匹配规则会影响我们构造的数据包处理流向,导致我们无法检查成功。本文我们会针对常见的5种情况进行分析并一一解决!
0x01 知识储备
在分析前我们需要对Tomcat匹配规则优先级有一个了解,匹配的优先级如下,优先级从上到下:
- 精确匹配(例如:
/admin/index.html
) - 路径匹配 (例如:/*)
- 拓展名匹配 (例如:
*.jsp
,*.jspx
) - 缺省匹配 (比如:
/
)
具体的匹配细节可以查看Tomcat源码org.apache.catalina.mapper.Mapper#internalMapWrapper()
0x02 情况一:原生Servlet环境下
Tomcat下存在多个默认的web项目,由于它们没有使用任何框架,所以借助它们来检查再好不过了。
- docs
- examples
- host-manager
- manager
当没有默认的web项目,我们只能检查ROOT
下的项目了。在使用原生Servlet开发的web应用中,我们要考虑的是开发人员自定义filter
和自定义servlet
对漏洞影响。
按照开发经验,一般过滤器是不会过滤.js
,.css
,.ico
等静态文件后缀的url,同时自定义的Servlet也不会去处理这些url。所以我们可以构造类似如下请求来绕过它们带来的影响。
1 | RequestUri:/facvon.ico |
0x03 情况二:Sping mvc环境下
Spring MVC的经典配置如下:
1 | <servlet> |
虽然覆盖掉了DefaultServlet
的匹配路径,但是*.jsp,*.jspx
依然会交给JspServlet
处理,所以我们可以构造如下请求让JspServlet来触发漏洞。
1 | RequestUri:/index.jsp |
这里顺便回答下上一篇文章提的问题
问题:如果已经知道某个contoller使用的是jsp为视图模版来渲染数据,我们能否通过它来触发漏洞?
答:其实是不可以的。因为spring mvc会将模版渲染后,交给JspServlet去处理之前,会调用org.apache.catalina.core.ApplicationDispatcher#doInclude
方法对3个include属性进行重新赋值,也就是把我们之前设置的值覆盖掉了不再可控!
0x04 情况三:Spring boot环境下
Srping boot结合Tomcat来部署有两种方式,分别是外置
和内嵌
。
5.1 内嵌Tomcat
我们先来说内嵌,它是默认的部署方式。顾名思义就是spring boot内部代码来调用Tomcat提供Web服务。这种方式默认AJP是不开启的。
若开启AJP,DefaultServlet
的匹配路径也会将org.springframework.web.servlet.DispatcherServlet
覆盖,而JspServlet
这个是没有被注册的,因为该类在jasper.jar
中,Spring boot默认的依赖中没有。
这里值得一提的是有一种情况是可以触发漏洞的,当Spring boot需要以JSP为视图模版时,jasper.jar需要被引入。通过调试Spring boot发现会自动注册一个将*.jsp
和*.jspx
给Jspservlet
的处理的mapper
,具体参考以下两处源码。
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#prepareContext
org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory#shouldRegisterJspServlet
5.2 外置Tomcat
外置就是把SpringBoot
项目打成war,部署到tomcat的webapps目录下。这种情况下的检测和Spirng MVC情况一样。
所以综合来看,内置情况下只有配置开启了AJP
并引入了jasper.jar
才可以被利用,这种情况较少。外置情况下可以直接利用,这种情况也较少。所以我认为Spring boot出现该漏洞的可能性不大。
0x05 情况四:shiro环境下
经典配置下shiro过滤器会对所有路径进行过滤,对url的访问权限有如下5个属性。
- anon: 无需认证即可访问
- authc: 需要认证才可访问
- user: 点击“记住我”功能可访问
- perms: 拥有权限才可以访问
- role: 拥有某个角色权限才能访问
假设配置如下,在未登录情况下只能访问被配置为anon
权限的login.jsp
,访问其他链接都会302跳转至登录页面。所以只能请求这个页面来触发漏洞。
1 | <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> |
但我们在自动化中如何发现被配置为anon
权限的URL呢?实验室的@背影
师傅给了一条很重要的提示,可以通过该漏洞设置request对象属性shiroFilter: 1
来“关闭”shiro的拦截功能。
如果request
对象的属性名alreadyFilteredAttributeName
的值不为空,那么将直接交给Tomcat
的servlet
处理,相当于关闭了shiro
的拦截!
alreadyFilteredAttributeName变量等于shiro过滤器名
+ .FILTERED
。
通过查看代码发现shiroFilter
其实是web.xml
设置的shiro
过滤器名,这是由开发人员自定义的,故带来了新的问题。若不知道shiro
过滤器名怎么办呢?
通过调试shiro
,发现请求会被上面说的5种权限过滤器,依次匹配并处理。最重要的是它们的名字固定!于是按照同样的方法,都给它们设置上已过滤flag,即可绕过shiro的限制。具体请求构造如下:
1 | RequestUri:/test.jsp |
0x06 情况五:Struts2环境下
以下分析的是Struts2 2.5.22
使用Struts2框架一般需要设置如下的全局过滤器
1 | <filter> |
该过滤器默认会将后缀为空
和.action
的URL请求,交给Struts2
的Action
处理,而其他后缀就交给Tomcat默认Servlet处理,漏洞利用需要让其走后者。
然而在请求路径的获取上Struts2有别于其他环境,这是导致漏洞利用方式稍有不同。它通过request
对象的javax.servlet.include.servlet_path
属性获取,而不是request.getServletPath()
。
org.apache.struts2.dispatcher.mapper.DefaultActionMapper#getUri()
所以我们在这里必须设置该属性值为非空非.action
的后缀test.jsp
,才能让Tomcat的JspServlet
来处理。但是如果我们还是使用原来的方式读/WEB-INF/web.xml
是行不通的,因为最终构造的路径如下是错误的。
1 | = javax.servlet.include.servlet_path + javax.servlet.include.path_info |
那我们能否将javax.servlet.include.path_info
设置为/../WEB-INF/web.xml
来吃掉1.jsp
形成正确路径呢?答案是可以的!可能看过我之前漏洞分析文章的朋友会说,不是说路径里不能使用../
进行跳目录么?其实是可以跳目录,只是不能跳出webapps
而已。这里重新说明下路径校验函数normalized()
的功能。
该方法的功能是中和掉路径中的./
和../
,比如/a/.//b/../c
就会被中和为/a/c
。如果最后依然存在../
在开头,才会返回null
,最终抛出非法路径的异常。
所以在Struts2
框架下检测该漏洞,需要构造如下请求来绕过。
1 | RequestUri: / |
0x07 扫描演示
最后便可以将以上各个场景的特点综合起来,编写扫描工具了。这里我搭建了SpringMVC + Shiro的环境进行演示。可以发现其他的url都重定向了,只有针对shiro构造的请求是200,并成功触发漏洞!
0x08 最后的话
- 本文只对每种环境较新版本进行分析,所以提供的扫描方案不可能适配所有版本环境,算是对精确检测做一个抛砖引玉。
- 每种环境下的检测方案,只考虑使用Tomcat默认存在缺陷的两个Servlet(
JspServlet
和DefaultServlet
)来检测,更完美的方案应该是去找每种环境下其他存在缺陷的Servlet。