【CVE-2020-1938】Tomcat AJP 任意文件读取和包含漏洞分析记录
2,修改一个属性设置
https://github.com/apache/tomcat/commit/40d5d93bd284033cf4a1f77f5492444f83d803e2
强制设置认证secret,否则不启动AJP Connector
3,添加一个新AJP属性
https://github.com/apache/tomcat/commit/b99fba5bd796d876ea536e83299603443842feba
应该就是新属性这里。
因为原本的代码里面会把不识别的属性添加进去,从而导致操纵内部数据。(但怎么操纵呢?)
注意公告里面的这句话
相关参数可控,构造特定参数”
与及两种利用方式
1、利用DefaultServlet实现任意文件下载 (不带后缀)
2、通过jspservlet实现任意后缀文件包含 (带jsp后缀)
这下子就全明白了,接下来就是下载tomcat源码调试了。
0x02 环境搭建
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.apache.tomcat</groupId> <artifactId>Tomcat7.0.99</artifactId> <name>Tomcat7.0.99</name> <version>7.0</version> <properties> <java.version>1.7</java.version> </properties> <dependencies> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant-apache-log4j</artifactId> <version>1.6.5</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant-commons-logging</artifactId> <version>1.6.5</version> </dependency> <dependency> <groupId>wsdl4j</groupId> <artifactId>wsdl4j</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>javax.xml.rpc</groupId> <artifactId>javax.xml.rpc-api</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.eclipse.jdt.core.compiler</groupId> <artifactId>ecj</artifactId> <version>4.5.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>Tomcat7.0</finalName> <sourceDirectory>java</sourceDirectory> <resources> <resource> <directory>java</directory> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.0</version> <configuration> <encoding>UTF-8</encoding> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> </project>
成功跑起来(后补的图)
回到正题,代码太水,手撸时间花费太多了,直接找别人写好的,github大法好,直接搜索ajp
接着研究一下代码,改写一下就可以发送我们自己的属性了。
wireshark抓包,跟xray的poc一样了
0x03 源码调试
跟进,直接跟进到Decode extra attributes,也就是获取解析属性设置属性的地方
循环获取,switch判断,看到如果case是属性类型,在最后的一个else里面把没有判断到的属性直接设置到request里面
代码接着往下走,在预处理完了request headers之后,在adapter里面处理request
接着调用容器来处理
根据请求的url是否带JSP后缀,tomcat会将request交由不同的servlet来处理
不带jsp后缀的,直接用DefaultServlet来处理的情况(文件读取)
在HttpServlet中根据请求方法调用不同的方法处理
这里方法是GET,一路跟进去
跟进,最后看到处理路径的方法getRelativePath
也就是在这里对安恒说的那三个值进行判断
当javax.servlet.include.request_uri不为空的时候,取javax.servlet.include.path_info和javax.servlet.include.servlet_path的值进行拼接,然后返回path,之后进入lookupCache方法
这里面的流程先是在缓存里面找,找不到了,然后在本地找,最终来到 org.apache.naming.resources.FileDirContext 的file方法,然后new一个File类对象。
在File构造函数中会对path进行净化,限制了跨目录
调用栈
file:811, FileDirContext (org.apache.naming.resources) doLookup:208, FileDirContext (org.apache.naming.resources) doLookupWithoutNNFE:494, BaseDirContext (org.apache.naming.resources) lookup:475, BaseDirContext (org.apache.naming.resources) lookupCache:1463, ProxyDirContext (org.apache.naming.resources) serveResource:831, DefaultServlet (org.apache.catalina.servlets) doGet:435, DefaultServlet (org.apache.catalina.servlets) service:621, HttpServlet (javax.servlet.http) service:415, DefaultServlet (org.apache.catalina.servlets) service:728, HttpServlet (javax.servlet.http) internalDoFilter:303, ApplicationFilterChain (org.apache.catalina.core) doFilter:208, ApplicationFilterChain (org.apache.catalina.core) invoke:219, StandardWrapperValve (org.apache.catalina.core) invoke:110, StandardContextValve (org.apache.catalina.core) invoke:492, AuthenticatorBase (org.apache.catalina.authenticator) invoke:165, StandardHostValve (org.apache.catalina.core) invoke:104, ErrorReportValve (org.apache.catalina.valves) invoke:1025, AccessLogValve (org.apache.catalina.valves) invoke:116, StandardEngineValve (org.apache.catalina.core) service:452, CoyoteAdapter (org.apache.catalina.connector) process:190, AjpProcessor (org.apache.coyote.ajp) process:654, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote) run:317, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net) runWorker:1145, ThreadPoolExecutor (java.util.concurrent) run:615, ThreadPoolExecutor$Worker (java.util.concurrent) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:745, Thread (java.lang)
最后就是将读取到的资源输出回来
带JSP后缀jspservlet处理情况(文件包含)
在jspservlet的service方法断点,从request中属性中取出org.apache.catalina.jsp_file的值放到jspFile中,之后传入到serviceJspFile中处理。
继续跟进,会先判断jsp 文件是否存在,如果存在,随后才会初始化wrapper,最后调用JspServletWrapper的service方法来解析。
这里继续跟进getResource,当System.getSecurityManager()=true的时候,可从远程加载文件
继续走,先是对path进行规范化处理
继续跟进,到最后同样也是在org.apache.naming.resources.FileDirContext file方法中新创建一个File对象,判断文件是否存在。
而其中base变量的值为访问的容器的web根目录
在org.apache.tomcat.util.http.mapper internalMapExtensionWrapper方法进行一系列判断,设置wrapper,其中有判断,根据后缀设置warpper,当后缀为jsp或jspx的时候都会用jspServlet来处理
调用栈如下:
internalMapExtensionWrapper:1170, Mapper (org.apache.tomcat.util.http.mapper) internalMapWrapper:945, Mapper (org.apache.tomcat.util.http.mapper) internalMap:874, Mapper (org.apache.tomcat.util.http.mapper) map:742, Mapper (org.apache.tomcat.util.http.mapper) postParseRequest:782, CoyoteAdapter (org.apache.catalina.connector) service:446, CoyoteAdapter (org.apache.catalina.connector) process:190, AjpProcessor (org.apache.coyote.ajp) process:654, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote) run:317, JIoEndpoint$SocketProcessor (org.apache.tomcat.util.net) runWorker:1145, ThreadPoolExecutor (java.util.concurrent) run:615, ThreadPoolExecutor$Worker (java.util.concurrent) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:745, Thread (java.lang)
具体的处理逻辑就不说了