Recently, I've been on a campaign to make people aware of TCP evasions of IDS/IPS solutions. Suppose that you could find a way to make an IDS/IPS believe that a TCP session has closed when it really hasn't. After you dupe the IDS/IPS into no longer tracking the session, you send your exploit and it goes undetected.
Sound difficult? Well, it really is not. There are a lot of nuances when dealing with a protocol as complex as TCP. And, many of these nuances are different interpretations made by different OS software. For instance, if you have wholly overlapping TCP seqments where both segments begin on the same sequence number and end on identical sequence numbers, but the payload is different which segment does the destination OS favor and just as important - does the IDS/IPS make the same choice?
I've got an interesting issue I found when doing research at Sourcefire years ago. This is a "nuance" that may not occur with all operating systems, but occurs when Linux runs on the destination host. And, it involves a RESET that appears to close the session, but really doesn't.
First let's take a look at some tcpdump output where I'm crafting the client side of the connection using a wonderful packet crafting tool called Scapy that uses the Python interpreter. The packets I crafted are in yellow. I'll cover how I crafted the session using Scapy in an upcoming blog. The three-way handshake starts on packet 1 where I craft the SYN. The server responds with the conventional SYN/ACK in packet 2. And, then in packet 3, I accidentally ACK the server with a value one more than the expected value. It isn't obvious, but the relative value ack 2 in tcpdump parlance should have been ack 1 if I got it right.
Now, here's where the bizarre stuff happens. In packet 4, the server resets the connection because of the invalid ack number. Fair enough. But, in packet 5, I send 10 bytes of data "GET /EVIL" and I assign a correct acknowledgement number as tcpdump indicates with ack 1. Now look at the server's response in packet 6. It acknowledges the 10 bytes of data! How is that possible - didn't it reset the connection? And, in packet 7, I send more data and the server acknowledges this in packet 8.
1. 10.1.3.7.8204 > 10.1.3.45.80: Flags [S], seq 10, win 8192
2. 10.1.3.45.80 > 10.1.3.7.8204: Flags [S.], seq 3506022755, ack 11, win 5840, options [mss 1460]
3. 10.1.3.7.8204 > 10.1.3.45.80: Flags [.], ack 2, win 8192
4. 10.1.3.45.80 > 10.1.3.7.8204: Flags [R], seq 3506022757, win 0
5. 10.1.3.7.8204 > 10.1.3.45.80: Flags [P.], seq 1:10, ack 1, win 8192
GET /EVIL
6. 10.1.3.45.80 > 10.1.3.7.8204: Flags [.], ack 10, win 5840
7. 10.1.3.7.8204 > 10.1.3.45.80: Flags [P.], seq 10:51, ack 1, win 8192
STUFF HTTP/1.1\r\nHost:www.whatever.com\r\n\r\n
8. 10.1.3.45.80 > 10.1.3.7.8204: Flags [.], ack 51, win 5840
9. 10.1.3.7.8204 > 10.1.3.45.80: Flags [R.], seq 51, ack 1, win 8192
You're probably thinking what's going on. Before I explain what I believe occurred, let's look at the implications of what just happened. You'd think that when an IDS/IPS saw the reset, it would stop tracking the session. And, let's say that the string "EVILSTUFF" represents, well - evil stuff. It's in two separate segments requiring reassembly so it's fairly safe to say most IDS/IPS solutions would miss this.
Okay, as for the explanation. My thought is that the server considered the third packet with the bad acknowledgement number a "foreign" or "rogue" segment. It did not consider it a valid segment of the forming session establishment. If that doesn't make sense, what happens if you try to send a PUSH packet with data to a session that hasn't been established? The server resets it. And, I believe that since this was not yet an established session, the server considered the segment with an invalid ACK number as foreign to any session it knew.
This highlights a couple of things. First, it's not hard to cause TCP evasions for an IDS/IPS. And second, there are issues that affect specific operating systems only. Servers on other operating systems than Linux that receive the bad client acknowledgement number may continue to send the SYN/ACK until a valid one arrives. It is extremely difficult for an IDS/IPS solution to have knowledge of all these OS-specific TCP issues and deal with them properly. And, that is why TCP evasions are possible.
Very interesting. I wonder if it's by design or a bug. Do you know what ids systems this affects?
ReplyDeleteAlso, the colour scheme burns my eyes. It even comes through as white on my rss reader, making it nearly impossible to read. You may want to reconsider that decision.
Judy, as always, nice work. Look forward to seeing more.
ReplyDeleteVery Nice, at one point i was really close to grasping this possibility. But the actual proof of such attack-vector being plausible is a thumbs-up for my natural paranoid state of being.
ReplyDeleteHi Judy, I think this kind of behavior is expected with any implementation using syn-cookie, (which explains why you don't see the SYN+ACK being retransmitted), as the wrong ACK is just a stray packet and the GET is considered as valid final ACK of three-way handshake since the syn-cookie will match.
ReplyDeleteBrian - It looks like Ashok has the answer why Linux does this because it uses SYN cookies. I just checked on the Linux system I use:
ReplyDeletesysctl -a | grep net.ipv4.tcp_syncookies
net.ipv4.tcp_syncookies = 1
IIRC, if Snort is configured to use preprocessor stream5_tcp: policy windows, I believe it will not be duped into resetting the connection. I know this isn't intuitive since we're using Linux, and I'm not necessarily recommending it, it's just the way that the stream5 preprocessor handles reset sequence numbers.
Ashok - Wow, thanks so much for the insight! That's amazing to associate this behavior with SYN cookies - now I better understand the seemingly bizarre behavior.
ReplyDeleteGreat post Judy. I might add this in to Rule2Alert to stress test systems such as Snort.
ReplyDeletehmm, i am not sure this is really caused by syn-cookies, on some linux, syn-cookie is enabled by default but they won't kick in until some threshold has been reached.
ReplyDeletealso, even if syn-cookier kicks in, it should wait for the ACK, and decrypt the sequence number, and the "ACK 2" just won't match...
oops, did not notice the "ACK 1" in packet 5, so it will make syn-cookie happy.
ReplyDeletebut i do recall seeing something like syn-cookie won't kick in until a certain number is reached. (i might be wrong)
This is almost definitely a syncookie thing. Since the linux host is using its magic algorithm to generate a sequence number, you could theoretically open a SYN-less connection, if you knew the magic data. Since syncookies uses the source and destination host and port numbers as part of its hash, using a ack + 2 wont validate against its syncookies algorithm, but using ack + 1 will validate. So to the webserver (in this case), doesn't actually have any entries in its table until that second connection (ack + 1) opens it.
ReplyDeleteI suppose someone now needs to write a patch for IDS's to understand syncookies a little better. I wish I had the time to set up snort to test this, but syncookies most definitely does look like the culprit.
Now the next question is why does stream5 handle it better. Interesting thing to look into.
stream5 would have to handle this as an OS-specific policy because not all OS's respond this way. Some of stream5's policies ensure that the sequence number on the reset is the next expected one - in other words one more than the last value - not two more as seen in the above reset. If this is the case, Snort will not be fooled. This is not a universal policy because some OS's allow a reset to be any value in the current window.
ReplyDeleteJudy, I just posted a blog with a sample scapy script using this evasion technique.
ReplyDeletehttp://www.malforge.com/node/35
Thanks for the packet-crafting fun!
famousjs - That was the next post I was planning on doing! I'm going to cover it again in some detail so please don't think I'm pirating your idea. Thanks for the post. One of my motivations for this and many of the other blogs I plan to do is to show how awesome Scapy is. Thanks for helping me in this regard.
ReplyDeleteI just wanted to reiterate that this is not a universal evasion against Snort. This "issue" was discovered when I worked on stream5 research at Sourcefire. As I mentioned yesterday, the default stream5 policy of "windows" will actually not be fooled by this because of what it considers an invalid reset sequence number.
ReplyDeleteAt Sourcefire, we never truly understood this weird behavior. As Ashok astutely commented - it is a protection mechanism against SYN flooding that is implemented by SYN cookies. I sent this information to Steve Sturges,the author of stream5, at Sourcefire.
I kept thinking about this, and ended up writing a blog posting about how I see the specifics working.
ReplyDeletehttp://blog.bmurray.ca/2010/06/idsips-evasion-with-syncookies/
Dude, great article. Now, if only you could make that font legible enough without having to zoom in using magnifier...
ReplyDeleteHi Judy,
ReplyDeleteGreat article! This just solidifies my love for Scapy. This will be great for our QA testing in house.
Marcos - great to hear from you. Glad this was helpful. When I get some time, I'm going to post the Scapy code for this.
ReplyDelete