如何發(fā)現(xiàn)NTP放大攻擊漏洞
NTP漏洞相關(guān)的文章在Drops 已經(jīng)有過了,并且不止一篇,之所以又翻譯了這一片文章,是覺得文章的整體思路很不錯,希望對看這篇文章的你有所幫助。
0x00 簡介
NTP放大攻擊其實(shí)就是DDoS的一種。通過NTP服務(wù)器,可以把很小的請求變成很大的響應(yīng),這些響應(yīng)可以直接指向到受害者的電腦。
NTP放大使用的是MONLIST 命令。MONLIST 命令會讓 NTP 服務(wù)器返回使用 NTP 服務(wù)的最后600 個客戶端IP。通過一個有偽造源地址的NTP請求,NTP 服務(wù)器會將響應(yīng)返回給那個偽造的 IP 地址。你可以想象,如果我們偽造受害者的 IP 對大量的NTP服務(wù)器發(fā)送MONLIST請求,這將形成DOS攻擊。
顯然我們不能容忍這樣做,但我比較有興趣的是去發(fā)現(xiàn)有多少 NTP 服務(wù)器能夠發(fā)大這種數(shù)據(jù)。他不是什么新的攻擊,所以你希望不會有太多的NTP服務(wù)器支持MONLIST命令。
0x01 如何去做
為了確定有多少NTP服務(wù)器響應(yīng)MONLIST請求,我會通過兩個獨(dú)立的部分去做。
第一部分
在第一部分,通過masscan工具,對UDP的123 端口進(jìn)行掃描,掃描結(jié)果保存到 ntp.xml 文件中,命令如下:
./masscan -pU:123 -oX ntp.xml --rate 160000 101.0.0.0-120.0.0.0
由于我的服務(wù)器帶寬比較小,如果選擇全網(wǎng)掃描,肯定會較慢,所以我隨機(jī)的選擇了一個 IP 段:101.0.0.0-120.0.0.0。
掃描完成后,會把 UDP 123 端口開放的設(shè)備保存在 XML 文件中。不知道什么原因,我的掃描結(jié)果 xml 文件中包含了許多重復(fù)的記錄,我寫了一個 python 腳本用于處理這些重復(fù)的記錄,去重后的結(jié)果會保存到 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'
這個腳本運(yùn)行后,port123.txt 文件中包含開放 UDP 123 端口并且去重后的所有 IP。
第二部分
在第二部分中我們主要來確定port123.txt 中的IP的123端口是否運(yùn)行NTP服務(wù),如果是NTP 服務(wù),是否響應(yīng)MONLIST請求。
我寫了一個 python 腳本來實(shí)現(xiàn)上面的需求,主要用到 scapy 庫。
首先我導(dǎo)入我腳本需要的所有庫,并且定義一些變量:
- from scapy.all import *
- import thread
然后我構(gòu)造了發(fā)給 NTP 服務(wù)器的 MONLIST 請求的原始數(shù)據(jù)。在這個過程中我發(fā)現(xiàn)請求的數(shù)據(jù)必須達(dá)到一定的值服務(wù)器才會返回?cái)?shù)據(jù),具體原因不清楚。只要請求超過 60 字節(jié),服務(wù)器就會返回?cái)?shù)據(jù),因此我下面的代碼中有 61 個\x00 字符。
- rawData = "\x17\x00\x03\x2a" + "\x00" * 61
在 python 腳本中我打開了兩個文件:port123.txt 是 masscan 發(fā)現(xiàn)的開放 UDP 123 端口的 IP 地址,monlistServers.txt 是用于保存支持 MONLIST 命令的 NTP 服務(wù)器。
- logfile = open('port123.txt', 'r')
- outputFile = open('monlistServers.txt', 'a')
然后我定義了一個叫 sniffer 的函數(shù),這個函數(shù)的作用主要就是監(jiān)聽在 48769 端口上的 UDP 數(shù)據(jù),這個端口是發(fā)送 MONLIST 請求的源端口,只要任何 NTP 服務(wù)器響應(yīng) MONLIST 請求,都將響應(yīng)到這個端口上。目標(biāo)網(wǎng)絡(luò)地址是你的 IP 地址,NTP 服務(wù)器的響應(yīng)將返回到這個 IP 上,在本文中,我講設(shè)置這個 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 的數(shù)據(jù)包都會被捕獲到,并且會放到 analyser 函數(shù)中,稍后我講介紹 analyser 函數(shù)。
sniffer 定義好了,并且會在線程中執(zhí)行,同時會放到后臺運(yùn)行。
- thread.start_new_thread(sniffer, ())
接下來,我遍歷 masscan 發(fā)現(xiàn)的所有 IP 地址。對于每個 IP 地址我都會發(fā)送一個源端口為 48769,目的端口是 123 的 UDP 數(shù)據(jù)包,數(shù)據(jù)包就是我們前面構(gòu)造的 rawData。實(shí)際上這個就是對所有的 IP 發(fā)送 MONLIST 請求。
- for address in logfile:
- send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
只要有 NTP 服務(wù)器響應(yīng) MONLIST 請求,這個響應(yīng)數(shù)據(jù)將會被運(yùn)行在線程中 sniffer 抓取,sniffer 會把所有接收到的數(shù)據(jù)放到 analyser 函數(shù)中處理,而 analyser 函數(shù)會檢查捕獲到的數(shù)據(jù)包,并且確定包的大小超過 200 字節(jié)。在實(shí)際的測試中我發(fā)現(xiàn),如果 NTP 服務(wù)器不響應(yīng) MONLIST 請求,響應(yīng)包的大小通常在 60-90 字節(jié),或者不存在響應(yīng)包。如果 NTP 服務(wù)器響應(yīng) MONLIST 請求,響應(yīng)包就會比較大,一般包含多個響應(yīng)包,通常每個包為 480 字節(jié)。所以只要檢查到所接收的響應(yīng)包是大于 200 字節(jié)就表示該 NTP 服務(wù)器支持 MONLIST 請求。最后我們會把響應(yīng)包大約 200 字節(jié)的 IP 地址寫入到 outputFile。
- if len(packet) > 200:
- if packet.haslayer(IP):
- outputFile.write(packet.getlayer(IP).src + '\n')
通常如果 NTP 服務(wù)器支持 MONLIST 請求,那么它將會返回多個數(shù)據(jù)包用于包含使用 NTP 服務(wù)的 IP 地址。因?yàn)?sniffer 會捕捉所有符合條件的數(shù)據(jù)包,所以 outputFile 文件中將會有許多重復(fù)的數(shù)據(jù)。我通過 sort 和 uniq 命令來對 outputFile 文件進(jìn)行去重。
- sort monlistServers.txt | uniq
這個結(jié)果文件中包含所有支持 MONLIST 命令的 NTP 服務(wù)器。
完整的 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 最后
正如我前面所提到的,我的帶寬實(shí)在是太小了,所以我只能夠選擇一個 IP 段:101.0.0.0-120.0.0.0。如果我的數(shù)學(xué)不是體育老師教的話,那么我應(yīng)該不會算錯,這個 IP 段內(nèi)包含 318,767,104 個 IP 地址(19256256)。
masscan 發(fā)現(xiàn) 253,994 個設(shè)備開放了 UDP 的 123 端口,占了掃描 IP 的 0.08%。
在 253,994 個設(shè)備中,支持 MONLIST 命令的設(shè)備有 7005 個,占比為 2.76%。
如果按照這個比例進(jìn)行換算的話,那個整個互聯(lián)網(wǎng)上將有 91,000 臺開啟 MONLIST 功能的 NTP 服務(wù)器。