MS15-034/CVE-2015-1635 HTTP.SYS 漏洞分析

wyzsk發表於2020-08-19
作者: 360安全衛士 · 2015/04/16 10:10

前言


在4月的補丁日,微軟透過標記為“高危”的MS15-034補丁,修復了HTTP.SYS中一處遠端程式碼漏洞CVE-2015-1635。據微軟公告(https://technet.microsoft.com/en-us/library/security/MS15-034)所稱,當存在該漏洞的HTTP伺服器接收到精心構造的HTTP請求時,可能觸發遠端程式碼在目標系統以系統許可權執行。

這是對於伺服器系統影響不小的安全漏洞,任何安裝了微軟IIS 6.0以上的Windows Server 2008 R2/Server 2012/Server 2012 R2以及Windows 7/8/8.1作業系統都受到這個漏洞的影響。

從微軟的公告致謝來看,這個漏洞是由“Citrix Security Response Team”(美國思傑公司的安全響應團隊)發現,從網上公開的資訊來看,Citrix公司是一家從事雲端計算虛擬化、虛擬桌面和遠端接入技術領域的高科技企業。這也引發了Twitter上很多關於該漏洞是否是由針對Citrix公司的APT攻擊中發現的疑問,而就在微軟釋出補丁的不到12個小時內,便有匿名使用者在Pastebin網站上貼出了針對這個漏洞可用的概念驗證攻擊程式碼,似乎也印證了這一點。

筆者和360Vulcan的小夥伴們獲得該資訊後,就開始針對其進行深入的分析,並在12小時內初步分析清楚了漏洞的原理和利用相關資訊,下面就將我們分析的一些結果分享給大家,以便更好地促進安全社群理解和防禦這一高危安全漏洞。

漏洞重現


結合Pastebin網站上貼出的資訊(http://pastebin.com/ypURDPc4)和微軟公告,我們知道這是一個位於HTTP.SYS中的整數溢位漏洞,根據Pastebin網站的python程式碼,我們知道透過給IIS伺服器傳送這樣格式的HTTP請求,就可以觸發(檢測)這個漏洞:

GET / HTTP/1.1
Host: stuff
Range: bytes=0-18446744073709551615

我們直接使用wget或curl工具,也可以直接測試這個漏洞,例如使用如下命令列:

#!bash
wget 127.0.0.1 –debug –header="Range: bytes=0-18446744073709551615"

此處18446744073709551615轉為十六進位制即是 0xFFFFFFFFFFFFFFFF(16個F),是64位無符號整形所能表達的最大整數,那麼我們很容易可以想到,這個“整數溢位”必然同這個異常的超大整數有關。

Pastebin上POC的作者提供的檢測工具程式碼認為,如上請求包,若IIS伺服器返回“Requested Range Not Satisfiable”,則是存在漏洞,否則如果返回”The request has an invalid header name“,則說明漏洞已經修補。

在實測中可能很多人也會發現並非如此,針對不同的伺服器,這個測試程式很可能導致伺服器直接BSOD甚至直接引發VM程式Crash(對於虛擬主機),這是為什麼呢?這究竟是發生在何處的什麼原因的整數一處呢?在下面的小節中我們將會進一步講到。

漏洞原理分析


HTTP.SYS是微軟從IIS6.0開始,為了在Windows平臺上最佳化IIS伺服器效能而引入的一個核心模式驅動程式。它為IIS及其他需要運用HTTP協議的微軟伺服器功能提供HTTP請求的接收與響應、快速快取、提高效能、日誌等功能服務。

更多關於HTTP.SYS的資訊,可以參考微軟Technet Library中”IIS 6.0 Architecture”中的“HTTP Protocol Stack”一章(https://technet.microsoft.com/en-us/library/cc739400(v=ws.10).aspx)。 HTTP.SYS提供了兩個最重要的功能是Kernel-mode caching 和Kernel mode request queuing,而本次的安全漏洞就出在Kernel mode caching(核心模式快取)中。

這裡筆者以Windows 8.1 X86平臺上安裝的IIS 8.5為例進行分析講解,這裡我們分析的存在漏洞的HTTP.SYS版本號為6.3.9600.16520,修補後的http.sys版本為6.3.9600.17712

Pastebin上POC程式碼的匿名作者提到,補丁修補了http!UlpParseRange函式,透過RtlUlonglongAdd函式實現了修補/攔截。

從測試程式碼和函式名上,我們都可以看出這個漏洞同HTTP頭中的”Range“域有直接的關係, Range請求是HTTP協議中HTTP客戶端用於只獲取伺服器上檔案的某一部分資料的請求域,更多關於Range請求的細節和規範,可以參考RFC 7233 “Hypertext Transfer Protocol (HTTP/1.1): Range Requests”(http://www.rfc-editor.org/rfc/rfc7233.txt)。

這裡先簡單介紹一下http.sys快取工作的原理,IIS程式w3wp.exe接收到HTTP請求後,將資料快取到核心中,並整合HTTP回應頭,最後由http.sys組織資料包經由網路核心元件傳送出去。請求中包括Ranges物件的指定範圍,而快取中則包含了http檔案和大小資訊等。

我們接下來先來看看這個UlpParseRange函式,看他是否是這個漏洞的根本原因。

UlpParseRange的整個程式碼比較長,這裡就不全部貼出了,函式的邏輯很簡單,就是從Range bytes=lower-upper(也可以是lower-或-upper形式)中,解析出lower(即讀取範圍的開始offset)和upper(即讀取範圍的結束offset)),然後計算要讀取的長度,在正常的情況下,upper大於lower,因此長度=upper-lower +1

這裡如果是測試程式碼中的例子,lower=0 ,upper=0xFFFFFFFFFFFFFFFF

我們看看未修補前的程式碼是怎麼樣寫的

#!bash
PAGE:0009AD2C                 sub     eax, edx
PAGE:0009AD2E                 sbb     ecx, edi
PAGE:0009AD30                 add     eax, 1
PAGE:0009AD33                 mov     [esi], eax
PAGE:0009AD35                 adc     ecx, ebx
PAGE:0009AD37                 mov     [esi+4], ecx

透過彙編程式碼我們可知,這裡是將upper先減去lower,再加1,得到兩者之間的長度差距(例如 bytes=20-50, 則50-20+1 , 兩者之間有31個位元組)

按照例子裡的寫法,就是0xFFFFFFFFFFFFFFFF – 0 + 1 , 確實發生了整數溢位,64位無符號整數上溢為0。

我們來看修改後的版本:

#!bash
PAGE:0009B501                 push    ebx
PAGE:0009B502                 sub     eax, edx
PAGE:0009B504                 push    1
PAGE:0009B506                 sbb     ecx, edi
PAGE:0009B508                 push    ecx
PAGE:0009B509                 push    eax
PAGE:0009B50A                 mov     ecx, esi
PAGE:0009B50C                 call    [email protected]

這裡的程式碼是將upper 先減去 lower,然後再用RtlUlonglongAdd 將結果同1相加,這裡RtlUlonglongAdd會做安全性檢查,如果相加結果溢位,則會返回STATUS_INTEGER_OVERFLOW.

由於測試程式碼中lower傳入的是0,所以這裡也發生了溢位並被捕獲、阻止,但如果lower != 0,這裡壓根就不會捕獲到整數溢位,這是怎麼回事呢?真正出現問題的地方是這裡嗎?

實際上,這可能是POC編寫者故意隱藏了一點關鍵細節: UlpParseRange透過操縱Range引數可以引發整數溢位,也確實被進行了修補,但是並非這個Range資料真正出現問題的地方。

我們進一步推測和分析,發現本次漏洞真正利用的地方,而是UlAdjustRangesToContentSize,這個函式用於最終修正Ranges中指定的StartingOffset和Length的合法性。

首先UrlpParseRange解析了Range引數並獲得StartingOffset和Length後,會將其儲存在http請求的物件中,而在解析到對應的快取後,對比Offset + Length的大小,是否超過要請求的快取檔案資料長度,如果超出了,就要把length裁剪為適合的長度,防止讀取超出的資料,見如下程式碼:

#!bash
PAGE:0007FD09                 mov     eax, [ebp+length_low]    
PAGE:0007FD0C                 add     eax, dword ptr [ebp+offset_low]
PAGE:0007FD0F                 mov     dword ptr [ebp+offset_low], eax
PAGE:0007FD12                 mov     eax, [ebp+length_high]
PAGE:0007FD15                 adc     eax, dword ptr [ebp+offset_high]  ;將Length + Offset
PAGE:0007FD18                 cmp     eax, esi                          ;esi = content length ,快取的實際資料長度,進行對比
PAGE:0007FD1A                 jb      short loc_7FD30
PAGE:0007FD1C                 ja      short loc_7FD23
PAGE:0007FD1E                 cmp     dword ptr [ebp+offset_low], ecx  
PAGE:0007FD21                 jb      short loc_7FD30
PAGE:0007FD23
PAGE:0007FD23 loc_7FD23:                             
PAGE:0007FD23                                         
PAGE:0007FD23                 sub     ecx, [ebp+length_low]                ;length = contentlength-offset            
PAGE:0007FD26                 mov     eax, esi
PAGE:0007FD28                 sbb     eax, [ebp+length_high]
PAGE:0007FD2B                 mov     [edx+4], eax

這裡我們看到是一處可利用的整數溢位,Length + offset 如果發生溢位,就會小於contentsize,這裡就會跳過這個”adjust”的過程,Length沒有得到任何處理和修正,我們成功控制了Length。

以例子中的數值為例, length + offset = (0xFFFFFFFFFFFFFFFF + 1 ) + 0 (這個+ 1是前面UlpParseRange新增的) = 0 ,小於contentsize

而假設lower不為0,則結果 = lower ,只要結果小於contentsize,也是不會被adjust的。

也就是說,UlpParseRange處發生了整數溢位,而在此處導致了安全檢查的繞過,同時,如果lower != 0 ,UlpParseRange時不會被觸發整數溢位,而是應該在這裡得以觸發。

到這裡我們就弄清楚了這個漏洞的觸發流程和原理:

1.upper(range結束的offset) = 0xFFFFFFFFFFFFFFFF時,UlpParseRange或UlAdjustRangesToContentSize會觸發整數溢位,導致繞過UlAdjustRangesToContentSize的Length檢查

2.Length 可控,但是Length = 0xFFFFFFFFFFFFFFFF – lower(range開始的offset) , 且lower必須要小於要獲取目標檔案的資料長度contentlength。

BSOD的重現和原理


看到很多測試攻擊程式的研究人員都無法穩定重現BSOD,看Github上的討論,透過調整lower的數值,有些人可以打藍Server 2012 R2,有些人就不行,或者換個檔案就不行。

實際上,我們分析了這個漏洞的原理就可以很清楚的瞭解其中的規律了,首先一條原則是上面已經說到的lower不能大於請求的content length,例如假設請求iisstart.htm(648Bytes),lower就必須小於647。

同時,HTTP請求的處理實際是先透過w3wp發起的程式上下文內http先解析HTTP請求包,組合成緊湊的http回應包後,透過

UlSendData->UxTpTransmitPacket->UxpTpEnqueueTransmitPacket

排入佇列,然後再由UlSendCacheEntryWorker將其傳送出去,在這個過程中,如果range指定的資料開始offset小於緊湊的資料包頭部的總長度,那麼就不會觸發到後面繼續命中快取的處理。(range只允許對資料檔案記憶體指定,不能指定響應頭內的)

這裡我使用wget新增頭部的方式測試,回應包的長度應該是(針對Windows 8.1 X86)310個位元組,也就是說,lower必須大於等於310個位元組,其他的傳送還需要調整這個數值。

所以,針對iisstart.htm , lower >= 310 且 < 647 就可以穩定觸發BSOD了

進一步利用


這個漏洞難道只能BSOD嗎?說好的遠端程式碼執行呢?再深入看下漏洞觸發的細節,看上去似乎不能遠端程式碼執行,但是遠端讀取伺服器核心記憶體資料是有可能的。

UlpSendCacheEntry->UlBuildFastRangeCacheMdlChain中,http.sys會為HTTP回應頭和快取來源buffer/length(我們可控)建立MDL,那麼,對於我們的超長length,就會創造一個巨大的mdl,接著放入UxTpTransmitPacket的資料包物件中,透過tcpip->netio,最後解析MDL,將資料最終發出去。

此時是可以超過快取的空間,讀取快取記憶體往後的資料,如果快取記憶體後面是連續的0xffffffffffffffff – lower(4GB?)左右核心記憶體(通常是X64),就有可能實現資訊洩露。

不過首先是很難有連續的4G記憶體,同時透過IIS也很難一下獲得如此多的資料,那麼只能設法降低這個記憶體要求:length = 0xFFFFFFFFFFFFFFFF – lower ,且lower < contetnlength才行,我們可以想辦法提高content length,達到降低Length的目的,例如在伺服器上尋找一個接近4GB大小的檔案:)

分析時間倉促,有任何進一步的利用和錯誤之處,歡迎討論指正。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章