How do you protect your infrastructure devices from unauthorized virtual terminal connections? Arguably the most common configuration is a simple inbound ACL applied to the VTY lines or control plane, something like the following:
ip access-list standard PROTECT_VTY permit 10.0.0.0 0.0.0.255 ! line vty 0 15 access-class PROTECT_VTY in
While this will prevent hosts outside of the specified range(s) from connecting, it unfortunately does nothing to protect against one of the most trivial (D)DoS attacks: the TCP SYN flood.
A TCP SYN flood attack is accomplished by transmitting an excess of TCP SYN packets to a host in order to exhaust its incoming TCP connection queue. For each valid received SYN, a host (for the sake of this scenario, a router) must record each half-open connection in a temporary queue in anticipation of the following ACK when the TCP handshake is completed. Unfortunately, the size of this queue on most TCP/IP stack implementations is relatively small and easily exhausted; IOS 12.4T, for example, can handle only 20 half-open inbound connections to its VTY daemons.
When the victim's TCP connection queue is full, no new connections will be accepted, from authorized hosts or otherwise. So long as the attack continues, administrators will be unable to initiate new in-band terminal connections to their affected devices. To make matters worse, only empty TCP packets are needed to perform a SYN flood; a single malicious or infected host can easily sustain an attack against hundreds or thousands of victims across a local network.
To better understand how a TCP SYN flood can impair in-band network management, we can launch a simple attack against the router in our configuration example above. We can use a raw packet generator like scapy to generate TCP SYN packets at a constant rate, sourced from random spoofed addresses within the allowed management network (10.0.0.0/24):
>>> send(IP(src=RandIP('10.0.0.0/24'), dst='192.168.0.1')/TCP(sport=RandShort(), dport=22), loop=1)
NOTE: Issuing this command in a live environment is potentially a resume-generating event. Do not attempt this attack on a production network without authorization.
Appending the loop=1 parameter to scapy's send() command instructs it to regenerate and retransmit the packet at a constant rate indefinitely. On the victim router, we can see that the TCP connection queue is immediately exhausted:
R1# show control-plane host open-ports Active internet connections (servers and established) Prot Local Address Foreign Address Service State tcp *:22 10.0.0.209:21704 SSH-Server ESTABLIS tcp *:22 10.0.0.184:4075 SSH-Server ESTABLIS tcp *:22 10.0.0.204:45665 SSH-Server ESTABLIS tcp *:22 *:0 SSH-Server LISTEN tcp *:23 *:0 Telnet LISTEN tcp *:22 10.0.0.68:26559 SSH-Server ESTABLIS tcp *:22 10.0.0.0:40397 SSH-Server ESTABLIS tcp *:22 10.0.0.223:65093 SSH-Server ESTABLIS tcp *:22 10.0.0.4:24615 SSH-Server ESTABLIS tcp *:22 10.0.0.158:30095 SSH-Server ESTABLIS tcp *:22 10.0.0.246:35431 SSH-Server ESTABLIS tcp *:22 10.0.0.67:5708 SSH-Server ESTABLIS tcp *:22 10.0.0.18:23424 SSH-Server ESTABLIS tcp *:22 10.0.0.132:24587 SSH-Server ESTABLIS tcp *:22 10.0.0.26:15867 SSH-Server ESTABLIS tcp *:22 10.0.0.53:28094 SSH-Server ESTABLIS tcp *:22 10.0.0.243:26208 SSH-Server ESTABLIS tcp *:22 10.0.0.106:22409 SSH-Server ESTABLIS tcp *:22 10.0.0.50:23524 SSH-Server ESTABLIS tcp *:22 10.0.0.173:4664 SSH-Server ESTABLIS tcp *:22 10.0.0.67:19703 SSH-Server ESTABLIS tcp *:22 10.0.0.12:24496 SSH-Server ESTABLIS
Note that although each session is listed with a state of "established," the router has not received the final ACK from the spoof sourced, and it never will. Each of these connections will remain in memory for 30 seconds (this time will differ among stack implementations) until it expires. Assuming a constant, short-interval flood, the expired connection is almost sure to be immediately replaced with a new false connection before a valid connection can be attempted.
Obviously, there are two caveats to this attack: first, the attacker must know an authorized management subnet from which to spoof the SYN packets. Second, remember that the router is sending SYN/ACK replies to each of the sessions it accepts in it queue; if the spoofed source belongs to a legitimate, reachable machine, that machine will (or should) send a RST back to the router. When this happens, the corresponding half-open session will be immediately purged from the queue. Of course, a crafty attacker can easily get around both of these limitations, as typically only a single open authorized IP address is needed.
SYN flooding is certainly nothing new or complex, yet many networks are left vulnerable to the attack despite the relative ease of mitigation. As in mitigating many denial of service attacks, the idea is simply to prevent spoofed packets from reaching the target. On an enterprise network, this can generally be accomplished in one of two ways at the access edge:
- deny user traffic destined for infrastructure devices, or;
- allow only traffic from valid user subnets.
As it's preferable to deny by default and enforce a white list, most engineers will find the second option more appealing. Plus, it's easy to accomplish on IOS using uRPF:
R1(config)# interface f0/0 R1(config-if)# ip verify unicast source reachable-via rx
Applied to an access interface (an interface acting as the first-hop for a host subnet), the router will now accept only packets sourced from that interface's assigned subnet(s). This source filtering impedes not only the SYN flood against infrastructure devices demonstrated here, but any attack which relies on IP source spoofing.