Hash collision DoS vulnerability and PHP 5.x

There’s a lot of fear floating around right now about the hash collision DoS vulnerability which pretty much every web application platform out there (except for Perl, which fixed the vulnerability way back in 2003!) is open to. http://thehackernews.com/2011/12/web-is-vulnerable-to-hashing-denial-of.html

And yeah, it’s a pretty big deal – if you’re vulnerable, any PHP script that so much as touches $_POST or $_GET will be vulnerable. What none of these pages seem very inclined to tell you is exactly HOW to test for vulnerability – and, what may have already made this problem a non-issue for you. Spoiler: if you’re running a current LTS version of Debian or Ubuntu and you installed the LAMP stack during boot or by using tasksel install lamp-server, you’re probably fine. The Suhosin patch gets in the vulnerability’s way in the default configuration it uses on Squeeze and Lucid, and the Debian-style LAMP installation gives you Suhosin, so you’re good to go.

But what if you DIDN’T get your LAMP stack that way, or you just aren’t sure if you’re running Suhosin – or the right configuration of Suhosin – and you want to check? First, dump this little PHP script somewhere that you can access it – for most people, /var/www/hashtest.php will work fine:

<!--?php $test = $_POST['test']; echo "test passed!\n"; ?-->

Note: it’s that useless line accessing $_POST that makes this script potentially vulnerable – without that line, this script wouldn’t actually be vulnerable to the attack, because PHP builds the super-array for $_POST and $_GET lazily. You don’t access it… PHP doesn’t create it. Anyway, now make sure that you can actually access that script using wget, like this: wget -O – http://127.0.0.1/hashtest.php

You should get a quick HTML output that says “test passed!” – which isn’t true, because we didn’t actually test it – but now you know the script will actually execute. Now, wget -qO /tmp/hashcollide.txt https://jrs-s.net/hashcollide.txt – this gives you a “payload” file with a nice set of really nasty hash collisions that will confuse a PHP application badly. Finally, you’re ready to test it out – wget -O – –-post-file /tmp/hashcollide.txt http://127.0.0.1/hashtest.php[/b] and you’re off to the races. If you’re lucky, you’ll get this:

test passed!

If you’re not lucky, you get a nice long wait (max_execution_time in php.ini, 30 seconds by default) followed by this:

me@locutus:/var/www$ wget -O - --post-file /tmp/hashcollide.txt http://127.0.0.1/hashtest.php
 Connecting to 127.0.0.1:80... connected.
 HTTP request sent, awaiting response... 500 Internal Server Error
 2011-12-29 16:10:14 ERROR 500: Internal Server Error.

In my testing so far, FreeBSD (all versions), Ubuntu Hardy, Ubuntu Oneiric, and Debian Lenny are vulnerable. Ubuntu Lucid and Debian Squeeze were not. Again, this assumes you’ve installed the LAMP stack in the default manner; “cowboy installs” may or may not be vulnerable. Suhosin appears to be the key factor determining whether a particular machine will or will not fall prey to this vulnerability. The fix, if you are vulnerable – upgrade your OS to a current LTS version, upgrade all packages, and make sure you’re running Suhosin – then make sure you actually set the Suhosin variable suhosin.post.max_vars to 1000 or less.

In the following example, we discover that a stock Oneiric workstation is vulnerable, and then we fix it:

me@locutus:/var/www$ wget --post-file /tmp/hashcollide.txt -O - http://127.0.0.1/hashtest.php
 --2011-12-30 10:27:43-- http://127.0.0.1/hashtest.php
 Connecting to 127.0.0.1:80... connected.
 HTTP request sent, awaiting response... 500 Internal Server Error
 2011-12-30 10:28:43 ERROR 500: Internal Server Error.

Yup, it’s broken – so let’s fix it.

me@locutus:~$ sudo apt-get install php5-suhosin
me@locutus:~$ grep suhosin.post.max_vars /etc/php5/apache2/conf.d/suhosin.ini
;suhosin.post.max_vars = 1000
me@locutus:~$ sudo sed -i s/;suhosin\.post\.max_vars/suhosin\.post\.max_vars/ /etc/php5/apache2/conf.d/suhosin.ini
me@locutus:~$ grep suhosin.post.max_vars /etc/php5/apache2/conf.d/suhosin.ini
suhosin.post.max_vars = 1000
me@locutus:~$ wget -qO - --post-file /tmp/hashcollide.txt http://127.0.0.1/hashtest.php
test passed!

For some damn reason, even though Oneiric indicates that suhosin.post.max_vars should be set to 1000 by default, and even though the Suhosin project says they have it default to 200… in actuality, on Oneiric, it defaults to unset. If you uncomment the statement already in suhosin.ini as I did above, though, then restart Apache – you’re set.

Note: the “payload file” referenced above was lifted from https://github.com/koto/blog-kotowicz-net-examples/tree/master/hashcollision .