如何發現NTP放大攻擊漏洞
NTP漏洞相關的文章在Drops 已經有過了,并且不止一篇,之所以又翻譯了這一片文章,是覺得文章的整體思路很不錯,希望對看這篇文章的你有所幫助。
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 服務器。