Java Web安全之Java容器安全-Tomcat

Java Web安全之Java容器安全-Tomcat

Apache Tomcat

Java Web安全之Java容器安全-Tomcat

Apache Tomcat软件是Java ServletJavaServer PagesJava Expression Language 和Java WebSocket 技术的开源实现。Tomcat由于其自身简单、稳定、开源等特征,在中小型系统和并发量小的场景下被普遍使用,有极大的用户使用量。

规范对应版本

Tomcat版本Java EE版本Servlet版本JSP版本发布时间
Tomcat 5.0.0 +J2EE 1.4Servlet 2.4JSP 2.0(2003年11月24日)
Tomcat 6.0.0 – Tomcat 6.0.44Java EE 5Servlet 2.5JSP 2.1(2006年5月11日 – 2007年9月11日)
Tomcat 7.0.0 – Tomcat 7.0.25Java EE 6Servlet 3.0JSP 2.2(2009年12月10日 – 2011年2月6日)
Tomcat 8.0.0 +Java EE 7Servlet 3.1JSP 2.3(2013年5月28日)
Tomcat 9.0.0 +Java EE 8Servlet 4.0JSP 2.3(2017年2月5日)
Tomcat 10.0.0 +Jakarta EE8Servlet 4.0JSP 2.3/

参考: Web Application Specifications

最新版本

截止2020年09月15日最新版本已经发布到:9.0.3810.0.0-M8 alpha

Github地址:https://github.com/apache/tomcat

Tomcat官网:https://tomcat.apache.org/

历史版本下载地址:https://archive.apache.org/dist/tomcat/

安全漏洞

Tomcat在历史上曾被曝出多种类型的漏洞,这些漏洞将会导致服务器存在较高的安全风险,其中包括但不限于:

  • 默认配置
  • 弱口令爆破
  • 历史安全漏洞
  • Tomcat AJP协议
  • 如何在利用Tomcat Getshell

Tomcat 口令爆破

Tomcat在默认情况下提供了一些管理后台,不同的管理后台提供了不同的功能,这些管理后台使用了Basic认证的方式进行权限校验,如果暴露在互联网上,将存在遭到暴力破解的安全风险。

其中主要包含两种:Manager以及Host Manager

Manager APP

在生产环境中,为了实现部署新的Web应用程序或取消部署现有的程序而不必重启容器、动态更新程序代码、列出一些JVM或操作系统的属性值、列出应用程序或会话等等基础功能,Tomcat默认包括了一个Web应用程序,通常位于$CATALINA_BASE/webapps/manager目录下:

Java Web安全之Java容器安全-Tomcat

并且这个应用程序默认的context path也为/manager,访问应用的方式为/manager/html

对于这个APP的详细介绍,可以在官方文档找到,以Tomcat 7.0为例,文档的位置为:https://tomcat.apache.org/tomcat-7.0-doc/manager-howto.html

我们查看manager应用下的web.xml,发现其中配置了如下servlet-mapping:

Java Web安全之Java容器安全-Tomcat

由此我们可以知道:

  • /manager/html/:提供HTML格式的管理页面
  • /manager/status/:提供服务器状态(Server Status)页面
  • /manager/jmxproxy/:提供JMX proxy 接口
  • /manager/text/:提供纯文本页面

由于Tomcat Manager提供的这些功能是需要管理人员才能使用和查看的,因此如果默认开启的话将会存在很高的安全问题,所以Tomcat并没有直接提供这些功能。

如果想要使用这些功能,则需要在$CATALINA_BASE/conf/tomcat-users.xml中配置相关的用户信息,包括用户名、密码、用户角色,来对使用这些功能的用户进行身份鉴别和权限验证。

Java Web安全之Java容器安全-Tomcat

在 manager 项目中的web.xml中我们可以看到能够使用的这些角色:

Java Web安全之Java容器安全-Tomcat

他们对应的权限分别为:

  • manager-gui:能够访问/manager/html/的管理界面。
  • manager-script:能够访问 /manager/text/ 以及/manager/status/界面。
  • manager-jmx:能够访问/manager/jmxproxy/ 以及/manager/status/界面。
  • manager-status:能够访问 /manager/status/的Server Status界面。

在正确的配置了用户身份信息和角色后,就能够访问相应的页面了。以本篇作者的环境为例,演示如下:

Server Status页面:

Java Web安全之Java容器安全-Tomcat

html管理后台:

Java Web安全之Java容器安全-Tomcat

纯文本页面-使用指令list:

Java Web安全之Java容器安全-Tomcat

JMX Proxy页面:

Java Web安全之Java容器安全-Tomcat

对于这几个Servlet功能的实现如果感兴趣,可以查看在org.apache.catalina.manager 目录下的代码,在此章节将不过多关注。

Java Web安全之Java容器安全-Tomcat

Host Manager

顾名思义,此管理后台用于管理运行在Tomcat上的虚拟主机,文件路径位置为$CATALINA_BASE/webapps/manager,context path为/host-manager

详细介绍可以在官方文档找到,以Tomcat 7.0为例,文档的位置为:https://tomcat.apache.org/tomcat-7.0-doc/host-manager-howto.html

servlet-mapping以及用户权限均有两个:

Java Web安全之Java容器安全-Tomcat

配置方式同 manager,不再重复,以下为页面示例:

html页面管理后台:

Java Web安全之Java容器安全-Tomcat

纯文本页面-使用指令list:

Java Web安全之Java容器安全-Tomcat

暴力破解

刚才提到的这些应用之所以采用Basic认证进行身份验证,原因是在应用配置、Tomcat配置等文件中进行了如下配置:

  • 认证的方式(BASIC、DIGEST、FORM、SSL等)
Java Web安全之Java容器安全-Tomcat

通过 security-constraint 配置需要鉴权的访问路径

Java Web安全之Java容器安全-Tomcat

用户的身份信息(账户、密码)、权限(角色)

Java Web安全之Java容器安全-Tomcat

默认的域(Realm)配置,Tomcat的server.xml中默认配置 UserDatabaseRealm,它从配置的全局资源 conf/tomcat-users.xml 中提取用户信息,在Tomcat 7.0及以上版本中,还提供了LockOutRealm 的组合域,用来阻止短时间内多次登录失败的情况。

Java Web安全之Java容器安全-Tomcat

在经过了如上配置后,再访问这些管理后台将需要进行Basic认证。

弱口令

虽然Tomcat没有提供默认用户,但是在配置文件中含有注释了的配置样例,其中包括的用户名密码:

tomcat:tomcat
both:tomcat
role1:tomcat

有的管理员可能会取消注释直接使用这些默认配置的账户密码,因此可以将其当做Tomcat的默认口令

除此之外,还有的管理员习惯于使用常用的用户名及密码,如:

admin:admin
admin:123456
root:root
manager:manager:
admin:admin888
...

以上仅仅是列出一小部分作为示例,实际上还有很多种类的弱口令。一旦弱口令被攻击者猜解到,攻击者能够轻易的获取一些用户的权限,大大降低了攻击成本。

暴破

Basic认证是一种较为简单的HTTP认证方式,客户端通过明文(Base64编码格式)传输用户名和密码到服务端进行认证。

客户端携带数据格式为:

Authorization: Basic dG9tY2F0OjEyMzEyMw==

服务端会根据用户是否登陆成功返回 200 或 401 等不同的状态码,攻击者可以自行组织字典进行暴力破解攻击。

暴力破解携带的账户密码可能是弱口令,也可能是撞库攻击,还可能是根据站点、管理员自身信息生成的字典等,具有更高的成功率。

如下,使用 Burpsuite的 Intruder 模块对 Tomcat 6.0 的 /manager/html 路径的基础认证进行爆破:

Java Web安全之Java容器安全-Tomcat

限制

刚才提到了,在Tomcat 7以上配置文件默认添加了LockOutRealm,首先我们看一下 LockOutRealm的逻辑,代码位于org.apache.catalina.realm.LockOutRealm。类里的字段很明了,无需解释。

Java Web安全之Java容器安全-Tomcat

在 authenticate 方法中进行身份验证,如果用户登陆失败,将调用 registerAuthFailure 方法标记用户的登录失败状态

Java Web安全之Java容器安全-Tomcat

这段代码我贴一下:

    private void registerAuthFailure(String username) {
        LockOutRealm.LockRecord lockRecord = null;
        synchronized(this) {
            if (!this.failedUsers.containsKey(username)) {
                lockRecord = new LockOutRealm.LockRecord();
                this.failedUsers.put(username, lockRecord);
            } else {
                lockRecord = (LockOutRealm.LockRecord)this.failedUsers.get(username);
                if (lockRecord.getFailures() >= this.failureCount && (System.currentTimeMillis() - lockRecord.getLastFailureTime()) / 1000L > (long)this.lockOutTime) {
                    lockRecord.setFailures(0);
                }
            }
        }

        lockRecord.registerFailure();
    }

重点的判断逻辑是:

如果用户的登录失败次数>=5次,并且,(当前时间-上次登录失败的时间)>300s,将会将用户登录失败的次数重新设置为0。

函数最后一行是内部类的方法,将 failures += 1,并将 lastFailureTime置为当前时间:

Java Web安全之Java容器安全-Tomcat

由此可知,在5分钟之内同一账户登陆失败5次以上,LockOutRealm 将会封锁用户,在未来5分钟之内没有新的登陆失败的情况,会从0开始重新计数,因此这种方式是能够一定程度缓解系统受到的暴力破解攻击的。

Tomcat AJP 协议

AJP(Apache JServer Protocol) 协议最初是由 Gal Shachor 设计。对于Web服务器与Servlet容器通信来讲,最主要目的是:

  • 提高性能(主要是速度)。
  • 添加对SSL的支持。

目前Tomcat中使用的版本均为AJP1.3,简称为ajp13。ajp13协议是面向数据包的。出于对性能的考虑,选择了以二进制格式传输,而不是更易读的纯文本。

Web服务器通过TCP连接与servlet容器进行通信。为了减少昂贵的套接字(socket)创建过程,web服务器将尝试保持与servlet容器的持久的TCP连接,并为多个请求/响应周期重复使用一个连接。

官方文档位置:https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html

在Tomcat的server.xml 中默认配置了两种连接器:

Java Web安全之Java容器安全-Tomcat

一种是使用的HTTP Connector,监听8080端口,还有一个AJP Connector,监听了8009端口。在Tomcat中这个协议的监听的一直都是默认开启的。

AJP Connector 的配置文档:http://tomcat.apache.org/tomcat-6.0-doc/config/ajp.html

AJP Connector 可以通过 AJP 协议和另一个 web 容器进行交互。例如正常情况下的访问是由客户端通过HTTP协议访问到Tomcat服务器返回结果,此时将由HTTP Connector处理请求,但也可以使用通过AJP协议来进行访问,此时将由AJP Connector来处理。

但是通常情况下用户的客户端并不会支持AJP协议,因此想要使用AJP协议进行访问,需要自己实现连接器,或由中间的代理服务器进行转发。

  • Apache HTTP Server 2.x 上的启用 AJP 的 mod_proxy 模块(在 2.2 上已成为默认配置模块)
  • 其他任何支持 JK 1.2.x 的服务器

AJP 协议配置

测试环境:

  • 操作系统:CentOS 7
  • Web服务器:Apache/2.4.6
  • JSP服务器:Tomcat 9.0.27
  • JDK:1.8.0_251

mod_jk

Mod_JK是Apache的一个模块,其通过AJP协议实现Apache与Tomcat之间的通讯

官网地址:http://tomcat.apache.org/download-connectors.cgi

使用手册:http://tomcat.apache.org/connectors-doc/webserver_howto/apache.html

由于配置较 mod_proxy_ajp 复杂,此处不进行演示,有兴趣的朋友可以按照官方文档自行尝试。

mod_proxy_ajp

首先在 /conf/httpd.conf 中添加模块:

LoadModule proxy_ajp_module modules/mod_proxy_ajp.so

在虚拟主机中设置代理转发

<VirtualHost *:81>
    ProxyPass / ajp://localhost:8009/
    ProxyPassReverse / ajp://localhost:8009/
</VirtualHost>

正常启动Tomcat,把我们命令执行的 test.jsp 放在ROOT中,使用8080端口正常以HTTP协议直接访问项目:

Java Web安全之Java容器安全-Tomcat

使用 apache 监听的 81 端口进行 AJP 协议转发也可以正常访问:

Java Web安全之Java容器安全-Tomcat

AJP协议数据包处理

在看AJP协议数据包处理之前,先来了解一下Tomcat处理一个请求的过程。大致如下流程:

Java Web安全之Java容器安全-Tomcat

一次请求的处理可以划分为Connector及Container进行处理,经历的过程大致如下:

  • 一个TCP/IP数据包发送到目标服务器,被监听此端口的Tomcat获取到。
  • 处理这个Socket网络连接,使用Processor解析及包装成request和response对象,并传递给下一步处理。
  • Engine来处理接下来的动作,匹配虚拟主机Host、上下文Context、Mapping Table中的servlet。
  • Servlet调用相应的方法(service/doGet/doPost…)进行处理,并将结果逐级返回。

而对于使用HTTP协议或AJP协议进行访问的请求来讲,在解析包装成为request和response对象之后的流程都是一样的,主要的区别就是对socket流量的处理以及使用Processor进行解析的过程的不同。

提供这部分功能的接口为 org.apache.coyote.Processor<S> ,主要负责请求的预处理。并通过它将请求转发给Adapter,针对不用的协议则具有不同的实现类。

这个接口里定义了一些重要的方法:

Java Web安全之Java容器安全-Tomcat

这里主要还是针对于HTTP协议和AJP协议,抽象类AbstractProcessorLight及其子类AbstractProcessor还是对共有特性的封装。

Java Web安全之Java容器安全-Tomcat

AbstractProcessor具有三个子类,AjpProcessor 用来处理AJP协议,Http11Processor 用来处理HTTP/1.1,StreamProcessor用来处理HTTP/2,我们先来看看针对平常使用的HTTP协议的处理。

Http11Processor 重点的process()方法,使用service()方法来处理标准HTTP请求,这里我们重点看一下:

解析请求行和请求头部分:

Java Web安全之Java容器安全-Tomcat

在Tomcat 8.5 之后,加入了判断是否需要HTTP协议升级:

Java Web安全之Java容器安全-Tomcat

调用prepareRequest(),将相关信息放入Http11InputBuffer对象中

Java Web安全之Java容器安全-Tomcat

然后调用Adapter将请求交给Container处理:

Java Web安全之Java容器安全-Tomcat

然后接下来是一些收尾工作。在了解了这个过程后,我们再来看一下 AjpProcessor 中service()方法,大体上是一致的流程,只是具体的细节不同,首先是一些解析数据包读取字节的操作,这里不是重点,暂且不提,然后也是调用 prepareRequest() 方法进行预处理:

Java Web安全之Java容器安全-Tomcat

处理之后同样的调用Adapter将请求交给Container处理

Java Web安全之Java容器安全-Tomcat

而AJP协议的任意文件读取/任意文件包含漏洞,则出现在上面提到的 prepareRequest() 方法中。

AJP漏洞

在 AjpProcessor 的 prepareRequest() 中,恶意攻击者可通过控制请求内容,为request对象任意的设置属性。

switch/case 判断中,当attributeCode=10 时,将调用 request.setAttribute 方法存入。

Java Web安全之Java容器安全-Tomcat

所以在此攻击者拥有了可控的点,这个点该如何利用呢?

DefaultServlet

$CATALINA_BASE/conf/web.xml 中默认配置了如下内容:

Java Web安全之Java容器安全-Tomcat

可以看到这是一个默认的Servlet,这个 DefaultServlet 服务于全部应用,当客户端请求不能匹配其他所有Servlet时,将由此Servlet处理,主要用来处理静态资源。使用 serveResource() 方法提供资源文件内容:

Java Web安全之Java容器安全-Tomcat

会调用 getRelativePath() 方法获取请求资源路径:

Java Web安全之Java容器安全-Tomcat

这个方法存在一个判断,如图中红框位置标出:如果 request.getAttribute() 中javax.servlet.include.request_uri 不为空,则会取 javax.servlet.include.path_info 和javax.servlet.include.servlet_path 的值,并进行路径拼接,返回路径结果。

这个结果 path 会被带入到 getResource() 方法中返回结果,只要文件存在,即可读取其中内容。

Java Web安全之Java容器安全-Tomcat

由此可见,配合AJP协议中的缺陷,可以控制attribute中的内容,造成任意文件读取漏洞。

但是需要注意的是,在读取资源文件的过程中,会调用org.apache.tomcat.util.http.RequestUtil.normalize() 方法来对路径的合法性进行校验,如果存在 ./ 或 ../ 则会返回 null ,在后续流程中会抛出一个非法路径的异常终止文件读取操作。

Java Web安全之Java容器安全-Tomcat

因此我们无法使用 ../ 跳出目录,只能读取Web应用目录下的文件。

JspServlet

同样的在$CATALINA_BASE/conf/web.xml 中,对访问以 .jsp/*.jspx 后缀结尾的请求,调用 JspServlet 处理请求。

Java Web安全之Java容器安全-Tomcat

看一下重点的 service(),代码如下图,在attribute中含有如下 javax.servlet.include.servlet_pathjavax.servlet.include.path_info 时,将会取出并拼接为文件路径 jspUri

Java Web安全之Java容器安全-Tomcat

拼接成 jspUri 后,调用 serviceJspFile() ,将此文件解析为jsp文件并执行。

Java Web安全之Java容器安全-Tomcat

因此这就构成了一个文件包含漏洞。在文件内容可控的情况下,就可以延伸为任意代码执行漏洞,所以网上有的分析文章也出现了任意代码执行、任意命令执行漏洞的字眼。

AJP客户端

在了解了AJP协议的漏洞成因之后,我们只需要构造一个客户端就可以实现自己的攻击行为了。

AJP协议的请求和响应包结构在文档可以看到:https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html

实现过程这里不进行描述,代码放在如下位置,请自行观看:

javaweb-sec/javaweb-sec-source/javasec-test/javasec-tomcat-ajp

在这里进行漏洞两种利用方式的演示。

Web目录任意文件读取

任意文件读取需要满足的条件是:

  • 访问的地址(target)是一个没有 Servlet 映射的地址
  • request_uri 属性不为空
  • servlet_path 和 path_info 拼接得到我们想要读取的文件

如下图配置:

Java Web安全之Java容器安全-Tomcat

可以看到成功返回了文件内容:

Java Web安全之Java容器安全-Tomcat

JSP文件包含

假设我们在web目录下具有可控的文件,比如我们上传了一个aaa.jpg,文件里是一个执行whoami命令并返回结果的jsp恶意文件。

Java Web安全之Java容器安全-Tomcat

这是我们需要控制的是访问的地址(target)是一个.jsp结尾的文件,并且 servlet_path、path_info 拼接起来是我们可控的文件路径。

Java Web安全之Java容器安全-Tomcat

运行返回结果,可以看到我们的 jpg 文件以 jsp 解析并执行成功:

Java Web安全之Java容器安全-Tomcat

问题拓展

在实际对Tomcat AJP 漏洞的研究和利用过程中,逐渐产生了以下几个问题,请自行思考:

  • SpringBoot项目是否受到此漏洞影响?如果能受到影响,是在什么情况下?
  • Struts2、Shiro、SpringMVC 等等具有一些全局过滤器的情况下,是否能够触发漏洞?
  • jsp 作为视图模板时是否存在漏洞?

参考答案链接:https://www.colabug.com/2020/0318/7137788/

from

转载请注明出处及链接

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用*标注