A vulnerability in TCP, the transmission control protocol, recently received some exposure in the media. Paul Watson released a white paper titled Slipping In The window: TCP Reset Attacks at the 2004 CanSecWest conference, providing a much better understanding of the real-world risks of TCP reset attacks.
To better understand the reality of this threat, KernelTrap spoke with Theo de Raadt [interview], the creator of OpenBSD, an operating system which among other goals proactively focuses on security. In this article, we aim to provide some background into the workings of TCP, and then to build upon this foundation to understand how resets attacks work.
This is the first article in a two part series. The second article will look into how TCP stacks can be hardened to defend against such attacks. Toward this goal, we spoke with members of the OpenBSD team to learn what they have done so far, and what further plans they have to minimize the impact of reset attacks.
Transmission Control Protocol overview
The first section of this article aims at providing some basic information about TCP. We intentionally gloss over much detail, focusing primarily on that which is relevant to understanding TCP reset attacks. If you already have a good understanding of TCP, you may want to skip directly to the section on how reset attacks work. Please realize that as we only focus on the aspects of TCP necessary to understand reset attacks, if you don't already have a good understanding of TCP, don't expect to be an expert after reading this article.
TCP is an abbreviation for the Transmission Control Protocol, defined in RFC 793 which was released in September of 1981. TCP is a connection oriented protocol that can reliably get information from one host to another across a network. By reliable, we mean that TCP guarantees all data will arrive uncorrupted at the remote host, automatically detecting dropped or corrupted packets and resending them as needed.
Every TCP packet includes a header, which is defined by the RFC as follows:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |U|A|P|R|S|F| | | Offset| Reserved |R|C|S|S|Y|I| Window | | | |G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Programs utilize TCP by passing it buffers of data. TCP breaks this data into packages known as segments, and then uses IP to further package these segments into datagrams. Finally, the datagrams are embedded into a network packet which can be routed across a network.
When the packet arrives at its destination, the IP stack on the remote host extracts the datagram from the packet, then the segment from the datagram. The segment is then passed up to the TCP stack, where it can be validated. Ultimately the TCP stack can reassemble all the segments into the complete buffer which is then passed to the application. TCP provides two way communication, so this same process occurs in both directions.
With data having been broken into segments which are then routed as separate packets over a network, it is quite possible for packets to arrive at their destination out of order. A field in the TCP header provides for a 32-bit sequence number, a value that starts at an arbitrary integer then increments sequentially with each transmitted packet. Using these sequence numbers, the receiving TCP stack is able to properly reorder the received segments.
TCP also provides a mechanism for hosts to tell each other how much data they want to receive at a time. This is known as a 'window', defined by a 16-bit field in the TCP header. Once defined, a host will generally only receive data that is "within" its specified window, dropping anything else. Being within the window means that the sequence number of the packet is a number that is within the range of data that one end of the connection told the other end it was willing to receive at any one point in time. If a receive queue gets full, the host can declare a window of size 0, telling the other endpoint not to send anything and giving the backed up host a chance to catch up.
There are six 'control bits' defined in TCP, one or more of which is defined in each packet. The control bits are 'SYN', 'ACK', 'PSH', 'URG', 'RST', and 'FIN'. TCP uses these bits to define the purpose and contents of a packet. For the purposes of this article, we are primarily only interested with the SYN, ACK and RST flags, though we will briefly define them all.
The SYN bit is used in establishing a TCP connection to synchronize the sequence numbers between both endpoints. The ACK bit is used to acknowledge the remote host's sequence numbers, declaring that the information in the acknowledgment field is valid. The PSH flag is set on the sending side, and tells the TCP stack to flush all buffers and send any outstanding data up to and including the data that had the PSH flag set. When the receiving TCP sees the PSH flag, it too must flush its buffers and pass the information up to the application. The URG bit indicates that the urgent pointer field has a valid pointer to data that should be treated urgently and be transmitted before non-urgent data. The RST bit tells the receiving TCP stack to immediately abort the connection. And the FIN bit is used to indicate that the client will send no more data (but will continue to listen for data).
For example, a TCP connection is usually initiated with a three-way handshake. That is, host A will send a packet to host B, setting only the SYN control bit, and putting a randomly generated number into the sequence number field of the packet's TCP header. This randomly generated sequence number is the ISN, or initial sequence number.
Host B receives the packet with only the SYN bit sent, and therefor knows that host A is trying to establish a connection, currently trying to synchronize sequence numbers. Host B therefor replies by sending a packet back to host A, setting both the SYN and ACK bits. Host B generates his own random sequence number and puts it into the sequence number field of the packet's TCP header. He also puts Host A's ISN +1 into the acknowledgment field, indicating the next sequence number expected from Host A.
Host A receives this packet with both the SYN and the ACK bits set. It verifies that the number in the acknowledgment field is correct, then generates a third packet, this time with only the ACK control bit set. In the acknowledgment field of this third packet goes the sequence number +1 from the previous packet, acknowledging to Host B that the SYN ACK packet was received and indicating which sequence number is next expected. This done, the three way handshake is complete and a TCP connection is now established between host A and host B. At this point, data can be transmitted in both directions between the hosts.
The three way handshake we've described is the most common mechanism for opening a TCP connection. However, the RFC does not require that communication begin this way, and therefor it is perfectly legal to add in other control bits during the initiation of communication. OpenBSD creator Theo de Raadt notes that many of the problems with TCP have originated from the fact that the RFC really divides the protocol into two separate parts, the first being connection establishment and breakdown, and the second being data flow. Theo explains:
"This has resulted in weird stuff like SYN|ACK|FIN exchanges being possible... some people will say it is wrong, but it is explicitly left open in the RFCs.. nothing says you can't attempt to move data in your first packet."
In addition to allowing hosts to carry on bidirectional communication within one connection, TCP also allows hosts to maintain more than one TCP connection at a time. This is done using ports. A port is a unique integer from 1 through 65,535. All TCP connections originate on a "source port" within the above range, and terminate on a "destination port" within the same range.
For most common TCP connections, the destination port is a well known established value. For example, a web browser usually connects to destination port 80, a mail client sending email connect to destination port 25, and an ssh client connects to destination port 22. The reason these clients connect to specific ports is that their respective servers are known to be listening on those ports. The complete list of officially assigned port numbers is maintained by IANA.
The source of TCP connections, however, can usually originate from any port, though certain lower port numbers are reserved and unavailable for use as a source port. (In particular, ports 1-1024 are reserved for well known and privileged processes.) There can only be one outgoing TCP connection from each port at a time, allowing the TCP server to differentiate between multiple TCP connections from the same host.
When a TCP connection is made, the combination of the source port and IP address and the destination port and IP address results in a unique fingerprint that can be used to differentiate between all active TCP connections.
With this minimal understanding of how TCP works, it is now possible to understanding what reset attacks are and how they work.
TCP reset attacks
The primary idea behind a TCP reset attack is to falsely terminate an established TCP connection. Lets imagine an established TCP connection from host A to host B. Now, a third host, C, spoofs a packet that matches the source port and IP address of host A, the destination port and IP address of host B, and the current sequence number of the active TCP connection between host A and host B. Host C sets the RST bit on the spoofed packet, so when received by host B, host B immediately terminates the connection. This results in a denial of service, until the connection can be reestablished. However, the severity of such an attack is different from application to application.
Applications and protocols that require lengthy sustained connections are most vulnerable. Perhaps the most affected is Cisco's BGP, or Border Gateway Protocol, the latest version of which is defined in RFC 1771. BGP is so vulnerable as it relies on a persistent TCP session being maintained between peers. If the connection gets terminated, it then takes time to rebuild routing tables and remote hosts may perform "route flapping". A route flap is any routing change that results in a BGP table being changed. A router that is constantly loosing its connection and causing route flapping on other BGP routers will eventually suffer route dampening, or suppression. As BGP is used on large border routers, this could have a significant impact on a large number of users.
As described earlier in this article, TCP utilizes sequence numbers to determine whether or not a packet with valid endpoints (IP addresses and ports) applies to the current active session. This both allows TCP to both properly reassemble even out of order packets into the original data buffer, as well as to ignore potentially spoofed packets.
Slipping in the window:
In Paul Watson's recently released technical white paper, Slipping in the Window: TCP Reset Attacks, (currently only available in doc format) he explains that TCP connections are more susceptible to this type of attack than was previously thought. Specifically, in previous calculations it was assumed that a brute force attack would have to try every sequence number from 1 up to the maximum of 4,294,967,295. In Paul's paper, however, he points out that the number of required attempts is significantly less than this, due to TCP windows.
For example, if the TCP stack on host A has defined a 16K window, the stack must accept any packet that has a sequence number that falls within this range, as the packets may be arriving out of order. Hence, someone that is performing a TCP reset attack doesn't have to send a RST packet with every possible sequence number, instead only having to send a RST packet with a sequence number from each possible window. In other words, still assuming a 16K window size an attacker would have to send 4,294,967,295 / 16,384, or 262,143 packets to exhaust all possible windows.
262,143 may sound like a large number, though when considering a RST attack it is possible that an attacker will be able to generate tens of thousands of packets per second, depending on their bandwidth. What more, an attack could be distributed among many hosts, thus being an aggregation of all their bandwidths. However, assuming the attacker has a single DSL at their disposal, according to Paul's paper they will be able to generate about 250 packets per second. Thus, a single DSL would be able to exhaust all possible windows in just over 17 minutes. However, with a T1 at 4,370 packets a second, the attacker would be able to exhaust all possible windows within only 60 seconds.
Our example assumed a 16K window size, however the TCP RFC provides a 16 bit field for the window, allowing it to be as large as 64K. If the full window size is utilized, an attacker would only have to send 4,294,967,295 divided by 65,535, or 65,537 packets. With this window size, it would take a DSL about 4 minutes, and a T1 only about 15 seconds. And remember that these times assume that you would have to try every single window before finding a match, when by the law of averages you could expect it to take closer to half that time.
In Paul's paper, he times the results of actual reset attacks. At DSL speeds against a host using 64K windows, Paul was able with brute force to on average generate a reset in within 3 minutes, however some of the attacks took only a few seconds to be successful. At T1 speeds, Paul was on average able to blindly tear down a session in less than 8 seconds. With a connection faster than a T1, the time to brute force a matching RST attack can quickly become insignificant.
At this point one may wonder why anyone would specify a large window, as it obviously decreases the security of the TCP stack. The reason is, larger window sizes generally provide higher performance. For this reason, RFC-1323 was defined in 1992 titled, 'TCP Extensions for High Performance'. Among other features, this RFC defines "window scaling", a widely supported TCP extension that effectively increases the available window size from 16 bits to 30 bits. Thus, against an application or protocol using window scaling open to the maximum range, an attacker would only have to send 4,294,967,295 divided by 1,073,741,824, or 4 packets. That's right, it would only take 4 spoofed packets to generate a matching RST packet against an application that fully utilized window scaling. Again, an example that uses window scaling is Cisco's BGP, which frequently utilizes window scaling to improve performance.
All of the above examples assume that the attacker already knows the destination port and IP address as well the the source port and IP address. The destination port and IP address are easy, as they are generally published. The source IP address is also generally easy, as this is simply the client that is being spoofed. The only piece that can frequently be difficult to find is the source port.
For example, if an operating system randomly assigns source ports from a pool that ranges from 1025 through 49,152 (such as OpenBSD), this increases the difficulty of performing a reset attack 48,127 times as the attacker would have to try their sequence attack with every possible port number. In our first example with 16k windows, we determined that with known endpoints it would require 262,143 packets to guarantee a successful reset attack. However, if using random ports as we've described, it would now require 262,143 times 48,127, or 12,616,156,161 packets. An attack of that size would all but certainly be detected and dealt with before a brute force reset would occur.
Realizing the security added by using randomized source ports, you would expect that most operating systems employ this defensive strategy. Unfortunately, according to Paul's paper, most operating systems allocate source ports sequentially, including Windows and Linux. A notable exception is OpenBSD, which began randomizing source port allocation in 1996.
A reset attack can be performed indirectly without setting the RST bit. Instead, the attacker can set the SYN bit, otherwise identically performing the brute force attack as described earlier. On most TCP stack implementations, a duplicate SYN will cause a RST to be sent in reply with the provided sequence number. If the SYN is within the window of the active matching session, in addition to sending a RST in reply, it will cause the local end of the connection to be immediately torn down. This action is by design, intended to handle system reboots during a TCP connection.
In response to the threats of reset attacks, the IETF is now recommending that when TCP stacks receive an in-window SYN packet, they should instead reply with an ACK. If the SYN was truly from the remote client attempting to re-establish communication after a reboot, then it will not expect the ACK packet and will reply with a valid (in-window) RST, causing the now stale connection to be torn down. If the SYN was instead spoofed by an attacker, the ACK will be ignored. However, OpenBSD creator Theo de Raadt points out that this may be even more problematic, as if the attacker keeps spoofing valid SYN packets the continuous flood of generated ACK packets could become a serious problem. Theo notes, "nowhere is the IETF telling anyone that this is dangerous", further explaining:
"Every SYN I spoof onto one of your sessions, you will send an ACK over your peer link. If I have 1GB to spoof to you, but your link to your peer is a T1, I can hurt your T1. We call these types of things 'reflectors'. They are bad."
For these reasons, Theo explained that SYN based reset attacks are actually more serious than RST based reset attacks. Not only can using the SYN bit tear down a connection, but based on the latest IETF recommendations it can also result in the generation of more packets, potentially flooding a connection. He added:
"Think of it this way: Some systems have SYN behaviors that can interact badly with RST behaviors on other systems."
Blind data injection:
A third related attack is called 'blind data injection', and again relies an brute force to find a matching sequence number. Essentially, instead of simply sending empty RST or SYN packets, the attacker could send data packets. The attack would commence as we've already described, except now instead of tearing down a connection, the attacker may instead corrupt the connection, invalidating the data that was being exchanged.
At this point, you should have a decent understanding of what a reset attack is, and how it works. Our goal with this article was to better understand if the threat is real, and indeed it seems to be. Asking Theo for his views on the reality of reset attacks, he replied, "lots of people are saying this is not a problem, but I am sure we will see a worm using it one day." Worms can effectively perform distributed attacks, greatly increasing the speed at which a reset attack could be conducted.
It is not possible to fully protect against a brute force TCP reset attack, but there are many things that can be done to harden TCP stacks. The TCP RFC is not a narrow definition, leaving many design desicions up to each operating system's implementation. It is perhaps because of this fact that many popular operating systems are quite susceptible to reset attacks, though there are published methods for hardening against them. Theo predicts, "fact is, critical server systems with these poor TCP behaviours will stay around for quite some time."
Choices made in the implementation of a TCP stack can significantly affect an attacker's ability to efficiently perform reset and other types of attacks. In the second part of this series, we will explore methods of protecting against reset attacks, using OpenBSD as an example. Hopefully other operating systems will follow their lead, futher hardening themselves against such attacks.