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.
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:
This approach will catch "~...}...{", which the snort rule would have missed as there was a "}", just not in the right location to match the "{".
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 fail to include a string to match against, you'll only get #'s for output. To see the text of all packet, use the empty string as match criteria:
ngrep -t '' 'tcp and port 80'
Finally, 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.
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.
The wu-ftpd vulnerability:
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 thanks to Jordan Ritter, ngrep's author, for this useful tool.