【CVE-2020-1938】Tomcat AJP 任意文件讀取和包含漏洞分析記錄


0x00 前言
2020年2月20日傍晚,在某群看到有群友問CNVD的tomcat文件包含漏洞有什么消息沒

 

 

接着看到安恆信息應急響應中心公眾號發了個漏洞公告
隨后chy師傅也在群里發了個阿里雲的公告鏈接
根據安恆和阿里雲公告給出的信息我們知道是tomcat ajp服務出了問題,但具體是哪里出了問題卻不知道。(這個時候我就在想阿里雲是怎么知道ajp服務出了問題的呢?

 

 

 
以tomcat7為例給出的修復版本是100,遂去github看commit,看100版本release之前的commit記錄。
 
0x01 初見端倪
找到15天前相關ajp的commit

 

從最下面往上看:
1,先是默認不開啟AJP connector,然后又是修改默認綁定地址,綁定在本地

 

2,修改一個屬性設置

https://github.com/apache/tomcat/commit/40d5d93bd284033cf4a1f77f5492444f83d803e2

 

 強制設置認證secret,否則不啟動AJP Connector

 

3,添加一個新AJP屬性

https://github.com/apache/tomcat/commit/b99fba5bd796d876ea536e83299603443842feba

 

 

 應該就是新屬性這里。

因為原本的代碼里面會把不識別的屬性添加進去,從而導致操縱內部數據。(但怎么操縱呢?)

https://github.com/apache/tomcat/commit/b99fba5bd796d876ea536e83299603443842feba#diff-e5bf250e10dab446db3ee424bc5c9ba8L871

 

 

接下來重點是分析ajp協議交互,如何發送屬性
后面翻資料,看到cnvd的公告( https://www.cnvd.org.cn/webinfo/show/5415

 

注意公告里面的這句話

相關參數可控,構造特定參數”

 

加上前面對commit的分析,也從側面證實了自己的想法。
課間休息,吃個飯回來
安恆研究院公眾號直接發了分析文章(https://mp.weixin.qq.com/s/GzqLkwlIQi_i3AVIXn59FQ)
直接就拋出了要控制的三個屬性

 

 

與及兩種利用方式

1、利用DefaultServlet實現任意文件下載 (不帶后綴)

2、通過jspservlet實現任意后綴文件包含 (帶jsp后綴)

 這下子就全明白了,接下來就是下載tomcat源碼調試了。

 

0x02 環境搭建

環境搭建這塊其實是在吃飯之前就弄了,因為一直被吹去吃飯,導致一直配置不成功源碼導入idea。
根據這個文章: https://blog.csdn.net/u013268035/article/details/81349341,我用的是tomcat 7.0.99搭建環境
有幾個注意點:
1,我們需要下載兩個tomcat,一個是tomcat源碼壓縮包(https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.99/src/apache-tomcat-7.0.99-src.zip),另一個是tomcat可執行的壓縮包。(https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.99/bin/apache-tomcat-7.0.99-windows-x64.zip)
2,根據文章,注意是將tomcat可執行文件壓縮包里面的webapps和conf拷貝到源碼的home目錄
3,新建一個pom.xml,復制一下即可
<?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>

成功跑起來(后補的圖)

 

接下來的問題就在於如何用ajp協議與tomcat交互,然后動態調試了
交互這塊,兩個方法:
1,直接根據官方文檔手擼
2,找人家寫好的

 

在之前長亭科技(漏洞發現者)發了漏洞公告,還起了個名字叫“幽靈貓”,搭配發了poc檢測到xray,拉下xray用wireshark抓包分析了一下
發現只是檢測一下版本而已,但是有一個標頭讓我覺得奇怪,“AJP_REMOTE_PORT”,這個東西應該就是屬性了。

 

 

回到正題,代碼太水,手擼時間花費太多了,直接找別人寫好的,github大法好,直接搜索ajp

 

 

找到兩個庫,看一下READEME,選擇了python版本
拉下來研究一下代碼(代碼很熟悉就是chy師傅推特gif圖上面的),又看到了我們熟悉的"AJP_REMOTE_PORT"

 

接着研究一下代碼,改寫一下就可以發送我們自己的屬性了。

 

 wireshark抓包,跟xray的poc一樣了

 

 

 0x03 源碼調試

根據前面,我們可以知道關鍵點在org.apache.coyote.ajp.AbstractAjpProcessor prepareRequest方法
在org.apache.coyote.ajp.AbstractAjpProcessor process方法中 在調用prepareRequest方法的地方下一個斷點
 

 

 跟進,直接跟進到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根目錄

 

題外: tomcat內部是如何判斷使用哪個servlet的呢?

在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)

具體的處理邏輯就不說了

 

0x04 總結
0,思路一定要冷靜清晰,分析清楚最重要的點,明白自己應該做什么
這里總結一下自己的思路:看公告,尋蛛絲馬跡,然后到github找commit記錄,大概理解漏洞的原理,然后根據需要的東西一步步進行
(需要AJP交互,如何解決?需要源碼調試,環境搭建?)
1,漏洞公告以官方為准,細心留意公告的用詞
2,開源的代碼首先想到到github找commit記錄
3,多方資料輔助驗證
 
不足的地方:
0,tomcat源碼運行流程,安恆是怎么知道DefaultServlet和jspservlet的呢?
1,代碼能力提升,如果沒有人家的輪子,你能自己快速造出來嗎?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM