NTP Amplification Discovery

Introduction

NTP amplification attacks are a form of DDOS which use NTP servers to turn small requests into large responses which can be directed to the victims computer.

NTP amplification makes use of the MONLIST command. The MONLIST command directs the NTP server to respond with the last 600 IP addresses which used the server. By spoofing the source IP address of a MONLIST request, it will cause the NTP server to respond with the data to the spoofed IP address. You can imagine that with a large selection of NTP servers. If all were sent the same MONLIST request with the victims IP spoofed as the source address,  it could be used as a method of DOS.

I obviously don’t condone doing this but i though it would be interesting to find how many NTP servers allow for this amplification of data. It is not by any means a new form of attack, so you would hope that many NTP servers dont respond to the MONLIST command.

How it works

To determine how many NTP servers respond to the MONLIST command i go through 2 seperate stages.

Stage 1

The first stage is to perform an initial scan on UDP port 123 (NTP port). I did this with the masscan tool using the following command:

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

My server only has very little bandwidth and therefore quite slow to scan. I have chosen to perform the test only on the 101.0.0.0-120.0.0.0 address range because of this. The range was simply chosen at random.

Once the scan has completed I’m left with an XML file which contains all the devices which were scanned which had port 123 open. For some reason, my masscan output file contained lots of duplicate entries for the same IP addresses. sometimes 100’s of records for each address. I created a simple python script capable of parsing large masscan output files and removes out all the duplicate entries. The script stores the single entries into a file called port123.txt.

The script i used to do this can be found below:

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'

So once this script has run i i have an port123.txt file containing all IP addresses with port 123 open (With no duplicates).

Stage 2

The second stage is to parse the port123.txt file and determine if NTP is running on the port, and if it is, does it respond to the MONLIST command.

To achieve this i write a small python script which makes use of scapy.

I first import all the library’s ill need for the script and initialise some variables.

from scapy.all import *
import thread

I then  create the raw MONLIST request data string which will be sent to the NTP server. I’m not sure why but the servers wouldn’t respond back with data unless the request was of a certain size. I found anything over 60 bytes works ok. So the \x00 has been appended to the end 61 times.

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

I then open both the output file used for writing the NTP servers which respond to a MONLIST request and the port123.txt file used for parsing the addresses found by masscan.

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

I then define a function called sniffer. This function listens for incoming UDP data on port 48769. This port is the source port used when sending monlist requests. So any NTP server responding to a monlist request will respond on that port. the dst net address should by your IP address which the NTP server will respond back to. In  this example i have set it 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 packets which the sniffer picks up which meet the UDP port 48769 requirements will be run though a function called analyser which i describe shortly.

Now the sniffer function is defined it is executed within a thread. This allows it to run in the background.

thread.start_new_thread(sniffer, ())

Next i iterate through the IP addresses found by masscan. For each address i send a UDP packet to port 123 from the destination port 48769 with the rawData string defined at the beginning of the script. So this loop essentially is spewing out MON_GETLIST requests to all the servers found by masscan.

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

Any devices wich respond to the MON_GETLIST request will send data back which will be picked up by the sniffer running in the thread. This sniffer runs all received packets through the analyser function. The analyser function checks the length of the captured packet to ensure it is over 200 bytes in size. I found during my testing that if the NTP server doesn’t support the MONLIST command, the response is usually 60, 90 bytes in size, or non existent. If the server does respond to the MONLIST command however, the response is much larger, and consists of multiple packets. Usually around 480 byte per packet. So by checking if the received packet is larger than 200 bytes indicates that the MONLIST command works on the particular NTP server. This is obviously the case as one of the defining factors of using NTP amplification in this way is that the responses are large in size. If the response is greater than 200 bytes the IP address is written to the outputFile.

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

It is quite common when a server responds to a monlist command that it returns multiple packets containing all the IP address data. (Depending on how many addresses it returns.) Because of this the outputFile often contains many duplicate addresses. as the sniffer captures all returned data. For this reason i run the output file through a sort and uniq to first group identical addresses and then remove duplicates using the following command:

sort monlistServers.txt | uniq

The result is a file containing all monlist enabled NTP servers.

The complete script as previously described can be found below:

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'

Results

As i mentioned previously, my bandwidth on my VPS is poor. So i have chosen to only perform the scan on the address range 101.0.0.0-120.0.0.0. If my maths is correct this is a total of 318,767,104 addresses (19 * 256 * 256 * 256).

The masscan found 253,994 devices with NTP port 123 open. This is 0.08% of the devices scanned.

Of the 253,994 devices, 7005 of them responded to the monlist command (2.76%).

If these values are equivalent to what would be found on a whole scan of the internet for monlist enabled NTP servers, it would mean that there are around 91,000 monlist enabled NTP servers.

 

 

 

 

One thought on “NTP Amplification Discovery

  1. Mads Jensen

    As i far as i know the minimum packet size is 48 bytes. Thanks for the explanation though, it helped a ton.

     

Leave a Reply

Your email address will not be published. Required fields are marked *