How to detect NTP amplification Attack Vulnerability
0x00 Introduction
NTP amplification attacks are actually DDoS attacks. Through the NTP server, a small request can be converted into a large response, which can direct to the victim's computer.
NTP amplification uses the MONLIST command. The MONLIST command causes the NTP server to return the IP addresses of the last 600 clients using the NTP service. Through an NTP request with a forged source address, the NTP server returns a response to the forged IP address. As you can imagine, if we forge the victim's IP address and send a MONLIST request to a large number of NTP servers, this will form a DOS attack.
Obviously, we cannot tolerate this, but I am more interested in discovering how many NTP servers can send big data. It is not a new attack, so you don't want many NTP servers to support the MONLIST command.
0x01 how to do it
To determine how many NTP servers are responding to MONLIST requests, I will do this using two independent parts.
Part 1
In the first part, use the masscan tool to scan UDP port 123 and save the scan results to the ntp. xml file. The command is as follows:
./masscan -pU:123 -oX ntp.xml --rate 160000 101.0.0.0-120.0.0.0
I randomly selected an IP segment: 101.0.0.0-120.0.0.0.
After scanning, the devices opened on UDP port 123 are saved in the XML file. I don't know why. My scan result xml file contains many duplicate records. I wrote a python script to process these duplicate records, the deduplication result is saved to the port123.txt file.
The Code is as follows:
from lxml import etreeport = Noneaddress = NoneparsedServers = []#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'
After this script is run, the port123.txt file contains all the IP addresses that open the UDP 123 port and remove the duplicated IP addresses.
Part 2
In the second part, we mainly determine whether port 123 of the IP address in port123.txt runs the NTP service, and whether the NTP service responds to the MONLIST request.
I wrote a python script to meet the above requirements and mainly used the scapy library.
First, I import all the libraries required by my script and define some variables:
from scapy.all import *import thread
Then I constructed the raw data for the MONLIST request sent to the NTP server. In this process, I found that the request data must reach a certain value before the server will return data. The specific reason is unclear. As long as the request exceeds 60 bytes, the server will return data, so the code below contains 61 \ x00 characters.
rawData = "\x17\x00\x03\x2a" + "\x00" * 61
In the python script, I opened two files: port123.txt is the IP address of the open UDP 123 port found by masscan, and monlistservers.txt is the NTP server used to save the support for the MONLIST command.
logfile = open('port123.txt', 'r')outputFile = open('monlistServers.txt', 'a')
Then I defined a function named sniffer, which listens to UDP data on port 48769. This port is the source port that sends the MONLIST request, any NTP server responds to the MONLIST request to this port. The target network address is your IP address, and the NTP server will return a response to this IP address. In this article, I will set this IP address 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 conforming to UDP port 48769 will be captured and put into the analyser function. I will introduce the analyser function later.
Sniffer has been defined and will be executed in the thread and run in the background.
thread.start_new_thread(sniffer, ())
Next, I traverse all IP addresses found by masscan. For each IP address, I will send a UDP packet whose source port is 48769 and the destination port is 123. The packet is the rawData we constructed earlier. In fact, this is to send a MONLIST request to all IP addresses.
for address in logfile: send(IP(dst=address)/UDP(sport=48769, dport=123)/Raw(load=rawData))
As long as the NTP server responds to the MONLIST request, the response data will be captured by sniffer in the running thread, and sniffer will put all received data into the analyser function for processing, the analyser function checks the captured data packets and determines that the packet size exceeds 200 bytes. In actual tests, 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 the response packet does not exist. If the NTP server responds to the MONLIST request, the response packet will be large. Generally, it contains multiple response packets, usually 480 bytes for each packet. Therefore, if the received response packet is greater than 200 bytes, the NTP server supports MONLIST requests. At last, we will write an IP address of approximately 200 bytes in the response packet to outputFile.
if len(packet) > 200: if packet.haslayer(IP): outputFile.write(packet.getlayer(IP).src + '\n')
Generally, if the NTP server supports MONLIST requests, it will return multiple data packets to contain IP addresses using the NTP service. Because sniffer captures all qualified data packets, there will be a lot of repeated data in the outputFile file. I deduplicated the outputFile using the sort and uniq commands.
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 serverrawData = "\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 serversoutputFile = 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 last
This IP segment contains 318,767,104 IP addresses (19256256 ).
Masscan found that 253,994 devices opened UDP port 123, accounting for 0.08% of the scan IP address.
Among the 253,994 devices, there are 7005 devices that support the MONLIST command, accounting for 2.76%.
If this ratio is used, there will be 91,000 NTP servers on the Internet that have enabled the MONLIST function.