如何發現 NTP 放大攻擊漏洞

wyzsk發表於2020-08-19
作者: 心傷的胖子 · 2015/01/21 11:38

原文:http://jamesdotcom.com/?p=578

NTP 漏洞相關的文章在 Drops 已經有過了,並且不止一篇,之所以又翻譯了這一片文章,是覺得文章的整體思路很不錯,希望對看這篇文章的你有所幫助。

BTW:本文翻譯比較隨意,但是並沒有破壞原文含義。

0x00 簡介


NTP 放大攻擊其實就是 DDoS 的一種。透過 NTP 伺服器,可以把很小的請求變成很大的響應,這些響應可以直接指向到受害者的電腦。

NTP 放大使用的是 MONLIST 命令。MONLIST 命令會讓 NTP 伺服器返回使用 NTP 服務的最後 600 個 客戶端 IP。透過一個有偽造源地址的 NTP 請求,NTP 伺服器會將響應返回給那個偽造的 IP 地址。你可以想象,如果我們偽造受害者的 IP 對大量的 NTP 伺服器傳送 MONLIST 請求,這將形成 DOS 攻擊。

顯然我們不能容忍這樣做,但我比較有興趣的是去發現有多少 NTP 伺服器能夠發大這種資料。他不是什麼新的攻擊,所以你希望不會有太多的 NTP 伺服器支援 MONLIST 命令。

0x01 如何去做


為了確定有多少 NTP 伺服器響應 MONLIST 請求,我會透過兩個獨立的部分去做。

第一部分

在第一部分,透過 masscan 工具,對 UDP 的 123 埠進行掃描,掃描結果儲存到 ntp.xml 檔案中,命令如下:

./masscan -pU:123 -oX ntp.xml --rate 160000 101.0.0.0-120.0.0.0

由於我的伺服器頻寬比較小,如果選擇全網掃描,肯定會較慢,所以我隨機的選擇了一個 IP 段:101.0.0.0-120.0.0.0。

掃描完成後,會把 UDP 123 埠開放的裝置儲存在 XML 檔案中。不知道什麼原因,我的掃描結果 xml 檔案中包含了許多重複的記錄,我寫了一個 python 指令碼用於處理這些重複的記錄,去重後的結果會儲存到 port123.txt 檔案中。

程式碼如下:

from lxml import etree
port = None
address = None
parsedServers = []
#Opens the file used to store single enteries.
outputFile = open('port123.txt', 'a')
#Iterates through the masscan XML file.
for event, element in etree.iterparse('ntp.xml', tag="host"):
    for child in element:
        if child.tag == 'address':
            #Assigns the current iterations address to the address variable.
            address = child.attrib['addr']
        if child.tag == 'ports':
            for a in child:
                #Assigns the current iterations port to the port variable.
                port = a.attrib['portid']
        #is both port and IP address are present.
        if port > 1 and address > 1:
            #If the IP hasnt yet been added to the output file.
            if address not in parsedServers:
                print address
                #Write the IP address to the file.
                outputFile.write(address + '\n')
                #write the IP to the parsedServers list
                parsedServers.append(address)
            port = None
            address = None
    element.clear()
outputFile.close()
print 'End'

這個指令碼執行後,port123.txt 檔案中包含開放 UDP 123 埠並且去重後的所有 IP。

第二部分

在第二部分中我們主要來確定 port123.txt 中的 IP 的 123 埠是否執行 NTP 服務,如果是 NTP 服務,是否響應 MONLIST 請求。

我寫了一個 python 指令碼來實現上面的需求,主要用到 scapy 庫。

首先我匯入我指令碼需要的所有庫,並且定義一些變數:

from scapy.all import *
import thread

然後我構造了發給 NTP 伺服器的 MONLIST 請求的原始資料。在這個過程中我發現請求的資料必須達到一定的值伺服器才會返回資料,具體原因不清楚。只要請求超過 60 位元組,伺服器就會返回資料,因此我下面的程式碼中有 61 個\x00 字元。

rawData = "\x17\x00\x03\x2a" + "\x00" * 61

在 python 指令碼中我開啟了兩個檔案:port123.txt 是 masscan 發現的開放 UDP 123 埠的 IP 地址,monlistServers.txt 是用於儲存支援 MONLIST 命令的 NTP 伺服器。

logfile = open('port123.txt', 'r')
outputFile = open('monlistServers.txt', 'a')

然後我定義了一個叫 sniffer 的函式,這個函式的作用主要就是監聽在 48769 埠上的 UDP 資料,這個埠是傳送 MONLIST 請求的源埠,只要任何 NTP 伺服器響應 MONLIST 請求,都將響應到這個埠上。目標網路地址是你的 IP 地址,NTP 伺服器的響應將返回到這個 IP 上,在本文中,我講設定這個 IP 為:99.99.99.99。

def sniffer():
    sniffedPacket = sniff(filter="udp port 48769 and dst net 99.99.99.99", store=0, prn=analyser)

任何符合 UDP 埠 48769 的資料包都會被捕獲到,並且會放到 analyser 函式中,稍後我講介紹 analyser 函式。

sniffer 定義好了,並且會線上程中執行,同時會放到後臺執行。

thread.start_new_thread(sniffer, ())

接下來,我遍歷 masscan 發現的所有 IP 地址。對於每個 IP 地址我都會傳送一個源埠為 48769,目的埠是 123 的 UDP 資料包,資料包就是我們前面構造的 rawData。實際上這個就是對所有的 IP 傳送 MONLIST 請求。

for address in logfile:
    send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))

只要有 NTP 伺服器響應 MONLIST 請求,這個響應資料將會被執行線上程中 sniffer 抓取,sniffer 會把所有接收到的資料放到 analyser 函式中處理,而 analyser 函式會檢查捕獲到的資料包,並且確定包的大小超過 200 位元組。在實際的測試中我發現,如果 NTP 伺服器不響應 MONLIST 請求,響應包的大小通常在 60-90 位元組,或者不存在響應包。如果 NTP 伺服器響應 MONLIST 請求,響應包就會比較大,一般包含多個響應包,通常每個包為 480 位元組。所以只要檢查到所接收的響應包是大於 200 位元組就表示該 NTP 伺服器支援 MONLIST 請求。最後我們會把響應包大約 200 位元組的 IP 地址寫入到 outputFile。

    if len(packet) > 200:
        if packet.haslayer(IP):
            outputFile.write(packet.getlayer(IP).src + '\n')

通常如果 NTP 伺服器支援 MONLIST 請求,那麼它將會返回多個資料包用於包含使用 NTP 服務的 IP 地址。因為 sniffer 會捕捉所有符合條件的資料包,所以 outputFile 檔案中將會有許多重複的資料。我透過 sort 和 uniq 命令來對 outputFile 檔案進行去重。

sort monlistServers.txt | uniq

這個結果檔案中包含所有支援 MONLIST 命令的 NTP 伺服器。

完整的 python 指令碼如下:

from scapy.all import *
import thread
#Raw packet data used to request Monlist from NTP server
rawData = "\x17\x00\x03\x2a" + "\x00" * 61
#File containing all IP addresses with NTP port open.
logfile = open('output.txt', 'r')
#Output file used to store all monlist enabled servers
outputFile = open('monlistServers.txt', 'a')
def sniffer():
    #Sniffs incomming network traffic on UDP port 48769, all packets meeting thease requirements run through the analyser function.
    sniffedPacket = sniff(filter="udp port 48769 and dst net 99.99.99.99", store=0, prn=analyser)

def analyser(packet):
    #If the server responds to the GET_MONLIST command.
    if len(packet) > 200:
        if packet.haslayer(IP):
            print packet.getlayer(IP).src
            #Outputs the IP address to a log file.
            outputFile.write(packet.getlayer(IP).src + '\n')

thread.start_new_thread(sniffer, ())

for address in logfile:
    #Creates a UDP packet with NTP port 123 as the destination and the MON_GETLIST payload.
    send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
print 'End'

0x02 最後


正如我前面所提到的,我的頻寬實在是太小了,所以我只能夠選擇一個 IP 段:101.0.0.0-120.0.0.0。如果我的數學不是體育老師教的話,那麼我應該不會算錯,這個 IP 段內包含 318,767,104 個 IP 地址(19256256)。

masscan 發現 253,994 個裝置開放了 UDP 的 123 埠,佔了掃描 IP 的 0.08%。

在 253,994 個裝置中,支援 MONLIST 命令的裝置有 7005 個,佔比為 2.76%。

如果按照這個比例進行換算的話,那個整個網際網路上將有 91,000 臺開啟 MONLIST 功能的 NTP 伺服器。

over!

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

相關文章