Articles related to NTP vulnerabilities have already been covered in Drops, and not just one. The reason for translating this article again is that I find the overall thought process quite good. I hope it helps you who are reading this article.
BTW: This translation is quite casual, but it does not distort the original meaning.
0x00 Introduction
NTP amplification attack is actually a type of DDoS. Through NTP servers, a very small request can be turned into a very large response, and these responses can be directed straight to the victimâs computer.
NTP amplification uses the MONLIST command. The MONLIST command makes the NTP server return the last 600 client IPs that used the NTP service. By sending an NTP request with a spoofed source address, the NTP server will return the response to that spoofed IP address. You can imagine, if we spoof the victimâs IP and send MONLIST requests to a large number of NTP servers, this will form a DOS attack.
Obviously, we cannot tolerate this, but I am more interested in finding out how many NTP servers can send such data. This is not a new attack, so you would hope that not too many NTP servers support the MONLIST command.
0x01 How to do it
To determine how many NTP servers respond to MONLIST requests, I will do it through two independent parts.
Part One
In the first part, using the masscan tool, I will scan the UDP port 123, and save the scan results to the ntp.xml file with the following command:
./masscan -pU:123 -oX ntp.xml --rate 160000 101.0.0.0-120.0.0.0
Since my server bandwidth is relatively small, if I choose to scan the entire network, it will definitely be slower, so I randomly selected an IP segment: 101.0.0.0-120.0.0.0.
After the scan is completed, the devices with UDP port 123 open will be saved in the XML file. For some reason, my scan result XML file contained many duplicate records, so I wrote a Python script to handle these duplicate records. The deduplicated results will be saved to the port123.txt file.
The code is as follows:
from lxml import etree
port = None
address = None
parsedServers = []
#Opens the file used to store single entries.
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 iteration's address to the address variable.
address = child.attrib['addr']
if child.tag == 'ports':
for a in child:
#Assigns the current iteration's port to the port variable.
port = a.attrib['portid']
#If both port and IP address are present.
if port and address:
#If the IP hasn't 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')
After running this script, the port123.txt file will contain all the deduplicated IPs with UDP port 123 open.
Part Two
In the second part, we mainly determine whether the IPs in port123.txt have NTP services running on port 123, and if so, whether they respond to MONLIST requests.
I wrote a Python script to achieve the above requirements, mainly using the scapy library.
First, I imported all the libraries needed for my script and defined some variables:
from scapy.all import *
import thread
Then I constructed the raw data for the MONLIST request to be sent to the NTP server. During this process, I found that the request data must reach a certain value for the server to return data. The specific reason is unclear. As long as the request exceeds 60 bytes, the server will return data. Therefore, there are 61 \x00
characters in the code below.
rawData = "\x17\x00\x03\x2a" + "\x00" * 61
In the Python script, I opened two files: port123.txt contains the IP addresses with UDP port 123 open found by masscan, and monlistServers.txt is used to save NTP servers that support the MONLIST command.
logfile = open('port123.txt', 'r')
outputFile = open('monlistServers.txt', 'a')
Then I defined a function called sniffer
, whose main role is to listen for UDP data on port 48769. This port is the source port for sending MONLIST requests. As long as any NTP server responds to the MONLIST request, the response will be directed to this port. The target network address is your IP address, and the NTP serverâs response will return to this IP. In this article, I will set this IP to: 99.99.99.99.
def sniffer():
sniffedPacket = sniff(filter="udp port 48769 and dst net 99.99.99.99", store=0, prn=analyser)
Any data packet that matches UDP port 48769 will be captured and passed to the analyser function, which I will introduce later.
The sniffer is defined and will be executed in a thread, running in the background.
thread.start_new_thread(sniffer, ())
Next, I iterate through all the IP addresses found by masscan. For each IP address, I will send a UDP packet with a source port of 48769 and a destination port of 123. The data packet is the rawData we constructed earlier. Essentially, this sends MONLIST requests to all IPs.
for address in logfile:
send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
As long as any NTP server responds to the MONLIST request, the response data will be captured by the sniffer running in the thread. The sniffer will pass all received data to the analyser function, which will check the captured data packets and determine if the packet size exceeds 200 bytes. In actual testing, I found that if the NTP server does not respond to the MONLIST request, the response packet size is usually between 60-90 bytes, or there is no response packet. If the NTP server responds to the MONLIST request, the response packet will be relatively large, usually containing multiple response packets, each typically 480 bytes. Therefore, as long as the received response packet is greater than 200 bytes, it indicates that the NTP server supports the MONLIST request. Finally, we will write the IP address of the response packet larger than 200 bytes to the outputFile.
if len(packet) > 200:
if packet.haslayer(IP):
outputFile.write(packet.getlayer(IP).src + '\n')
Typically, if the NTP server supports the MONLIST request, it will return multiple packets containing the IP addresses using the NTP service. Since the sniffer will capture all matching packets, the outputFile will contain many duplicate entries. I use the sort and uniq commands to deduplicate the outputFile.
sort monlistServers.txt | uniq
The result file contains all NTP servers that support the MONLIST command.
The complete Python script is as follows:
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 incoming network traffic on UDP port 48769, all packets meeting these 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 Conclusion
As I mentioned earlier, my bandwidth is really too small, so I could only choose an IP segment: 101.0.0.0-120.0.0.0. If my math teacher wasnât my PE teacher, then I shouldnât be wrong in calculating that this IP segment contains 318,767,104 IP addresses (19 256 256).
masscan found 253,994 devices with UDP port 123 open, accounting for 0.08% of the scanned IPs.
Among the 253,994 devices, 7005 devices support the MONLIST command, accounting for 2.76%.
If we convert according to this ratio, there will be 91,000 NTP servers with the MONLIST feature enabled across the entire Internet.
over!