0x01漏洞背景CVE-2020-1938是Tomcat-Ajp协议漏洞分析,Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器&
0x01 漏洞背景 CVE-2020-1938 是 Tomcat-Ajp 协议漏洞分析,Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持。由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器。
0x02 影响版本 Apache Tomcat 9.x <9.0.31 Apache Tomcat 8.x <8.5.51 Apache Tomcat 7.x <7.0.100 Apache Tomcat 6.x 0x03 环境搭建 0x1 JDK 安装 cp -r jdk-8u161-linux-x64.tar.gz /usr/local/java cd /usr/local/java tar -zxvf jdk-8u161-linux-x64.tar.gz
在/etc/profile文件中写入
export JAVA_HOME&#61;/usr/local/java/jdk1.8.0_161 export JRE_HOME&#61;/$JAVA_HOME/jre export CLASSPATH&#61;.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH&#61;$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
执行生效 source /etc/profile
0x2 Tomcat 安装 tar -zxvf apache-tomcat-8.5.30.tar.gz
在/etc/profile文件中写入
export CATALINA_HOME&#61;/usr/local/tomcat/apache-tomcat-8.5.30 export CLASSPATH&#61;.:$JAVA_HOME/lib:$CATALINA_HOME/lib export PATH&#61;$PATH:$CATALINA_HOME/bin
执行生效 source /etc/profile
启动 catalina.sh start
0x3 开启Tomcat调试 在tomcat bin 文件夹下的catalina.sh中添加如下代码
export JAVA_OPTS&#61;&#39;-agentlib:jdwp&#61;transport&#61;dt_socket,server&#61;y,suspend&#61;n,address&#61;5005&#39; # OS specific support. $var _must_ be set to either true or false.
Tomcat 调试原理 分为两种情况&#xff1a;
调试器开端口&#xff0c;远程JVM连接本地端口 远程JVM开放端口&#xff0c;调试器连接 总的来说&#xff0c;两个VM之间通过debug协议进行通信&#xff0c;然后以达到远程调试的目的。两者之间可以通过socket进行通信。其中&#xff0c;调试的程序常常被称为debugger, 而被调试的程序称为 debuggee。两种调试情况如下图所示&#xff1a;
关于调试协议JDWP这里先不讲&#xff0c;等有机会在进行讲解。
0x4 添加tomcat源码 在调试过程中&#xff0c;intellij反编译代码会有出入&#xff0c;直接下载tomcat相对应版本的源代码 https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.30/src/apache-tomcat-8.5.30-src.zip
将文件夹中的java 添加到intellij中&#xff0c;就可以对着源代码进行调试了
0x5 开启Intellij调试 设置Use module classpath 为项目根目录&#xff0c;设置调试端口等信息&#xff0c;如下图所示&#xff1a;
0x03 知识补充 0x1 Tomcat Connector Apache Tomcat服务器通过Connector连接器组件与客户程序建立连接&#xff0c;Connector表示接收请求并返回响应的端点。即Connector组件负责接收客户的请求&#xff0c;以及把Tomcat服务器的响应结果发送给客户。在Apache Tomcat服务器中我们平时用的最多的8080端口&#xff0c;就是所谓的Http Connector&#xff0c;使用Http&#xff08;HTTP/1.1&#xff09;协议.
在conf/server.xml文件里&#xff0c;他对应的配置为
而 AJP Connector&#xff0c;它使用的是 AJP 协议&#xff08;Apache Jserv Protocol&#xff09;是定向包协议。因为性能原因&#xff0c;使用二进制格式来传输可读性文本&#xff0c;它能降低 HTTP 请求的处理成本&#xff0c;因此主要在需要集群、反向代理的场景被使用。
Ajp协议对应的配置为
下图是tomcat 服务器开放连接端口情况。
0x2 Tomcat Servlet 在tomcat conf/web.xml中配置着tomcat的路由处理&#xff0c;主要有两个servlet分支
这个规则匹配/后没有后缀的
< servlet> < servlet-name> default servlet-name> < servlet-class> org.apache.catalina.servlets.DefaultServlet servlet-class> < init-param> < param-name> debug param-name> < param-value> 0 param-value> init-param> < init-param> < param-name> listings param-name> < param-value> false param-value> init-param> < load-on-startup> 1 load-on-startup> servlet> < servlet-mapping> < servlet-name> default servlet-name> < url-pattern> / url-pattern> servlet-mapping>
这个规则匹配路径中有.jsp的路由
< servlet> < servlet-name> jsp servlet-name> < servlet-class> org.apache.jasper.servlet.JspServlet servlet-class> < init-param> < param-name> fork param-name> < param-value> false param-value> init-param> < init-param> < param-name> xpoweredBy param-name> < param-value> false param-value> init-param> < load-on-startup> 3 load-on-startup> servlet> < servlet-mapping> < servlet-name> jsp servlet-name> < url-pattern> *.jsp url-pattern> < url-pattern> *.jspx url-pattern> servlet-mapping>
0x3 Tomcat 请求处理 该图介绍了Tomcat内部处理HTTP请求的流程
用户发送请求至8080端口&#xff0c;被Connector获取后&#xff0c;Connector中的Processor用于封装Request&#xff0c;Adapter用于将封装好的Request交给Container。 Connector把该请求交给Container中的Engine来处理&#xff0c;并等待Engine的回应。 Engine 获得请求localhost/test/index.jsp&#xff0c;匹配所有的虚拟主机Host。 Engine搜索对应的主机&#xff0c;/test匹配到Context&#xff0c; path&#61;"/test"的Context获得请求/index.jsp&#xff0c;在它的mapping table中寻找出对应的Servlet。Context匹配到URL PATTERN为*.jsp的Servlet&#xff0c;对应于JspServlet类&#xff08;匹配不到指定Servlet的请求对应DefaultServlet类&#xff09; Wrapper是最底层的容器&#xff0c;负责管理一个Servlet。构造HttpServletRequest对象和HttpServletResponse对象&#xff0c;作为参数调用JspServlet的doGet()或doPost()&#xff0c;执行业务逻辑、数据存储等程序。 Context把执行完之后的HttpServletResponse对象返回给Host Host把HttpServletResponse对象返回给Engine Engine把HttpServletResponse对象返回Connector Connector把HttpServletResponse对象返回给客户Browser 0x04 漏洞分析 该漏洞通过AJP协议端口触发&#xff0c;正是由于上文所述&#xff0c;Ajp协议的请求在Tomcat内的处理流程与我们上文介绍的Tomcat处理HTTP请求流程类似。我们构造两个不同的请求&#xff0c;经过tomcat内部处理流程&#xff0c;一个走default servlet(DefaultServlet)&#xff0c;另一个走jsp servlet(JspServlet)&#xff0c;可导致的不同的漏洞。
0x1 文件读取漏洞 # 请求url req_uri &#61; &#39;/asdf&#39; # AJP协议请求中的三个属性 javax. servlet. include. request_uri &#61; &#39;/&#39; javax. servlet. include. path_info &#61; &#39;WEB-INF/web.xml&#39; javax. servlet. include. servlet_path &#61; &#39;/&#39;
在搭好的环境上&#xff0c;设置断点
Step 1 AjpProcessor->service()->prepareRequest()
进入prepareRequest函数&#xff0c;该函数处理Requests请求&#xff0c;重点在这一块
在该case分支request.setAttribute(n,v )
函数是解析函数&#xff0c;通过三次循环可见下图效果
通过三个循环将attributes变量赋值成如下所示&#xff1a;
随后将请求传给CoyoteAdapter&#xff0c;对request进行封装&#xff0c;将请求抓发给Container&#xff1a;
通过多级反射和调用&#xff0c;到达Servlet路由分发
Step 2 DefaultServlet-> service() -> doGet() 从CoyoteAdapter到达doGet的调用链
Servlet 的service函数根据请求的方法&#xff0c;调用不同的处理函数如下图匹配Get 方法&#xff1a; 1588562393145.png
Step 3 getRelativePath() 从get函数进入后执行serveResource函数
其中有个关键的函数getRelativePath
protected String getRelativePath ( HttpServletRequest request, boolean allowEmptyPath) { String servletPath; String pathInfo; if ( request. getAttribute ( "javax.servlet.include.request_uri" ) !&#61; null) { pathInfo &#61; ( String) request. getAttribute ( "javax.servlet.include.path_info" ) ; servletPath &#61; ( String) request. getAttribute ( "javax.servlet.include.servlet_path" ) ; } else { pathInfo &#61; request. getPathInfo ( ) ; servletPath &#61; request. getServletPath ( ) ; } StringBuilder result &#61; new StringBuilder ( ) ; if ( servletPath. length ( ) > 0 ) { result. append ( servletPath) ; } if ( pathInfo !&#61; null) { result. append ( pathInfo) ; } if ( result. length ( ) &#61;&#61; 0 && ! allowEmptyPath) { result. append ( &#39;/&#39; ) ; } return result. toString ( ) ; }
从代码中可以看出request.getAttribute的变量正好是我们POC中的变量&#xff0c;POC中的参数代入getRelativePath()方法&#xff0c;RequestDispatcher.INCLUDE_REQUEST_URI的值为’/’&#xff0c;不为空。pathInfo和servletPath参数的值拼接成result&#xff0c;getRelativePath()方法将result返回&#xff0c;返回内容为&#xff1a;’/WEB-INF/web.xml’。
Step 4 getResource() -> validate() -> normalize() serveResource()方法继续往下&#xff0c;可以看到这行代码&#xff1a;
该函数功能从字面意思上就是获取内容
进入了valiate方法&#xff0c;该方法为路径检测方法&#xff0c;其中主要调用了normalize方法&#xff0c;重点关注该方法&#xff1a;
if ( normalized. endsWith ( "/." ) || normalized. endsWith ( "/.." ) ) { normalized &#61; normalized &#43; "/" ; addedTrailingSlash &#61; true ; } while ( true ) { int index &#61; normalized. indexOf ( "//" ) ; if ( index < 0 ) { break ; } normalized &#61; normalized. substring ( 0 , index) &#43; normalized. substring ( index &#43; 1 ) ; } while ( true ) { int index &#61; normalized. indexOf ( "/./" ) ; if ( index < 0 ) { break ; } normalized &#61; normalized. substring ( 0 , index) &#43; normalized. substring ( index &#43; 2 ) ; } while ( true ) { int index &#61; normalized. indexOf ( "/../" ) ; if ( index < 0 ) { break ; }
过滤掉了请求中的路径穿越符号 /…/&#xff0c;也就导致了该漏洞只能读取webapps目录下的文件。
Step5 ServletOutputStream.write()
经过Tomcat内部流程处理&#xff0c;经过Tomcat的Container和Connector&#xff0c;最终返回给客户端。
特别注意 关键参数req_uri决定了我们可以读取webapps下其他目录的文件。当为’asdf’时无法匹配到webapps下的路径&#xff0c;所以路由到tomcat默认的ROOT目录&#xff1b;二来是为了让tomcat将请求流到DefaultServlet&#xff0c;从而触发漏洞。
真实文件 请求路径 webapps/manager manager/asdf webapps/ROOT /asdf
0x2 文件包含漏洞 该漏洞的触发与上个相似&#xff0c;只不过servlet的分支不同&#xff0c;该漏洞走的是JspServlet&#xff0c;所有的jsp文件的路由 将漏洞利用代码修改为
Step 1 JspServlet -> service() -> serviceJspFile() 从CoyoteAdapter进行分发&#xff0c;这里分发到jspservlet路由&#xff0c;因此我们要在jspservlet上下断点
0x05 后续 自己编写ajp协议 tomcat web服务器交互原理 java 反射调用链分析 0x06 参考链接 https://xz.aliyun.com/t/7683 https://www.anquanke.com/post/id/199448#h2-7 https://www.freebuf.com/column/227973.html https://www.jianshu.com/p/f902ac5d29e4