Packet signatures

During the source of investigating the wu-ftpd exploit (see references), analysts were reminded of the one of the limitations of current Intrusion Detection Systems; the inability to handle regular expressions. When the attack has identifiable fixed strings of characters, the signature is reasonably straightforward:

alert tcp $EXTERNAL_NET any -> $HOME_NET 21 \
(msg:"FTP EXPLOIT wu-ftpd 2.6.0"; flags: A+; \
content: "|2e2e3131|venglin@";reference:arachnids,440; \
classtype:attempted-user; sid:348; rev:1;)

This Snort signature recognizes packets sent to an FTP server with the hex characters 2e2e313 followed by the word "venglin@" in them.

The problem comes when we don't have a fixed series of characters to match against. The November 2001 wu-ftpd exploit used the fact that a sequence of whitespace, a tilde, optional characters, a left curly brace, and any other characters without a matching right curly brace would crash wu-ftpd, allowing a buffer overflow exploit to take over.

Because of the possibility of additional characters and the requirement that the second block of characters not contain a "}", we need to use something stronger than fixed strings.

The snort signatures:

alert tcp $EXTERNAL_NET any -> $HOME_NET 21 (msg:"FTP wu-ftp file completion
attempt ["; flags:A+; content:"["; content:!"]";
reference:url,archives.neohapsis.com/archives/vulnwatch/2001-q4/0059.html;
sid:1377; rev:1;)

alert tcp $EXTERNAL_NET any -> $HOME_NET 21 (msg:"FTP wu-ftp file completion
attempt {"; flags:A+; content:"{"; content:!"}";
reference:url,archives.neohapsis.com/archives/vulnwatch/2001-q4/0059.html;
sid:1378; rev:1;)

will do a good first pass at detecting file completion attempts with "~...{..." and "~...[..." as long as the requests don't have any right braces or brackets, respectively.

Ngrep and regular expressions to the rescue

A second pass at a signature for the wu-ftpd exploit needs to be able to check for a "~", optional characters, "{", and perhaps additional non-"}" characters. While Snort has a "regex" option, it's not a sufficiently strong regex implementation to match specific characters (as the authors acknowledge in the documentation).

The Ngrep package can, like tcpdump, watch for packets destined to a given port (21 in this case). Unlike tcpdump, it has the ability to look for a regular expression in the payload of the packet, allowing us to get a little closer to a correct wu-ftpd attack signature.

The ngrep command to alert us to these attacks is:

ngrep -t -O /var/log/wuftpd '~.*(\{[^}]*|\[[^]]*)$' 'tcp and port 21'

Here's the interpretation:

-t
show timestamp
-O /var/log/wuftpd
log matching packets to a BPF file for later analysis with tcpdump, ethereal, snort or other BPF compatible tools.
'~.*(\{[^}]*|\[[^]]*)$'
Only look at packets with the following objects:
~
literal tilde
.*
optional other characters
(...|...)
Either the first block (mismatched curly brace) or second block (mismatched square brackets)
\{
literal left curly brace
[^}]*
all other characters up to the end ($) must be anything but a right curly brace.
\[
literal left square bracket
[^]]*
all other characters up to the end ($) must be anything but a right square bracket.
$
The end of the string - this requires that all of the packets up to the end not be right brackets or braces.

This approach will catch "~...}...{", which the snort rule would have missed as there was a "}", just not in the right location to match the "{".

More examples

To see all outgoing web requests from machine 12.13.14.15:

ngrep -t '^(GET|POST) ' 'src host 12.13.14.15 and tcp and dst port 80'

The caret (^) instructs ngrep to only look at the beginning of the packet payload. As above, the (...|...) will match either GET or POST. Ngrep will show just the actual web requests:

ngrep -t '^(GET|POST) ' 'src host 12.13.14.15 and tcp and dst port 80'
interface: eth0 (12.13.14.0/255.255.255.0)
filter: ip and ( src host 12.13.14.15 and tcp and dst port 80 )
match: ^(GET|POST) 
###
T 2001/11/30 14:23:40.379947 12.13.14.15:39905 -> 206.228.70.121:80 [AP]
  GET / HTTP/1.0..If-Modified-Since: Fri, 30 Nov 2001 18:38:18
  GMT..User-Agent: Mozilla/4.78 [en] (X11; U; Linux 2.5.1-pre1
  i686)..Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,
  image/png, */*..Accept-Encoding: gzip..Accept-Language:
  en..Accept-Charset: iso-8859-1,*,utf-8..Via: 1.0
  mymachine:3128 (Squid/2.4.STABLE1)..X-Forwarded-For:
  127.0.0.1..Host: www.stearns.org..Cache-Control: max-age=259200.
  .Connection: keep-alive....
#####
T 2001/11/30 14:24:05.782582 12.13.14.15:39907 -> 12.33.247.4:80 [AP]
  GET /cgi-bin/blockrules HTTP/1.0..Referer:
  http://www.stearns.org/..User-Agent: Mozilla/4.78 [en] (X11; U; Linux
  2.5.1-pre1 i686)..Accept: image/gif, image/x-xbitmap, image/jpeg,
  image/pjpeg, image/png, */*..Accept-Encoding: gzip..Accept-Language:
  en..Accept-Charset: iso-8859-1,*,utf-8..Via: 1.0
  mymachine:3128 (Squid/2.4.STABLE1)..X-Forwarded-For:
  127.0.0.1..Host: www.incidents.org..Cache-Control: max-age=259
  200..Connection: keep-alive....
#########
T 2001/11/30 14:24:14.066732 12.13.14.15:39908 -> 12.33.247.4:80 [AP]
  POST /cgi-bin/blockrules HTTP/1.0..Referer:
  http://www.incidents.org/cgi-bin/blockrules..User-Agent: Mozilla/4.78
  [en] (X11; U; Linux 2.5.1-pre1 i686)..Accept: image/gif,
  image/x-xbitmap, image/jpeg, image/pjpeg, image/png,
  */*..Accept-Encoding: gzip..Accept-Language: en..Accept-Charset:
  iso-8859-1,*,utf-8..Content-Type:
  application/x-www-form-urlencoded..Content-Length: 135..Via: 1.0
  mymachine:3128 (Squid/2.4.STABLE1)..X-Forwarded-For:
  127.0.0.1..Host: www.incidents.org..Cache-Control:
  max-age=259200..Connection: keep-alive....
#######

The first is a straight web request to www.stearns.org. The second and third are an example of a POST request to www.incidents.org.

The #'s indicate that a packet matched the BPF filter, but failed to match the string check. The "-q" quiets the #'s if you only want the text.

If you don't need to look for a specific string, make sure you put in an empty string as the match string:

ngrep -t '' 'tcp and port 80'

To see who's failing to encrypt their pop mail logins:

ngrep -t 'USER' 'tcp and port 110'
interface: eth0 (12.13.14.0/255.255.255.0)
filter: ip and ( tcp and port 110 )
match: USER
###
T 2001/11/30 16:07:51.722417 12.13.14.15:1810 -> mailserver:110 [AP]
  USER billy barty..
####################exit
23 received, 0 dropped

This has the added benefit of not displaying the actual password used, as this is sent in a future packet. For ethical uses, this is a real benefit.

If you need to look for a hex string, use the "-X" option; your match string will be interpreted as hex characters to find in the packets. "-x" will show the packet in hex format. Either or both are useful in detecting virii and buffer overflows; a long string of hex 90 bytes is characteristic of an Intel architecture buffer overflow.

Limitations

The major issue with this type of matching is fragmentation. Ngrep can only string match against individual packets; it has no ability to match a string that is broken across two or more packets. This allows an attacker to sneak packets past ngrep by fragmenting them into small enough pieces that the string to be matched shows up in two or more packets. If the string to be matched falls somewhere in the middle of the TCP stream, the same problem can occur with natural fragmentation. We were lucky in the above "(GET|POST) " example because we knew that the string to match against was at the beginning of the packet; had it fallen, say, about 1500 bytes in, we might not have been so lucky.

This fragmentation problem exists in Linux' iptables firewall string match as well. Snort does not suffer from this problem as Snort reassembles the packets into the original TCP stream before string matching.

As a side note, detecting attacks for this wu-ftpd vulnerability are actually more complex than the simplified example used above for demonstration purposes. As the bracket/brace pairs can be nested and additional unmatched right brackets/braces are ignored, the actual regular expression to correctly match the attack without false positives or negatives is either tremendously complex with regular expression backreferences or nonexistant.

References

The wu-ftpd vulnerability:

Credits

William is an Open-Source developer, enthusiast, and advocate from Vermont, USA. His day job at the Institute for Security Technology Studies at Dartmouth College pays him to work on network security and Linux projects.

Many thanks to Vicki Irwin of Sans and George Bakos of ISTS for their input and assistance on this article.

Many, many thanks to Jordan Ritter, ngrep's author, for this useful tool and for some instructive feedback on this article.