漏洞描述:
3月27日,在Windows 2003 R2上使用IIS 6.0 爆出了0Day漏洞(CVE-2017-7269),漏洞利用PoC開始流傳,但糟糕的是這產品已經停止更新了。網上流傳的poc下載鏈接如下。
github地址:https://github.com/edwardz246003/IIS_exploit
結合上面的POC,我們對漏洞的成因及利用過程進行了詳細的分析。在分析過程中,對poc的exploit利用技巧感到驚嘆,多次使用同一個漏洞函數觸發,而同一個漏洞同一段漏洞利用代碼卻實現不同的目的,最終通過ROP方式繞過GS的保護,執行shellcode。
調試環境:
虛擬機中安裝Windows Server 2003企業版,安裝iss6.0后,設置允許WebDAV擴展。使用的調試器為:windbg:6.7.0005.1
遠程代碼執行效果如下:
由上圖可到,漏洞利用成功后可以network services權限執行任意代碼。
漏洞分析:
漏洞函數
漏洞位於ScStoragePathFromUrl函數中,通過代碼可以看到,在函數尾部調用memcpy函數時,對於拷貝的目的地址來自於函數的參數,而函數的參數為上層函數的局部變量,保存在上層函數的棧空間中。在調用memcpy時,沒有判斷要拷貝的源字符長度,從而導致了棧溢出。
通過偽代碼更容易看出:
漏洞利用:
在POC中,可以看到發送的header中包含兩部分<>標簽,這會使上面的每個循環體都會運行兩次,為了下面的描述方便,我們對這兩個header的標簽部分分別定義為HEAD_A與HEAD_B。
漏洞利用流程:
-
在HrCheckIfHeader函數中,通過使用HEAD_A溢出,使用HEAD_B被分配到堆空間地址中。
- 在HrGetLockIdForPath函數中,再次通過使用HEAD_A溢出,使HEAD_B所在的堆地址賦值給局部對象的虛表指針,在該對象在調用函數時,控制EIP。
-
最終調用IEcb類的對象偏移0x24處的函數指針,控制EIP
漏洞利用主要在於HrCheckIfHeader函數與函數HrGetLockIdForPath中。
函數HrCheckIfHeader主要功能是對用戶傳遞來的Header頭進行有效性的判定。在函數中HrCheckIfHeader通過了while循環來遍歷用戶輸入的Header頭中的數據。
HrGetLockIdForPath主要功能是對傳遞來的路徑信息進行加鎖操作。在HrGetLockIdForPath函數中,也是通過while循環來遍歷路徑信息,同樣也對應着兩次調用漏洞函數。
調試過程:
兩次溢出控制EIP
對這4個調用漏洞函數的地方分別下斷:
bp httpext!CParseLockTokenHeader::HrGetLockIdForPath+0x114 ".echo HrGetLockIdForPath_FIRST";
bp httpext!CParseLockTokenHeader::HrGetLockIdForPath+0x14f ".echo HrGetLockIdForPath_SECOND";
bp httpext!HrCheckIfHeader+0x11f ".echo HrCheckIfHeader_FIRST";
bp httpext!HrCheckIfHeader+0x159 ".echo HrCheckIfHeader_SECOND";
調試程序,共會斷下6次,我們對這6次斷點處漏洞函數在利用時的功能進行歸納:
第一次:
暫停在HrCheckIfHeader _FIRST,對漏洞利用沒有影響
第二次:
斷在HrCheckIfHeader _SECOND,此處調用漏洞函數的目的是為了使用HEAD_A標簽,來溢出漏洞函數,目的是使用HEAD_A標簽中的堆地址覆蓋棧中的地址,此堆地址會在隨后使用。
運行漏洞函數前,
運行過漏洞函數后,可以看到棧空間中的0108f90c位置處的內容已經被覆蓋成了680312c0,680312c0正是一個堆中的地址。
第三次:
暫停在HrCheckIfHeader_FIRST,此時漏洞函數的作用是,將HEAD_B標簽拷貝到上面的堆地址中。本來正常的程序在這里會將用戶傳遞進來的HEADER拷貝到棧空間中,但在上面因為溢出,將HEAD_B標簽拷貝到了堆中。可以看到使用的堆地址680312c0。
第四次:
暫停到HrCheckIfHeader_FIRST,對漏洞利用沒有影響
第五次:
HrCheckIfHeader_SECOND,此處調用漏洞函數的目的是為了使用HEAD_A標簽,來溢出漏洞函數,目的是使用HEAD_A標簽中的堆地址覆蓋棧中的地址,此堆地址會在隨后使用。溢出AAA db ebp-14 將棧中的地址改成了與堆中的地址 680312c0,在這里ebp-14的地址也被覆蓋,這個地址在下面第六次的溢出時,會賦值給對象指針,在這里就控制了ebp_14的值,也就可以控制下一步中的對象指針。
第六次:
HrCheckIfHeader_FIRST在這個函數下面的子子函數中會調用虛函數,從而控制EIP。
總結一下,在上面六次調用處,需要關注的利用過程是:
1) 第二次與第三次處是必須的,因為沒有第二次處的利用,就不會有第三次處的把HEAD_B拷貝到堆中。沒有堆中的地址在第六次調用時就沒法控制虛表指針。所以沒有第二次的溢出調用,就不會有堆中的HEAD_B內存。(本來HEAD_B的歸宿是棧空間,就是因為溢出了才把HEAD_B放到了堆空間中)
2) 第五次再次把棧溢出,把堆的地址寫到了局部變量中,才導致第六次能成功調用虛函數。因為第六次調用虛函數時,是調用的局部變量的虛函數。如果沒有第五次斷點處的溢出,就無法把堆中地址成功的寫入到局部變量的虛函數中,也就無法控制虛函數指針。
由此可以看出,兩次對漏洞函數溢出操作,其中一次溢出操作(第二次斷點處)將棧地址改寫為堆地址,保證了HEAD_B被寫入到堆中,另外一次溢出操作(第五次斷點處)將局部變量對象的指針指向堆。兩次溢出代碼相同,實現的目的卻不同,雙劍合壁,鬼斧神工,巧妙結合實現對EIP的控制。
ROP
控制EIP后,使用ROP技術繞過GS的保護。
使用SharedUserData的方法執行自定義的函數
來到shellcode處:
Shellcode進行一次循環解碼:
解碼完成后,就是長得比較漂亮的shellcode了
緩解方案:
l 禁用 IIS 的 WebDAV 服務
l 使用 WAF相關防護設備
l 建議用戶升級到最新系統 Windows Server 2016。
總結
通過分析可以看到,漏洞原理只是因為沒有對拷貝函數的長度做判斷,而導致了棧溢出。這也提醒廣大程序員們,慎用不安全的內存操作函數,在編譯代碼時開啟所有保護。從漏洞利用角度分析,對於棧溢出,喜聞樂見的利用手法為修改返回地址,覆蓋虛表指針等方法,但這種利用棧溢出把指針引向堆空間中,在需要的時候,再通過溢出將堆空間中的地址引回到棧空間中的利用手法確實也是標新立異、與眾不同,同一個漏洞代碼處使用多次溢出最終實現exploit,即使在分析完成后也對利用手法回味悠長。