Linux 2.4/2.6 Kernel Off-by-one TCP Timestamp Issue and Potential IDS/IPS Evasion

Back in my Sourcefire days, I did a lot of research on TCP timestamp behavior. In a nutshell, TCP timestamps can be included as a TCP option to specify the sending host's timestamp and echo the most recently received timestamp from the other side of the connection. The notion of time or timestamp is not the typical one since it denotes, for most operating systems except OpenBSD, a representation of the uptime of the host since the last reboot.

My interest was understanding how general and operating-specific timestamp behavior might evade notice by an intrusion detection or prevention system. In general, a host that receives a TCP segment with a TCP timestamp must compare the current timestamp in the segment with what it considers the previous timestamp. If the timestamp is equal or greater than the previous one, it is acceptable. Otherwise, the segment should not be acknowledged. There are many different nuances involved in this process including how to deal with out-of-order segments. If you'd like to read way more than you'd ever consider interesting about all the nuances of TCP timestamps, take a look at the paper that Steve Sturges and I wrote "Target-Based TCP Timestamp Stream Reassembly".

I discovered some very unusual behavior with Linux 2.4+ kernels where a timestamp of one less than the previous one was actually accepted. Not only that, when the acknowledgement of that segment was returned,the echoed timestamp was one more than it should have been.

I've crafted the client side of the conversation using my favorite tool Scapy and am displaying the tcpdump output from some sessions. Speaking of Scapy....

*** Warning Shameless Marketing ***

I'm teaching a SANS class "Power Packet Crafting Using Scapy" at SANS Network Security 2010 in Las Vegas on September 26.

Contrary to the popular belief - what your learn and craft in Vegas, doesn't have to stay in Vegas!

I've currently got 0, count 'em - 0, students signed up and I'm feeling awfully unloved and lonely. So, please consider attending this course, since this is probably my last shot at teaching it unless the numbers improve. I know, I know it's awfully unbecoming to have to beg and grovel, but it's all I have left.

*** End Shameless Marketing ***

Take a look first at how a receiving Windows Vista host (IP properly handles an old timestamp.

Record 1. I craft the client SYN to have an initial timestamp value of 100.

Record 2. The server responds and echoes the client's timestamp value of 100 after it's own timestamp value.

Record 4. I send the data "GET /index.html" in relative bytes 1:17 with a good timestamp value of 100.

Record 5. The server acknowledges the data with "ack 17" and echoes the good timestamp value of 100.

Record 6. I attempt to send the rest of the HTTP GET request of "HTTP/1.0\r\n\r\n" in relative bytes 17:29 with a one-less-than-acceptable timestamp value of 99.

Record 7. The server predictably issues a duplicate acknowledgement with "ack 17" indicating it did not accept the previous segment. It echoes the rejected timestamp value of 99.

Now, take a look at how a receiving Linux 2.6 kernel host (IP improperly handles an old timestamp. The session is identical to the Windows Vista one until Record 7.

Record 7. The Linux server erroneously accepts the one-less-than-acceptable timestamp value of 99 and acknowledges the data sent in Record 6. with "ack 29". As well, it echoes a timestamp value of 100 instead of 99.

You're probably thinking to yourself "Who cares?" - no wonder no one wants to attend your Scapy class - you're long-winded and tiresome and you probably went to band camp as a kid! Wait a second, though. What if an IDS/IPS is not aware of this bizarre behavior? Say, for instance, that "GET /EVILSTUFF" in a payload represents suprisingly enough - something malicious. And, say you have a rule that looks for this content in a URL. Further, suppose we divide this into two segments in our crafted session and place "GET /EVIL" in one segment with a timestamp of 100 and "STUFF HTTP/1.0\r\n\r\n" in the next segment with a timestamp of 99. This forces the IDS/IPS to reassemble the two segments.

Now, if the receiver is a Linux host, it will accept this content and perhaps something evil will happen. If you have a TCP timestamp-aware IDS/IPS, it is possible that it will discard the segment with a timestamp of 99. After all - it is invalid. However, if you have a target-based IDS/IPS such as Snort, it can be configured to use a stream5 reassembly policy of "linux" for host and it will alert on the rule.

Once again, this demonstrates the merits of having an IDS/IPS, such as Snort, that is aware of operating system specific behaviors and is adept at handling them. And again, this demonstrates what a complex protocol TCP is, the nuances for receiving host and IDS/IPS interpretation, the the wealth of possibilities for TCP evasions.