Apache Tomcat任意文件讀取漏洞和命令執行漏洞源碼分析(CVE-2020-1938)
環境準備
在分析Apache Tomcat源碼漏洞之前,我們先需要使用Idea配置Tomcat源碼,請參考:https://blog.csdn.net/SouthWind0/article/details/105147406
漏洞分析
Tomcat在默認的conf/server.xml中配置了2個Connector,一個默認監聽8080端口處理HTTP請求,另外一個默認監聽8009端口處理AJP請求。
Tomcat在接收ajp請求的時候調用org.apache.coyote.ajp.AjpProcessor來處理ajp消息,prepareRequest()方法將ajp裏的內容取出,並設置成request對象的屬性。既然如此,我們先在調用prepareRequest()方法的地方下一個斷點。
跟進到Decode extra attributes這個位置,也就是獲取解析屬性和設置屬性的地方。可以看到它循環獲取數據後,又設置成request對象下面的三個Attribute屬性,這意味着我們可以控制這三個屬性。
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path
繼續跟進,可以看到封裝成了對應的request之後,它將要接着走servlet的映射。
通過走不同的映射,產生了兩種類型的漏洞,如下所示。關於映射請參考,Tomcat的DefaultServlet和JspServlet一文,地址:
https://blog.csdn.net/SouthWind0/article/details/105147262。
(1)任意文件讀取
AJP請求:
forwardrequest 2 "HTTP/1.1" "/123.png" 127.0.0.1 127.0.0.1 porto 8009 false "Cookie:AAAA=BBBB","Accept-Encoding:identity" "javax.servlet.include.request_uri:/","javax.servlet.include.path_info:log/test.jsp","javax.servlet.include.servlet_path:/"
我們發送上面的AJP請求,因爲/123.png將走DefaultServlet,關鍵請求參數如下:
javax.servlet.include.request_uri: /
javax.servlet.include.path_info: log/test.jsp
javax.servlet.include.servlet_path: /
Debug源碼如下:
接着調用容器來處理
在HttpServlet中調用doGet()方法
跟進,來到了org.apache.catalina.servlets.DefaultServlet的doGet()方法中,doGet()方法又調用serveResource()方法進行資源讀取操作。
跟進到serveResource()方法中,可以看到它使用getRelativePath()方法來獲取資源的相對路徑。
既然到了門口,我們去看看getRelativePath()方法,可以看到,由於我們的AJP請求設置javax.servlet.include.request_uri屬性值爲/不爲null,那麼資源的相對路徑構造如下:
= javax.servlet.include.path_info + javax.servlet.include.path_info
= / + log/test.jsp
= /log/test.jsp
返回來,我們已經通過getRelativePath()方法獲取資源的相對路徑,那麼接着就需要讀取資源了。接着往下走,可以看到通過getResources()方法就可以獲取到對應路徑的資源了。
值得一提的是,跟進這個方法,可以發現有趣的事情,如果路徑存在./或../則會返回null,這樣就解釋了爲什麼我們無法跳出webapps目錄來讀取文件了。
繼續往下走,最後資源對象的內容隨着resourceBody被寫入了ostream流對象中返回給客戶端。
成功讀取文件內容,我們請求的是/123.png,返回的是/log/test.jsp的內容。
(2)任意文件包含(代碼執行)
AJP請求:
forwardrequest 2 "HTTP/1.1" "/123.jsp" 127.0.0.1 127.0.0.1 porto 8009 false "Cookie:AAAA=BBBB","Accept-Encoding:identity" "javax.servlet.include.request_uri:/","javax.servlet.include.path_info:log/test.txt","javax.servlet.include.servlet_path:/"
我們發送上面的AJP請求,因爲/123.jsp將走JspServlet,關鍵請求參數如下:
RequestUri:/123.jsp
javax.servlet.include.request_uri: /
javax.servlet.include.path_info: log/test.txt
javax.servlet.include.servlet_path: /
Debug源碼如下:
接着調用容器來處理
在HttpServlet中調用service()方法
跟進,接着來到了org.apache.jasper.servlet.JspServlet的service()方法中,jspUri爲jsp文件的相對路徑,之後jspUri被傳入serviceJspFile()方法。
跟進serviceJspFile()方法,可以看到我們可以控制的jspUri被封裝成了一個JspServletWrapper,並添加到了Jsp運行上下文JspRuntimeContext中,最後wrapper.service()會編譯執行test.txt。這樣導致了test.txt被當作jsp文件編譯執行,代碼執行漏洞產生。
成功執行惡意代碼,我們訪問的是/123.jsp,返回的是把/log/test.txt當作jsp文件執行後的內容。
test.txt的文件內容:
注意
需要注意的是RequestUri:/,此時會走JspServlet;而RequestUri:/123,此時會走DefaultServlet。