One-liner iptables rule to Filter NTP Reflection on Linux Hypervisor

Anybody annoyed enough with massive NTP monlist floods over the weekend?
If you did like I did, I believe what immediately came to your mind was, “this shouldn’t have happened if they just had put a ‘default ignore’ line in their ntp.conf file!” But unfortunately there are some people who’re not like you, including casual VPS users in your datacenter, who just start ntpd and let it wide open. If you host these customers’ VM on Linux hypervisors, and in case you don’t know, NTP monlist DDoS from these guest machines can be filtered with just one iptables rule on the hypervisor.

First dissecting the packet, as always…

Honestly I haven’t yet tried to understand what this monlist thing is for, because all I need to know is how they look.

First 4bytes of NTP payload in request packet:

Network Time Protocol (NTP Version 2, private)
Flags: 0×17
0… …. = Response bit: Request (0)
.0.. …. = More bit: 0
..01 0… = Version number: NTP Version 2 (2)
…. .111 = Mode: reserved for private use (7)
Auth, sequence: 0
0… …. = Auth bit: 0
.000 0000 = Sequence number: 0
Implementation: XNTPD (3)
Request code: MON_GETLIST_1 (42)

First 4bytes of NTP payload in response packet:

Network Time Protocol (NTP Version 2, private)
Flags: 0xd7
1… …. = Response bit: Response (1)
.1.. …. = More bit: 1
..01 0… = Version number: NTP Version 2 (2)
…. .111 = Mode: reserved for private use (7)
Auth, sequence: 41
0… …. = Auth bit: 0
.010 1001 = Sequence number: 41
Implementation: XNTPD (3)
Request code: MON_GETLIST_1 (42)

So only the request code in the last byte, which is 0x2a, besides the obvious protocol (17) and either src or dst port (123), is enough to identify these packets. And while we know it was all response packets that were storming the Internet like crazy this past weekend, they may have most likely been initiated by request packets. But I’d rather create a rule that filters both request and response packets anyway.

U32 comes in real handy

It’s time to write an iptables rule in FORWARD chain. Let’s start with the easy part.
Now I’m going to filter both request and response packets, that means packets to be dropped are all udp(17) and either source or destination port matches 123. In this case, why not using the multiport module so we don’t have to write two seperate rules:

sudo iptables -i br0 -o br0 -A FORWARD -p 17 -m multiport --ports 123 \
-j DROP

But of course it’s not done yet… the above command is only for making your customers’ ntpd entirely useless.
We have to pinpoint the offending packets by specifying 0x2a request code. And it’s only recently that I have realized there is a really useful module called u32. For those who don’t know, may I strongly recommend you take a little break and go through this great tutorial before further reading my article…

OK, time to do some bit-shifting and masking on the packet:)

If you’ve finished the above tutorial, you already know how to move ahead to the end of the IP header.

0>>22&0x3C

Next, we skip 8 bytes of the entire udp header.

0>>22&0x3C@8

From here, you can evaluate 4 bytes, but we only need the last byte.

0>>22&0x3C@8&0xFF

We match this byte to decimal 42.

0>>22&0x3C@8&0xFF=42

Finally, the complete one-liner rule goes like this:

sudo iptables -A FORWARD -i br0 -o br0 -p 17 -m multiport --ports 123 \
-m u32 --u32 "0>>22&0x3C@8&0xFF=42" -j DROP

Time to verify

My hypervisor is 192.168.11.25, which hosts a VM at 192.168.11.26 that runs an open ntpd, and I’ll request a monlist from 192.168.11.23. Before applying the drop rule, I want to log matched packets.

On the hypervisor:

sudo iptables -F FORWARD
sudo iptables -A FORWARD -i br0 -o br0 -p 17 -m multiport --ports 123 \
-m u32 --u32 "0>>22&0x3C@8&0xFF=42" -j LOG --log-prefix="NTP MONLIST "

From 192.168.11.23:

ntpdc -nc monlist 192.168.11.26

On the hypervisor:

sudo iptables -L FORWARD -n -v

Chain FORWARD (policy ACCEPT 2 packets, 616 bytes)
pkts bytes target prot opt in out source destination
2 616 LOG udp -- br0 br0 0.0.0.0/0 0.0.0.0/0 multiport ports 123 u32 "0x0>>0x16&0x3c@0x8&0xff=0x2a" LOG flags 0 level 4 prefix "NTP MONLIST "

Both request and response packets matched!

sudo tail -2 /var/log/syslog

Feb 13 16:11:26 localhost kernel: [14551529.133043] NTP MONLIST IN=br0 OUT=br0 PHYSIN=eth0 PHYSOUT=vnet0 MAC=52:54:00:15:cb:0a:00:26:2d:03:ad:e4:08:00 SRC=192.168.11.23 DST=192.168.11.26 LEN=220 TOS=0×00 PREC=0×00 TTL=64 ID=23046 DF PROTO=UDP SPT=37436 DPT=123 LEN=200
Feb 13 16:11:26 localhost kernel: [14551529.133480] NTP MONLIST IN=br0 OUT=br0 PHYSIN=vnet0 PHYSOUT=eth0 MAC=00:26:2d:03:ad:e4:52:54:00:15:cb:0a:08:00 SRC=192.168.11.26 DST=192.168.11.23 LEN=396 TOS=0×00 PREC=0xC0 TTL=64 ID=0 DF PROTO=UDP SPT=123 DPT=37436 LEN=376

Append the drop rule:

sudo iptables -A FORWARD -i br0 -o br0 -p 17 -m multiport --ports 123 \
-m u32 --u32 "0>>22&0x3C@8&0xFF=42" -j DROP

Once again, requesting monlist from 192.168.11.23, which to receive no response:

ntpdc -nc monlist 192.168.11.26

sudo iptables -L FORWARD -n -v

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
3 836 LOG udp -- br0 br0 0.0.0.0/0 0.0.0.0/0 multiport ports 123 u32 "0x0>>0x16&0x3c@0x8&0xff=0x2a" LOG flags 0 level 4 prefix "NTP MONLIST "
1 220 DROP udp -- br0 br0 0.0.0.0/0 0.0.0.0/0 multiport ports 123 u32 "0x0>>0x16&0x3c@0x8&0xff=0x2a"

Caveat

My KVM shown in this article hosts guest machines with Linux kernel bridge. If I’m not mistaken, ovs kernel module is not compatible with iptables, therefore the above filter doesn’t work with guest machines under Open vSwitch. Also, I haven’t measured performance impacts of using the u32 module on the hypervisor.

Tamihiro Yuzawa

Tamihiro Yuzawa

Tamihiro Yuzawa is a network engineer at Sakura Internet, one of Japan's major data center service providers. Before he joined Sakura in 2007, he spent five years at a busy CRM service provider. Both companies have allowed him to stay mostly within the intersection of these circles, and he is pretty much determined to remain in a serious relationship with both Dev and Ops.
  • Mark

    Good analysis, I was looking around for other ways to block ntp monlist request using iptables. I came up with a slightly different recipe, just FYI:

    /sbin/iptables -I FORWARD -p udp –dport 123 -m string –algo bm –from 27 –to 28 –hex-string ‘|1700032A|’ -j DROP

    • tamil

      Mark, thanks for the comments.
      Only geekier people may tell how the string and u32 modules differ from each other in terms of efficiently filtering monlist requests and/or responses:) But in many other cases, I would favor u32 because of its flexibility, like when I want to do a range match. For example, we often observe another type of large-scale DDoS traffic comprising fragments of ridiculously huge UDP packets. To prevent customers’ VMs from emitting these packets, if I want to block outgoing packets in the original size of, say, 2000 bytes or larger, the following rule on the host OS would handle it.
      iptables -A FORWARD -p 17 -m physdev –physdev-in vnet0 -m u32 –u32 “0&0xFFFF=2000:65535″ -j DROP
      (Yeah from what I can tell, packets generated by guest machines are evaluated in the host machine’s FORWARD chain before they are fragmented, so I only have to check the total-length field.)