For example, we’ll be looking at a common buffer overflow detection technique using negative content matches and the isadataat rule option. We use SID 13916 to demonstrate:
alert tcp $EXTERNAL_NET any -> $HOME_NET 4000 (msg:"EXPLOIT Alt-N SecurityGateway username buffer overflow attempt"; flow:established, to_server; content:"username="; nocase; isdataat:450,relative; content:!"&"; within:450; content:!"|0A|"; within:450; metadata:policy balanced-ips drop, policy connectivity-ips drop, policy security-ips drop; reference:url,secunia.com/advisories/30497/; classtype:attempted-admin; sid:13916; rev:2;)
For easier analysis, here is the core detection functionality of the rule, broken into a list:
- flow:established, to_server;
- content:!"&"; within:450;
- content:!"|0A|"; within:450;
Because this is an attack on a server, we use the flow:established, to_server; rule option to tell Snort to only look at traffic that is headed towards a server (in this case, one listening on port TCP/4000). This both provide an increase in performance by not looking at server-response traffic, and will reduce false-positives by not looking at traffic that could not possibly trigger the vulnerability.
Next, we look to match the string “username=” in the packet. For those following along from last time, this will be the “first, longest” content match, and will be used in the fast-pattern matcher. This string is actually the beginning of the vulnerable field, so we will use it to anchor our upcoming detection. Also, note that we believe that “username=” could be upper or lowercase, or any combination, so we use the nocase keyword to modify the search to be case insensitive.
The attack appears to require at least 450 bytes of data to successfully overflow the buffer. Also, given the protocol we’re looking at, we know that both “&” and “\x0a” (also, \x0d\x0a, but we will just use \x0a for speed, since it works alone or paired with \x0d) are terminating characters that indicate the end of data for that key/value pair. Using this information we build the detection with the last three rule options.
We use a pair of negative content matches, content:!”&”; within: 450; and content:!”|0A|”; within: 450; to ensure that we do not see either terminating condition in the next 450 bytes of payload. Note that we use “within” as opposed to “depth”, because “within” sets the relative match flag and operates from the end of the previous match (username=). Depth would operate from the beginning of the payload (a non-relative search).
There is a problem with using negative content match. It will return as successful (that is, it did not see the provided pattern) even if it runs out of data to read before it reaches the end of our within: 450 requirement. This is bad for two reasons: One, we might false positive on an unreassembled TCP message (splitting the username= field across two packets), and two, if we don’t have enough data to create an overflow, we shouldn’t be doing a pair of content checks. To solve both these issues we use the isdataat: 450, relative; check to ensure that starting from the last match (username=), we have 450 bytes of data remaining in the payload buffer. If there isn’t enough data left, we won’t bother with the last two checks, avoiding a false-positive situation and the overhead of running two long, unnecessary content checks.
The detection is fairly simple, find “username=” and then see if the provided data is 450 or more bytes of data. If it is, we most likely have an attempted buffer overflow attack. But as you can see there are a number of factors we need to get right to ensure we have optimized detection that is both accurate and fast.
If you’re interested in more examples, catch the webcast on September 17th. Here are the details:
Date: Wednesday, September 17, 2008
Time: 1:00 PM US Eastern Daylight Time (GMT -4:00)