轉:https://mp.weixin.qq.com/s/qoEZE8lBbFZikKzRTwgdswios
從20/20過後來看,許多普遍部署的技術彷佛是一種奇怪或沒必要要的冒極大風險的想法。IT工程決策每每是由信息不完善和時間壓力形成的,而IT系統中的一些古怪問題最好用「當時看來是個好主意」來解釋。在這篇文章的做者我的觀點中,WPAD(「WebProxy Auto Discovery Protocol」 --更具體地說是「Proxy Auto-Config」-自動代理配置)是這些怪異之一。程序員
在互聯網早些時候 - 1996年之前,Netscape的工程師認爲JavaScript是編寫配置文件的好語言。這就產生了PAC--一種配置文件格式,其工做原理以下:瀏覽器鏈接到預先配置的服務器,下載PAC文件,並執行特定的JavaScript函數以肯定完整的代理配置。爲何不呢?它確實比XML(比方說)更具表現力和詳細,並且彷佛是向許多客戶提供配置的合理方式。數組
PAC自己伴隨了一個名爲WPAD的協議 --一個協議,使得瀏覽器不須要預先配置的服務器來鏈接。相反,WPAD容許計算機查詢本地網絡以肯定加載PAC文件的服務器。瀏覽器
不知何故,這項技術最終成爲1999年到期的IETF草案,如今,在2017年,每臺Windows機器都會詢問本地網絡:「嘿,我在哪裏能夠找到一個Javascript文件來執行?」。這能夠經過多種機制來現:DNS,WINS,但也許最有趣的是DHCP。緩存
近年來,瀏覽器的攻擊已經從主要面向DOM的方向發展到直接面向Javascript引擎,因此只要咱們能夠在沒有瀏覽器的狀況下經過網絡來執行Javascript執行就是激動人心的。最初調查顯示,JS引擎負責執行這些配置文件是JSCRIPT.DLL - JS引擎也支持IE7和IE8(若是使用適當的腳本屬性,仍然能夠在IE11在IE7 / 8兼容模式中使用)。這有好有壞 -- 一方面,這意味着並非每個Chakra bug自動成爲本地網絡的遠程攻擊,但另外一方面,這意味着一些很老的代碼將負責執行咱們的JavaScript。安全
安全研究人員以前曾警告WPAD的危險。 但就咱們所知,這是第一次發現對WPAD的攻擊,結果致使使用WPAD用戶機器的徹底破壞。服務器
Windows並非實現WPAD的惟一系統,其餘操做系統和應用程序也是如此。例如Google Chrome也有一個WPAD實現,但在Chrome中,評估來自PAC文件的JavaScript代碼發生在一個沙箱內。而其餘支持WPAD的操做系統默認不啓用它。這就是爲何Windows是目前這種攻擊最有趣的目標。網絡
如上所述,WPAD將查詢DHCP和DNS(按此順序)以獲取要鏈接的URL -- 若是沒有可用的DNS響應,顯然LLMNR和Netbios也可使用。WPAD-over-DNS的一些特性使得攻擊向量可以出人意料地發揮做用。數據結構
在最多見的狀況下,一臺機器將使用選項碼252查詢本地DHCP服務器.DHCP服務器回覆一個字符串-- 好比「http://server.domain/proxyconfig.pac」,它指定一個配置文件的URL。 而後客戶端繼續獲取這個文件,並將內容做爲Javascript執行。框架
在本地網絡中,攻擊者能夠簡單地模仿DHCP服務器 -- 能夠經過ARP遊戲或經過合法DHCP進行競爭。而後,攻擊者能夠提供一個惡意JavaScript文件所在的URL。
除了本地網絡攻擊場景,WPAD查找也可能經過DNS發生的事實會產生一個輔助攻擊場景。許多用戶將他們的計算機配置爲針對公共的,全局可見的DNS服務器(如8.8.8.8, 8.8.4.4, 208.67.222.222和208.67.220.220)來執行DNS查找。 在這種狀況下,一臺機器會發送DNS查詢(如wpad.local)到位於本地網絡以外的服務器。 處於網絡特權位置的攻擊者(例如網關或任何其餘上游主機)能夠監視DNS查詢並僞造應答,指示客戶端下載並執行惡意的JavaScript文件。
像這些設置彷佛是常見的 - 根據這個維基百科條目,DNS根服務器流量的一小部分是本地請求。
WPAD的特殊之處在於遞歸地遍歷本地機器名稱以查找要查詢的域。若是一臺機器被稱爲「laptop01.us.division.company.com」,則按照如下方式查詢如下域名:
1.wpad.us.division.company.com
2.wpad.division.company.com
3.wpad.company.com
4.wpad.com
這(根據這個維基百科條目)致使人們註冊wpad.co.uk並將流量重定向到在線拍賣網站。進一步引用該條目:
經過WPAD文件,攻擊者能夠將用戶的瀏覽器指向本身的代理,攔截並修改全部的http流量。儘管2005年對WindowsWPAD處理進行了簡單的修復,但它只解決了.com域的問題。在Kiwicon的一次演講顯示,世界其餘地區仍然受到這個安全漏洞的嚴重威脅,爲測試,咱們在新西蘭註冊一個樣本域,接收來自全國各地的代理請求,幾秒鐘就接受到大量的請求。一些wpad.tld域名(包括COM,NET,ORG和US)現今會指向客戶端回送地址,以防止此漏洞,儘管有些名稱仍然是註冊的(wpad.co.uk)。
所以,管理員應確保用戶能夠信任網絡中的全部DHCP服務器,而且網絡中全部可能的wpad域都受到控制。此外,若是沒有爲網絡配置wpad域,則用戶將轉到外部位置的具備域層次結構中下一個wpad站點,並將其用於配置。這就容許攻擊者在某個特定的國家註冊wpad子域名,經過將本身設置爲全部流量感興趣站點的代理,對該國的大部分互聯網流量進行中間人攻擊。
另外一方面,IETF草案明確要求客戶只容許「典型域」(例如非頂級域名)。咱們尚未調查客戶在什麼程度上實施這個,或者二級域名(如.co.uk)是不是流量重定向案例的罪魁禍首。
不管哪一種方式:若是某個特定組織的頂級域名(TLD)註冊了$ TLD,只要該頂級域名(TLD)未被客戶端明確列入黑名單,就能夠經過互聯網遠程利用Javascript引擎中的bug。 鑑於1999年的IETF草案提到了1994年的TLD列表(RFC1591),客戶不太可能已經更新了最新的TLD。
咱們花了一些時間採用手動分析和fuzz尋找jscript.dll中的bug。 Jscript存在着一些挑戰,由於不少用於觸發JavaScript引擎中的bug的「特性」不能在JScript中使用,只是因爲它太舊而沒法支持它們。例如:
沒有多個數組類型(int數組,float數組等)。所以混淆一個數組類型是不可能的。
沒有像更新,更快的JavaScript引擎那樣的優化(「fast path」)。這些fast path每每是bug的來源。
在通用JavaScript對象上定義getter / setter是不可能的。能夠調用defineProperty,但只能調用不適合咱們的DOM對象,由於在WPAD過程當中不會有DOM。即便存在,當調用一個帶有「JScript object expected」消息的DOM對象時,不少JScript函數也會失敗。
不可能改變已經建立對象的原型(即沒有「__proto__」屬性)。
可是,JScript確實有不少的「老派」漏洞,好比UAF。 JScript的垃圾回收器在這個老的MSDN文章中有描述。 JScript使用非世代的標記和清理垃圾收集器。從本質上講,不管什麼時候垃圾收集被觸發,它都會標記全部的JScript對象。而後從一組「根」對象(有時也稱爲「清道夫」)開始掃描它們,並從所遇到的全部對象中清除標記。全部仍被標記的對象都被刪除。一個反覆出現的問題是默認狀況下,堆棧上的局部變量不會被添加到根對象列表中,這意味着程序員須要記住將它們添加到垃圾回收器的根列表中,特別是若是這些變量引用能夠在函數生命週期中被刪除。
其餘可能的漏洞類型包括緩衝區溢出,未初始化的變量等。
爲了fuzzing,咱們使用了基於語法的Domato fuzz引擎,並專門爲JScript寫了一個新的語法。咱們經過查看各類JScript對象的EnsureBuiltin方法來識別有趣的內置屬性和函數,以添加到語法中。 JScript語法已經被添加到這裏的Domato倉庫中。
經過fuzz和手動分析,咱們肯定了七個安全漏洞。它們總結在下表中:
Vulnerability class |
Vulnerabilities affecting IE8 mode |
Vulnerabilities affecting IE7 mode |
UAF |
1340,1376,1381 |
1376 |
Heap overflow |
1369,1383 |
1369,1383 |
Uninitialized variable |
1378 |
1378 |
Out_of_bounds read |
1382 |
1382 |
Total |
7 |
5 |
在這篇博文發佈以前,全部以上的漏洞都已經被微軟所修復。
該表中的漏洞經過觸發它們所需的類和兼容模式來觸發。WPAD中的JScript至關於在IE7兼容模式下運行腳本,這意味着儘管咱們發現了7個漏洞,但WPAD中只能觸發5個。可是,其餘漏洞仍然能夠在Internet ExplorerIE8兼容模式下被惡意網頁使用(包括IE11)。
在這篇博文的其他部分中,咱們將討論JScript VAR和Strings,在深刻探討漏洞的工做方式以前,先描述這些內容是很是有用的。
JScript VAR是一個24字節(在64位版本上)的結構,表示一個JavaScript變量,而且與MSDN文章中描述的VARIANT數據結構基本相同。在大多數狀況下(足以跟蹤漏洞)其內存佈局以下所示:
Offset |
Size |
Description |
0 |
2 |
變量類型,3表示int,5表示double,8爲string |
8 |
8 |
由類型決定,多是一個數值或者指針 |
16 |
8 |
大多數類型不使用此字段 |
例如,咱們能夠經過將5寫入前2個字節(表示雙重類型)的VAR表示雙精度數,後在偏移8跟一個實際的雙精度值.最後8個字節將是未使用的,但它們若是從這個VAR複製另外一個VAR的值,將被複制。
JScript字符串是一種類型爲8,偏移量8處爲指針的VAR。指針指向這裏描述的BSTR結構。在64位構建BSTR佈局看起來像這樣:
一個字符串VAR直接指向字符數組,這意味着,要得到一個字符串的長度,指針須要減小4,並從那裏讀取長度。請注意,BSTR由OleAut32.dll處理,並分配在一個單獨的堆上(即與其餘JScript對象不一樣的堆)。
BSTR的釋放也不一樣於大多數對象,並非直接釋放BSTR,而是在調用SysFreeString時,它首先將一個字符串放入由OleAut32.dll控制的緩存中。 這個機制在JavaScript的堆風水(原文HeapFeng Shui)中有詳細的介紹。
infoleak的目的是得到咱們徹底控制的字符串的內存地址。此時咱們不會泄露任何可執行的模塊地址,這將在稍後進行。相反,其目標是挫敗高熵堆的隨機化,使第二階段的利用可靠,而沒必要使用堆噴射。
對於infoleak,咱們要在RegExp.lastParen中使用這個bug。爲了理解這個bug,咱們先來仔細看看jscript!RegExpFncObj的內存佈局,它對應於JScript RegExp對象。在偏移量0xAC處,RegExpFncObj包含20個整數的緩衝區。實際上這些是10對整數:第一個元素是輸入字符串的起始索引,第二個元素是結束索引。只要RegExp.test,RegExp.exec 或帶有RegExp參數的String.search遇到捕獲組(RegExp語法中的圓括號),匹配到的開始和結束索引就會存儲在這裏。顯然在緩衝區中只有10個匹配的空間,因此只有前10個匹配組被存儲在這個緩衝區中。
可是,若是RegExp.lastParen被調用,而且有超過10個捕獲組,RegExpFncObj :: LastParen會很高興地使用捕獲組的數量做爲索引進入緩衝區,致使越界讀取。這是一個PoC:
var r= new RegExp(Array(100).join('()'));
''.search(r);
alert(RegExp.lastParen);
這兩個索引(咱們稱之爲start_index和end_index)是在緩衝區邊界以外讀取的,所以能夠任意大。假設這第一次越界訪問不會致使崩潰,若是這些索引中的值大於輸入字符串的長度,那麼將發生第二次越界訪問,這容許咱們讀取輸入字符串的邊界以外的數據。像這樣越界讀取的字符串內容會返回給調用者一個可被檢查的字符串變量。
咱們要使用的是第二次越界讀取,但首先咱們須要弄清楚如何將受控數據寫入start_index和end_index。幸運的是,查看RegExpFncObj的佈局,在索引緩衝區結束後就是咱們控制的數據:RegExp.input值。經過將RegExp.input設置爲整數值並使用由41組空括號組成的RegExp,當調用RegExp.lastParen時,start_index將爲0,end_index將爲咱們寫入RegExp.input的任何值。
若是咱們把一個輸入字符串與一個被釋放的字符串相鄰,那麼經過在輸入字符串的邊界以後讀取,咱們能夠得到堆的元數據,例如指向其餘空閒堆段的指針(在紅黑色中的Left,Right和Parent節點堆的樹塊,請參閱Windows10 Segment堆內部瞭解更多信息)。圖1顯示了infoleak時的相關對象。
咱們使用20000字節長的字符串做爲輸入,以便它們不會分配到低碎片堆(LFH只能用於16K字節或更小的分配),由於LFH堆的元數據是不一樣的,而且不包括Windows 10 Segment堆中有用的指針。此外,LFH引入了隨機性,這會干擾咱們將輸入字符串放在釋放字符串旁邊的操做。
經過從返回字符串讀取堆中的元數據,咱們能夠得到一個釋放字符串的地址。那麼,若是咱們分配一個與被釋放字符串大小相同的字符串,它就能夠被放置在這個地址。咱們實現了咱們的目標,那就是咱們知道一個內容由咱們控制的字符串的內存地址。
整個infoleak過程以下所示:
分配1000個10000字符的字符串(注意:10000個字符== 20000字節)。
釋放2,4,6字符串……
觸發信息泄漏錯誤。使用剩餘的字符串之一做爲輸入字符串並讀取20080字節。
分析泄漏的字符串並獲取指向一個釋放字符串的指針。
分配與釋放的字符串(10000個字符)長度相同的特定內容的500個字符串。
特製字符串內容在這個階段並不重要,但在下一個階段是重要的,因此在這裏不會描述。另外請注意,經過檢查堆元數據,咱們能夠輕鬆地肯定進程正在使用的堆(Segment Heap vs NT Heap)。
圖2和圖3顯示了在infoleak時使用Heap History Viewer建立的堆可視化圖像。綠色條紋表示分配的塊(被字符串佔據),灰色條紋表示分配的塊被釋放,而後再次被分配(字符串釋放並在觸發infoleak bug後被從新分配)白色條紋表示從未分配的數據(保護頁)。你能夠看到隨着時間的推移字符串是如何分配的,一半被釋放(灰色的),一段時間後又被分配(條紋變成綠色)。
咱們能夠看到每隔3次分配這個大小後就會有一個保護頁。咱們的漏洞歷來沒有實際觸及任何這些保護頁(它讀取的字符串末尾的數據太少,以致於不會發生這種狀況),但在這種狀況下,在輸入字符串以後不會有空閒字符串infoleak,因此預期的堆元數據將會丟失。可是,咱們能夠很容易地檢測到這種狀況,並使用另外一個輸入字符串觸發infoleak bug,或者悄悄地停止exploit(注意:到目前爲止咱們沒有觸發任何內存損壞)。
Image 2: Heap Diagram: Showing the evolution ofthe heap over time
Image 3: Step-by-step illustration ofleaking a pointer to a string.
在攻擊的第二階段,咱們將使用Array.sort中的堆溢出bug。若是輸入數組中的元素數量大於Array.length / 2,則JsArrayStringHeapSort(若是未指定比較函數,則由Array.sort調用)將分配一個相同大小的臨時緩衝區(注意:能夠小於array.lenght)。而後嘗試從0到Array.length索引檢索相應的元素,若是該元素存在,則將其添加到緩衝區並轉換爲字符串。若是數組在JsArrayStringHeapSort的生命週期中沒有改變,這將工做正常。可是,JsArrayStringHeapSort將數組元素轉換爲能夠觸發toString()回調的字符串。若是在其中一個toString()回調元素被添加到未定義的數組中,將發生溢出。
爲了更好地理解這個bug及其可利用性,咱們來仔細看看溢出的緩衝區的結構。已經提到過,數組的大小與當前在輸入數組中的元素的數量相同(確切地說,它將是元素的數量+1)。數組中的每一個元素的大小都是48字節(64位版本),結構以下:
在JsArrayStringHeapSort期間,index<array.length數組中的每一個元素都會被檢索而且若是元素被定義,則會發生如下狀況:
1.數組元素被讀入偏移量爲16的VAR
2.原始的VAR被轉換成一個字符串VAR。指向字符串VAR的指針寫在偏移量0處。
3.在偏移8處,寫入數組中當前元素的索引
4.根據原始的VAR類型,在偏移量40寫0或1
觀察臨時緩衝區的結構,咱們不能直接控制不少細節。若是一個數組成員是一個字符串,那麼在偏移量爲0和24時,咱們將會有一個指針,當它被取消引用時,偏移量爲8的指針包含另外一個指向由咱們控制的數據的指針。然而,這是一個間接的在大多數狀況下對咱們有用的大方向。
然而,若是數組的成員是一個雙精度數,則在偏移24(對應於偏移量8原始VAR),該數的值將直接被寫入,而且是咱們的控制之下。若是咱們建立具備相同表明double的如在階段1中得到的指針的數,那麼咱們能夠溢出覆蓋指向緩衝區以外的指針爲指向咱們直接控制內存中。
如今問題變成了,咱們該按照這樣的方式覆蓋什麼指針來利用。若是咱們仔細研究一下對象如何在JScript中工做那麼就會發現一個可能的答案。
每一個對象(更具體地說,一個NameList的JScript對象)將有一個指向哈希表的指針。這個哈希表是一個指針數組。當一個對象的成員元素被訪問時,先計算元素名稱的hash。而後取消對應於哈希最低位偏移處的指針。這個指針指向一個對象元素的鏈表,而後這個鏈表被遍歷,直到咱們到達一個與請求元素具備相同名字的元素。這在圖4中顯示。
Image 4: JScript 內部對象元素
請注意,當元素的名稱少於4個字節時,它將存儲在與VAR(元素值)相同的結構中。不然,將會有一個指向元素名稱的指針。名稱長度<= 4對於咱們來講已經足夠了,因此咱們不須要深刻了解這個細節。
一個對象散列表是一個很好的覆蓋候選,由於:
咱們能夠經過訪問相應的對象成員來控制哪些元素被取笑引用。咱們用不受控制的數據覆蓋的元素將永遠不會被訪問。
經過控制相應對象的成員個數,咱們對散列表大小有着有限控制。例如,散列表以1024字節開始,但若是咱們向對象添加超過512個元素,散列表將被從新分配爲8192個字節。
經過用指向控制數據的指針覆蓋散列表指針,咱們能夠在控制數據中建立假的JScript變量,並經過訪問相應的對象成員來訪問它們。
要可靠地進行覆蓋,咱們執行如下操做:
分配和釋放大量大小爲8192的內存塊.這將打開Low Fragmentation Heap使得分配大小爲8192。這將確保咱們溢出的緩衝區,以及咱們溢出的散列表將被分配LFH。這一點很重要,由於這意味着附近不會有其餘大小的分配來破壞漏洞攻擊(由於LFH只能包含特定大小的分配)。這反過來確保咱們將高度可靠地覆蓋咱們想要的東西。
建立2000個對象,每一個對象包含512個成員。在這種狀態下,每一個對象都有一個1024字節的散列表。可是,向其中一個對象添加一個元素將致使其散列表增加到8192個字節。
將513元素添加到前1000個對象中,致使1000個8192字節哈希表的分配。使用長度爲300和170個元素的數組觸發Array.sort。這將分配一個大小爲(170 + 1)* 48 = 8208字節的緩衝區。因爲LFH的粒度,這個對象將被分配在與8192字節哈希表相同的LFH中。
當即(在第一個數組元素的toString()方法中)向第二個1000對象添加第513個元素。這使得咱們很是肯定如今排序緩衝區是相鄰的哈希表之一。在相同的toString()方法中也向數組中添加更多的元素,這會致使它超出邊界。
圖5顯示了排序緩衝區地址時的堆可視化(紅線)。您能夠看到排序緩衝區被大小相近的分配所包圍,這些分配都與對象哈希表相對應。你也能夠觀察到LFH的隨機性,由於隨後的分配不必定在隨後的地址上,然而這對咱們的利用沒有任何影響。
Image5: Heap visualization around the overflow buffer
正如前面提到的,咱們以這樣一種方式形成了溢出:一個不幸的JScript對象的hashtable指針會被覆蓋爲指向到咱們控制的數據的指針。如今終於輪到咱們的數據來起做用:咱們以這樣一種方式建立一個包含5個(假的)JavaScript變量:
變量1只包含數字1337。
變量2是特殊類型的0x400C。 這個類型基本上告訴JavaScript,實際的VAR是由偏移量爲8的指針指向的,在讀取或寫入這個變量以前,這個指針應該被解除引用。在咱們的例子中,這個指針指向變量1以前的16個字節。這基本上意味着變量2的最後8字節的qword和變量1的第一個8字節的qword重疊。
變量3,變量4和變量5是簡單的整數。它們的特殊之處在於它們分別在其最後8個字節中包含數字5,8和0x400C。
圖6中顯示了溢出以後被破壞的對象的狀態。
圖6:溢出後的對象狀態紅色區域表示溢出發生的地方。 底行中的每一個框(除了那些標記爲「...」的)對應於8個字節。 爲清楚起見,「...」框中的數據被省略
咱們只需訪問正確索引處(咱們稱之爲index1)的已損壞對象就能夠訪問變量1,對於變量2-5也是如此。實際上,咱們能夠經過訪問全部對象的index1來檢測哪一個對象被破壞,並查看哪一個對象如今具備值1337。
重疊變量1和變量2的做用是能夠將變量1的類型(第一個WORD)更改成5(double),8(string)或0x400C(pointer)。咱們經過讀取變量2,3或4而後將讀取的值寫入變量2來實現。例如,語句
corruptedobject [index2] = corruptedobject[index4];
具備將變量1的類型更改成String(8)的效果,而變量1的全部其餘字段將保持不變。
這種佈局給了咱們幾個很是強大的利用權限:
若是咱們將一個包含一個指針的變量寫入變量1,咱們能夠經過將變量1的類型改成double(5)並讀出它來泄露這個指針的值
咱們能夠經過在該地址僞造一個字符串來在任意地址上泄露(讀取)內存。咱們能夠經過首先將與咱們想要讀取的地址對應的double值寫入變量1,而後將變量1的類型更改成String(8)來完成此操做。
首先將對應地址的數值寫入變量1,而後將變量1的類型更改成0x400C(指針),最後將一些數據寫入變量1,從而寫入任意地址。
有了這些利用權限,一般得到代碼執行將很是簡單,可是因爲咱們正在利用Windows 10,咱們首先須要繞過Control Flow Guard(CFG)。
這裏可能有已經公開的方法,但事實證實,jscript.dll有一些很是方便的繞過方法(一旦攻擊者有一個讀/寫權限)。咱們將利用如下事實:
返回地址不受CFG保護
一些Jscript對象有指向本地堆棧的指針
具體來講,每一個NameTbl對象(在Jscript中,全部的JavaScript對象都繼承自NameTbl),在偏移量爲24的位置持有一個指向CSession對象的指針。CSession對象在偏移量爲80的地方保存一個指向本地堆棧頂部附近的指針。
所以,經過任意讀取,跟隨來自任何JScript對象的指針鏈,能夠檢索指向本地堆棧的指針。而後,經過任意寫入,能夠覆蓋返回地址,繞過CFG。
利用全部的漏洞元素,咱們如今能夠開始編寫poc。 咱們正在這樣作:
1.從任何JScript對象的vtable讀取jscript.dll的地址
2.經過閱讀jscript.dll的導入表讀取kernel32.dll的地址
3.經過讀取kernel32.dll的導入表來讀取kernelbase.dll的地址
4.在kernel32.dll掃描咱們須要的rop gadget
5.從kernel32.dll的導出表中獲取WinExec的地址
6.如上一節所述,泄漏堆棧地址
7.準備ROP鏈並將其寫入堆棧,從最接近咱們泄漏堆棧地址的返回地址開始。
咱們使用的ROP鏈以下所示:
[addressof RET] //needed to align the stack to16 bytes
[address of POPRCX; RET] //loads the first parameter into rcx
[address ofcommand to execute]
[address of POPRDX; RET] //loads the second parameter into rdx
1
[address ofWinExec]
經過執行這個ROP鏈,咱們用指定的命令調用WinExec。例如,若是咱們運行命令「cmd」,咱們將看到一個命令提示符被生成,以本地服務運行(運行WPAD服務的相同用戶)。
不幸的是,從做爲本地服務運行的子進程中,咱們不能與網絡通訊,可是咱們能夠作的是將提權載荷從內存中放入到磁盤中。本地服務能夠從那裏寫入並執行它。
雖然本地服務賬戶是服務賬戶,但它不具備管理權限。這意味着攻擊者在系統上能夠訪問和修改的東西是很是有限的,尤爲是在利用以後或系統重啓以後維持訪問。雖然在Windows中總有可能存在未固定的特權升級,但咱們不須要找到新的漏洞來升級咱們的特權。相反,咱們能夠濫用內置功能從本地服務升級到SYSTEM賬戶。咱們來看看已授予WPAD服務賬戶的權限:
Image7: Service Access Token’s Privileges showing Impersonate Privilege
咱們只有三個權限,但突出顯示的權限SeImpersonatePrivilege很是重要。此權限容許服務模擬本地系統上的其餘用戶。服務具備模擬特權的緣由是它接受來自本地系統上全部用戶的請求,可能須要表明他們執行操做。可是,只要咱們可以獲取咱們想要模擬的賬戶的訪問令牌,咱們就能夠得到令牌賬戶的徹底訪問權限,包括SYSTEM,這會使咱們得到本地系統上的管理員權限。
濫用模擬是Windows安全模型的一個已知問題(您能夠經過搜索令牌綁架找到更多詳細信息)。微軟已經試圖讓得到特權用戶訪問令牌變得更加困難,但幾乎不可能關閉全部可能門路。例如,James在Windows的DCOM實現中發現了一個漏洞,容許任何用戶訪問SYSTEM訪問令牌。雖然微軟修復了直接特權提高漏洞,可是他們沒有,也許不能修復令牌綁架問題。咱們能夠利用這個功能來捕獲SYSTEM令牌,冒充令牌,而後徹底破壞系統,好比安裝特權服務。
有一個經過DCOM(RottenPotato)令牌綁架的現有實現,可是實現被設計爲與Metasploit框架的getsystem命令一塊兒使用,但咱們並無使用msf。所以,咱們在C ++中實現了咱們本身的更簡單的版本,它使用CreateProcessWithToken API直接生成一個帶有SYSTEM標記的任意進程。咱們可以將它編譯成11KiB的可執行文件,比RottenPotato小得多,這使得它更容易放入磁盤並從ROP載荷運行。
當WPAD服務查詢PAC文件時,咱們提供利用WPAD服務的漏洞文件並運行WinExec來釋放和執行權限提高的二進制文件。這個二進制而後執行一個系統命令(在咱們的狀況下,硬編碼'CMD')。
這個漏洞在咱們的實驗中很是可靠地工做,可是有趣的是,不須要100%可靠的漏洞 - 若是漏洞使WPAD服務崩潰,當客戶端發出另外一個來自WPAD的請求時,將會產生一個新的實例服務,因此攻擊者能夠再試一次。雖然窗口錯誤報告可能會發生崩潰並將其報告給Microsoft,但用戶沒有禁用它,UI中將沒有任何跡象代表WPAD服務已經崩潰。
事實上,咱們的利用沒有很好的回收措施,一旦運行載荷就會使WPAD服務崩潰,因此若是咱們在服務被利用後繼續提供PAC文件,它將會被再次利用。你能夠在圖7中看到這個效果,這個效果是在漏洞利用服務器運行了幾分鐘而且在受害者機器上發出大量的HTTP請求以後獲得的。
稍後咱們會將漏洞利用源碼發佈在issuehacker中
執行不受信任的JavaScript代碼是危險的,而且在非沙箱環境中執行它更是如此。即便它是由相對強健的JavaScript引擎(如jscript.dll)也是如此。咱們肯定了7個安全漏洞,併成功地演示了在安裝了Fall Creator Update的Windows 10 64位徹底打補丁(撰寫本文)時從本地網絡(以及其餘地方)執行任意代碼。
因爲錯誤如今已經修復,這是否意味着咱們已經完成並能夠休息?不太可能。儘管咱們花費了至關多的時間,精力和計算能力來查找jscript.dll錯誤,可是咱們沒有聲稱咱們發現了全部這些錯誤。事實上,有第7個bug,就可能會有第8個。因此若是有什麼東西沒有改變的話,咱們頗有可能會看到這樣的一個鏈條,就像有一天在野外同樣(固然,樂觀地認爲攻擊者已經沒有這個能力了)。
那麼,微軟能夠作些什麼來使從此的攻擊變得更加困難:
默認狀況下禁用WPAD。實際上,在其餘操做系統支持WPAD的狀況下,Windows是惟一一個默認啓用的。
在WPAD服務內部提供JScript解釋器。因爲解釋器須要執行一個定義好輸入的JavaScript函數並返回輸出字符串,所以沙盒應該很是簡單。考慮到輸入輸出模型的簡單性,若是微軟在 seccomp-strict 中引入了一個相似限制性的沙箱,那將是很是好的:有些進程真的不須要比「接收一點數據」 「執行一些計算「,」返回一點數據「更多的特權。
若是您想自行採起措施,惟一可以使用新的,防止此類攻擊以及目前未知漏洞的方法彷佛是徹底禁用WinHttpAutoProxySvc服務。有時候,因爲其餘服務依賴於WPAD,因此在「服務」UI中沒法完成(「啓動類型」控件將變灰),但能夠經過相應的註冊表項來完成。在「HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinHttpAutoProxySvc」下,將「Start的值從3(手動)更改成4(禁用)。
如下是在搜索「禁用WPAD」時一般在網上找到的一些建議,這些建議在咱們的實驗中沒法防止攻擊:
在控制面板中關閉「自動檢測設置」
設置「WpadOverride」註冊表項
將「255.255.255.255 wpad」放在主機文件中(這將中止DNS變體,但可能不是DHCP變體)