作者:阿里云
2017年9月19日,Apache Tomcat 官方確認并修復了兩個高危漏洞,漏洞 CVE 編號:CVE-2017-12615 和 CVE-2017-12616,該漏洞受影響版本為7.0-7.80之間,官方評級為高危,在一定條件下,攻擊者可以利用這兩個漏洞,獲取用戶服務器上 JSP 文件的源代碼,或是通過精心構造的攻擊請求,向用戶服務器上傳惡意 JSP 文件,通過上傳的 JSP 文件 ,可在用戶服務器上執行任意代碼,從而導致數據泄露或獲取服務器權限,存在高安全風險。
漏洞編號:
CVE-2017-12616 CVE-2017-12615
漏洞名稱:
CVE-2017-12615-遠程代碼執行漏洞 CVE-2017-12616-信息泄露漏洞
官方評級:
高危,實際測試漏洞危害較低
漏洞描述:
當 Tomcat 中啟用了 VirtualDirContext 時,攻擊者將能通過發送精心構造的惡意請求,繞過設置的相關安全限制,或是獲取到由 VirtualDirContext 提供支持資源服務的 JSP 源代碼,從而造成代碼信息泄露。
當 Tomcat 運行在 Windows 操作系統時,且啟用了 HTTP PUT 請求方法(例如,將 readonly 初始化參數由默認值設置為 false),攻擊者將有可能可通過精心構造的攻擊請求數據包向服務器上傳包含任意代碼的 JSP 文件,JSP文件中的惡意代碼將能被服務器執行。導致服務器上的數據泄露或獲取服務器權限。
在一定的條件下,通過以上兩個漏洞可在用戶服務器上執行任意代碼,從而導致數據泄露或獲取服務器權限,存在高安全風險。
漏洞利用條件:
影響范圍:
CVE-2017-12615影響范圍:Apache Tomcat 7.0.0 – 7.0.79 (windows環境)
CVE-2017-12616影響范圍:Apache Tomcat 7.0.0 – 7.0.80
Apache Tomcat 默認開啟 PUT 方法,org.apache.catalina.servlets.DefaultServlet
的 readonly 默認為 true,而且默認沒有在 conf/web.xml 里寫,需要手工添加并且改為 false,才可以測試。
<init-param>
? <param-name>readonly</param-name>
? <param-value>false</param-value>
</init-param>
本次 Apache Tomcat 兩個 CVE 漏洞涉及到 DefaultServlet 和 JspServlet,DefaultServlet 的作用是處理靜態文件 ,JspServlet 的作用是處理 jsp 與 jspx 文件的請求,同時 DefaultServlet 可以處理 PUT 或 DELETE 請求,以下是默認配置情況:
除了 jsp 和 jspx 默認是由 org.apache.jasper.servlet.JspServlet
處理,其他默認都是由org.apache.catalina.servlets.DefaultServlet
來處理。
可以看出即使設置 readonly 為 false,默認 tomcat 也不允許 PUT 上傳 jsp 和 jspx 文件的,因為后端都用org.apache.jasper.servlet.JspServlet
來處理 jsp 或是 jspx 后綴的請求了,而 JspServlet 中沒有 PUT 上傳的邏輯,PUT 的代碼實現只存在于 DefaultServlet 中。
這個漏洞的根本是通過構造特殊后綴名,繞過了 tomcat 檢測,讓它用 DefaultServlet 的邏輯去處理請求,從而上傳 jsp 文件。
目前主要三種方法:
利用這兩種姿勢 PUT 請求 tomcat 的時候,騙過 tomcat 而進入 DefaultServlet 處理的邏輯,如圖:
調試 DefaultServlet.java 代碼流程,如下:
設置 readOnly 為 false
先調用棧
重點看 doPut,這里 tomcat 開始處理 PUT 請求,可以看到這里如果 readonly 是 true 就直接進入 error 了,所以需要設置成 false。
真正寫入文件在 FileDirContext.java 的 rebind 函數里。
上面遺留了一個問題就是當請求 jsp%20
或是 jsp::$DATA
后綴的時候,為什么最終卻寫入 .jsp
后綴的文件,這些其實是 java.io. FileOutputStream 的問題了,具體需要分析 jdk 的 C 代碼才能得到解答,如圖
跟到 open 是 native 的,已經不是 java 層面的問題了,這個 open 實際上是一個 jni 接口,然后調用 windowsAPI CreateFileW 創建文件,這里下載 openjdk6 的 jdk 代碼分析,如圖:
這里 Java_java_io_FileOutputStream_open
便是上邊 java 代碼里 open 函數的C代碼實現,其中參數 path 對應 open 函數的 name 變量,繼續跟蹤,如圖:
繼續跟入 winFileHandleOpen,這里最終是調用 windows 的 CreateFileW 實現文件創建,如圖:
而在 windows 下,創建文件是對后綴名稱有要求的,例如:如果后綴末尾是空格,會被去掉,a.txt::$DATA
傳入 CreateFileW 也會被處理成 a.txt
前面沒有說evil.jsp/
,這種方法也可以 PUT 上傳,但是不同于上面兩種,這種方法是利用了 File 類的特性,先看代碼,如圖:
這里測試發現 java.io.File 會過濾掉子文件名末尾的斜杠,寫一個測試用例確實是這樣的,如圖:
具體跟蹤一下代碼,如圖:
繼續跟入,如圖:
在這里這個 normalize(path, n, (prev == slash) ? i - 1 : i)
會將文件名末尾的/過濾掉,所以可以導致后面文件寫入 jsp 文件。
Apache Tomcat 7 默認值是 true,在默認條件下,無法成功利用這兩個漏洞。為了觸發漏洞,需要在 conf/web.xml 中 defaultservlet 的配置中手工添加如下配置:
<init-param>
? <param-name>readonly</param-name>
? <param-value>false</param-value>
</init-param>
我們通過構造的請求上傳 b.jsp,執行該請求請求就會由 DefaultServlet 處理,從而PUT操作就會順利執行,成功上傳文件,以下通過構造請求,觸發并復現該漏洞:
在分析的時候,我們也發現 File 類存在一個利用點,normalize 函數過濾掉了末尾斜杠。我們通過構造請求測試,發現可以被利用,通過對全版本測試,發現 Windows、Linux、Unix 下的 Apache Tomcat 5.X、6.X、7.x、8.x、9.x 版本均受到影響,從這點可以看出官方給出的補丁存在繞過,目前該問題已經得到 Apache 官方的確認。
從以上分析可以得出,該漏洞利用的前提條件需要手動開啟 readOnly 功能,以支持上傳操作,在 Apache tomcat 7.X 版本默認配置的情況下是無法成功利用漏洞,從實際測試來看,漏洞危害性并沒有那么高。
但是如果用戶一旦啟用了 readOnly 功能,黑客可利用漏洞成功入侵。
從以上分析可以得出,該漏洞利用的前提條件需要手動開啟 readOnly 功能,開發或運維人員可以排查是否啟用了 PUT 方法并開啟了 readOnly 功能。
Figure 1成功上傳文件
Figure 2上傳文件時報405,上傳文件失敗
為什么PUT方法不安全?
除標準常用的 GET 和 POST 方法外,HTTP 請求還使用其他各種方法,PUT 方法是 HTTP 請求方法中的一種。此方法用于請求服務器把請求中的實體儲存在請求資源下,如果請求資源已經在服務器中存在,那么將會用此請求中的數據替換原先的數據,作為指定資源的最新修改版。如果請求指定的資源不存在,將會創建這個資源,且數據位請求正文。
在以往的實際安全事件案例中,我們可以看到,由于 PUT 方法自身不帶驗證機制,有很多利用PUT方法很快捷簡單的成功入侵服務器,上傳 Webshell 或其他惡意文件,從而獲取敏感數據或服務器權限,從 web 安全年最佳實踐來看,我們不推薦使用這些不安全的 http OPTIONS 方法。
從安全最佳實踐來說,我們應該遵循“最小化”原則,不要隨意開啟不必要的服務或方法,僅開啟必要的功能,減小風險暴露面,從而降低安全風險,保障業務的安全性。
最佳實踐:如何知道自己網站使用了哪些http方法?
查看響應的 Allow: GET, HEAD, POST, OPTIONS,TRACE
注:該測試僅限于自身業務。
禁用不必要的 http 方法
IIS 默認拒絕 PUT 和 DELETE 請求,如果使用了不安全的方法,建議禁用 webDAV 模塊。
<Location />
僅允許GET和POST方法,修改后重啟服務。
<LimitExcept GET POST >
Order Allow,Deny
Deny from all
</LimitExcept>
</Location>
修改web.xml配置,增加以下內容,并重啟tomcat服務:
<security-constraint>
<web-resource-collection>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>HEAD</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
最后感謝阿里巴巴集團安全部柏通的技術支持。
[1]. https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
[2]. http://stackoverflow.com/questions/630453/put-vs-post-in-rest
[3]. https://tomcat.apache.org/security-7.html
[4]. http://tomcat.apache.org/security-7.html#Fixed_in_Apache_Tomcat_7.0.81