Snort's Stream5 and TCP overlapping fragments

Stream5 is a critical aspect of the inspection and detection equation of IDS Snort. It performs based on its specific configuration and thus must be configured correctly. Learn how this powerful Snort preprocessor addresses several aspects of network-centric traffic inspection in this edition of the Snort report.

It's important for value-added resellers and consultants to understand how Snort detects security events. Stream5 is a critical aspect of the inspection and detection equation. A powerful Snort preprocessor, Stream5 addresses several aspects of network-centric traffic inspection. Sourcefire calls Stream5 a "target-based" system, meaning it can perform differently depending on the directives passed to it. These directives tell Stream5 to inspect traffic based on its understanding of differences of behavior in TCP/IP stacks. However, if Stream5 isn't configured properly, customers may end up with a Snort installation that is running but not providing much real value. In this edition of Snort Report I survey a specific aspect of Stream5, found in Snort 2.7.x and 2.8.x.

If you've never read Insertion, evasion and denial of service: Eluding network intrusion detection by Thomas Ptacek and Timothy Newsham, then you absolutely must do so now. Rather than repeat the theory and consequences of their landmark paper, I'll show how features of the most current version of Snort addresses problems identified by the authors almost ten years ago.

Snort installation

First, I install Snort on FreeBSD 6.2.

Next, I edit my snort.conf file to include the changes indicated by the diff below. Note the deletion of all of the rule locations and the addition of a single rule for testing purposes.

With the changes made to snort.conf, I run Snort in testing mode to ensure I didn't forget anything.

Since Snort operated normally, I'm ready to trigger an alert.

Testing Snort on normal traffic

I start Snort and tell it to listen to the local interface while sending alerts to the screen.

taosecurity:/usr/local/snort- bin/snort -i lnc0 -c snort.conf -A console 

On a second machine I connect to the Snort sensor's OpenSSH port and enter a text string to trigger the alert.

tws:~# nc -v 22 inverse host lookup failed: Unknown host
(UNKNOWN) [] 22 (ssh) open
SSH-2.0-OpenSSH_4.2p1 FreeBSD-20060930
This is a test of the emergency broadcast system.
Protocol mismatch.

Snort reports seeing the traffic as I expected.


09/03-14:31:34.315118 [**] [1:2000001:0] TEST String to Port 22 TCP [**] [Priority: 0] {TCP} ->

The traffic looks similar to this:

Notice the entire test message is contained in one segment. This is normal traffic, and it will serve as a baseline for future scenarios.

Using Fragroute

I use Fragroute to exercise the new stream preprocessor, Stream5, present in Snort 2.7.x and beyond. I discuss Fragroute in Detect events without Snort IDS rules so I won't repeat the introduction here.

I create a fragroute.conf file with these contents:

tcp_seg 16

These directives tell Fragroute to chop up TCP segments into 16 byte fragments and print the traffic to the console.

Next, I start Fragroute on a Debian 4.0 Linux host with IP address For all subsequent cases, will be the "attacker" or client, and will be the "victim" and also the machine inspecting traffic with Snort.

tws:~# fragroute -f fragroute.conf
fragroute: tcp_seg -> print

When I repeat my test, connecting to port 22 and entering the "This is a test..." string, Snort doesn't alert. What could be wrong? The traffic was chopped into segments as expected.

Notice the test message is broken into four segments.

The key is found in the Snort startup messages. Look at this excerpt:

Stream5 TCP Policy config:
        Reassembly Policy: FIRST
        Timeout: 30 seconds
        Min ttl: 1
                Static Flushpoint Sizes: YES
        Reassembly Ports:
            21 client (Footprint)
            23 client (Footprint)
            25 client (Footprint)

Port 22 isn't being reassembled, although ports 21, 23, 25 and others are. To correct this problem I modify the entry for stream5_tcp in snort.conf to look like this:

preprocessor stream5_tcp: policy first, use_static_footprint_sizes, \
                                ports both 22

Repeating my test, I find my alert as expected.

09/03-16:29:07.070801 [**] [1:2000001:0] TEST String to Port 22 TCP [**] [Priority: 0] {TCP} ->

This is our first clue that it's important to understand how your inspection product is configured and how it makes decisions.

Overlapping segments

It's time to make life more interesting. I edit fragroute.conf to add overlapping fragments.

tcp_seg 16 new

Here "new" means Fragroute will send garbage first, then correct data last. If the recipient TCP/IP stack favors new data over old, then the connection will be OK because the garbage fragmented data will be discarded prior to being sent to the OpenSSH daemon. (The OpenSSH daemon will ignore it anyway, but if a legitimate OpenSSH connection attempt was being fragmented it would survive the "new" directive.)

At this point I am really interested to see if Snort and Stream5 tell me that someone is potentially attempting an insertion or evasion attack by introducing overlapping fragments.

When I run Snort and the test again, I don't get any alerts from Snort. What could be wrong this time?

Let's return to the stream5_tcp directive:

preprocessor stream5_tcp: policy first, use_static_footprint_sizes, \
                                ports both 22

Here "policy first" is the problem. According to the Stream5 README, "policy first" means "favor first overlapped segment." We want behavior that BSD supports, since the OpenSSH server is running FreeBSD. In other words, Snort is passively reassembling the traffic it sees to the OpenSSH server, but it's making decisions that don't match how the FreeBSD OS on the OpenSSH server reassembles overlapping fragments.

If we change the stream5_tcp directive we will have better results.

preprocessor stream5_tcp: policy bsd, use_static_footprint_sizes, \
                                ports both 22

Sure enough, when we re-run our test we get an alert:

09/03-16:44:40.729492 [**] [1:2000001:0] TEST String to Port 22 TCP [**] [Priority: 0] {TCP} ->

This time, Snort generates an alert because Stream5 reassembled the fragmented traffic in the same way that the underlying FreeBSD OS did. Incidentally, it's irrelevant that Snort is running on the FreeBSD OS itself. Snort makes its own TCP fragmentation and overlap reassembly decisions; it does not look to the underlying OS for answers.

Generic overlapping fragment detection

What if you want to detect any suspicious use of overlapping fragments? In my experience it's uncommon for normal TCP traffic to contain overlapping segments like the following:

That traffic is similar to the packets manipulated by Fragroute in the previous example. As you can see, the last packet contains the beginning of the test message. However, the first packet contains garbage ("b6reOo34j0rojpB.") that overlaps with bytes 17-32 (16 bytes) of the real test message. Because the FreeBSD OS of the OpenSSH server favored new data (the last packet) over old data (the first packet), the correct message was passed to layer 7.

It would be helpful to receive a message from Stream5 whenever odd behavior like this occurs on the wire.

We can modify the stream5_tcp directive one more time to detect this sort of odd activity.

preprocessor stream5_tcp: policy bsd, use_static_footprint_sizes, \
                                ports both 22, detect_anomalies, overlap_limit 1

Notice the inclusion of the "detect_anomalies, overlap_limit 1" option. (Thank you to beenph and VictorK in IRC for passing this information to me!) These options will cause Stream5 to alert when it sees at least one overlapping TCP segment.

In case one, I send the following string but Snort remains silent:

This is a test test test test test test test test test test

In contrast, the following string causes Snort to trigger:

This is a test test test test test test test test test test test

Stream5 produces an alert like this:

09/03-16:58:22.263117 [**] [129:7:1] Limit on number of overlapping TCP packets reached [**] [Priority: 3] {TCP} ->

It's good to get at least one alert on overlapping TCP fragments, but why did we only see one alert?

Why one and not the other?

Why did Snort stay quiet for case one, but react for case two?

The difference lies in the number of packets generated. To demonstrate this, I reproduced the critical elements of the two cases as captured by TCPdump. In each listing, the first number is a packet number for easy reference. Next is the P for the "Push" TCP flag. The number1:number2(number3) field means "sequence number of the first byte in this segment:sequence number of the first byte in the NEXT segment(number of bytes of data in this segement". In each case "ack 40" means the sender expects the other side of the conversation to next send data whose first byte will be number 40. All of these numbers are relative (not absolute) sequence numbers.

(Note that number2 is NOT the sequence number of the last byte of data in the segment. For packet 1 below, 32 is the last number. Count with me if you're confused: 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32. 33 would be the 17th byte, which is wrong. This is a common misconception in books on TCP/IP.)

Here are the sequence numbers for the traffic sent in the first case.

1. P 17:33(16) ack 40
2. P 33:49(16) ack 40
3. P 49:61(12) ack 40
4. P 1:33(32) ack 40

Packet 4 is the only overlapping fragment, since it contains bytes 17-32 which were already sent in fragment 1.

Here are the sequence numbers for the traffic sent in the second case.

1. P 17:33(16) ack 40
2. P 49:65(16) ack 40
3. P 65:66(1) ack 40
4. P 1:33(32) ack 40
5. P 33:65(32) ack 40M

Packets 4 and 5 are overlapping fragments. Packet 4 contains bytes 1-32; packet 1 already sent bytes 17-32. Packet 5 contains bytes 33-64; packet 2 already sent bytes 49-64. Therefore, unlike the previous case, this trace contains two overlapping fragments. Because Stream5's "overlap_limit 1" option was set and two fragments (case two) is greater than one fragment (case one), case two triggered Stream5 while case one did not.

I hope this edition of the Snort Report motivated you to think about how you can use Stream5 to detect Fragroute-type TCP fragment overlap attacks. Like most every Snort Report, this edition only scratched the surface of another powerful component of Snort.

About the author
Richard Bejtlich is founder of TaoSecurity, author of several books on network security monitoring, including Extrusion Detection: Security Monitoring for Internal Intrusions, and operator of the TaoSecurity blog.

Dig Deeper on Managed network services technology