The suhosin module provides transparent cookie and session encryption out of the box to PHP applications. Once enabled any session values stored on disk are encrypted with rijndael and a slight variation on base64 encoding, the same applies to any cookies that are stored on the client. Many people rely solely on this encryption to protect them against parameter tampering attacks.
This post will explain why suhosin encryption is not necessarily as secure as you might think and how its default configuration should not be relied upon to protect the content of sessions and cookies.
Basic suhosin encryption settings
First you need to understand the PHP suhosin session and cookie settings and how these affect access to the session and cookie data, in particular how they affect the generation of the encryption key used to protect the data. There are six settings for both suhosin.session and suhosin.cookie these are their defaults:
Parameter | Sessions | Cookies |
---|---|---|
encrypt | On | Off |
cryptkey | \ | \ |
cryptraddr | 0 | 0 |
cryptua | Off | On |
cryptdocroot | On | On |
checkraddr | 0 | 0 |
Parameter | Description |
---|---|
Encrypt | Turns on the transparent encryption |
cryptkey | Custom string added to the encryption key blank by default. |
cryptraddr | The number of octets of the users IP to add to the encryption key |
cryptua | Adds the user agent string to the encryption key |
cryptdocroot | Adds the document root as defined by Apache to the key |
checkraddr | Has no affect on the encryption key but prevents users on other IP addresses accessing the session data once decrypted. |
How Suhosin generates the encryption key
As you can see, the more Suhosin settings that are enabled the more complex the encryption key will become sessions by default are encrypted using solely the document root where as cookies use a concatenation of both the document root and user agent string. The key will always be built in the same way and take the order:
cryptkey + user agent + document root + IP octets
12345Mozilla/5.0 (X11; Linux x86_64; rv:6.0.2) Gecko/20100101 Firefox/6.0.2/var/www127.0.0.1
The variables are concatenated without a separator if for some reason the cryptkey string is NULL then Suhosin will default to a value of D3F4UL7. Once built the string is hashed using SHA256 and the result used to generate a 256bit rijndael encryption key. The code used to generate the SHA256 key is shown below:
char *suhosin_generate_key(char *key, zend_bool ua, zend_bool dr, long raddr, char *cryptkey TSRMLS_DC) { char *_ua = NULL; char *_dr = NULL; char *_ra = NULL; suhosin_SHA256_CTX ctx; if (ua) { _ua = sapi_getenv("HTTP_USER_AGENT", sizeof("HTTP_USER_AGENT")-1 TSRMLS_CC); } if (dr) { _dr = sapi_getenv("DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT")-1 TSRMLS_CC); } if (raddr > 0) { _ra = sapi_getenv("REMOTE_ADDR", sizeof("REMOTE_ADDR")-1 TSRMLS_CC); } suhosin_SHA256Init(&ctx); if (key == NULL) { suhosin_SHA256Update(&ctx, (unsigned char*)"D3F4UL7", sizeof("D3F4UL7")); } else { suhosin_SHA256Update(&ctx, (unsigned char*)key, strlen(key)); } if (_ua) { suhosin_SHA256Update(&ctx, (unsigned char*)_ua, strlen(_ua)); } if (_dr) { suhosin_SHA256Update(&ctx, (unsigned char*)_dr, strlen(_dr)); } if (_ra) { if (raddr >= 4) { suhosin_SHA256Update(&ctx, (unsigned char*)_ra, strlen(_ra)); } else { long dots = 0; char *tmp = _ra; while (*tmp) { if (*tmp == '.') { dots++; if (dots == raddr) { break; } } tmp++; } suhosin_SHA256Update(&ctx, (unsigned char*)_ra, tmp-_ra); } } suhosin_SHA256Final((unsigned char *)cryptkey, &ctx); cryptkey[32] = 0; /* uhmm... not really a string */ return cryptkey; }
If you refer back to the default settings the key used to encrypt sessions is derived from solely the web root e.g. /var/www (this is not the same as /var/www/). Cookies are encrypted with both the web root and the clients user agent string (which is known). A simple PHP error could give the location of the web root to an attacker which he could use to decrypt and tamper with client side cookies or the data stored within server sessions (assuming he has access to the sessions on disk).
Calculating the encryption key value
If we assume the default Suhosin settings are in place then defeating the cookie encryption is a simple case of either finding an error message with the web root in or staging a dictionary attack on the name of the web root. You should already know what your user agent string is so there is only one unknown variable to guess. Using information harvested from Google searches for "DocumentRoot not found" I've come up with a script that takes a domain name an generates a list of plausible web roots. Download the DocumentRoot generator These can be fed into a script to brute-force the encrypted data.
#bash> ./generate.php Usage ./generate.php domainname.com > directorylist.txt #bash> ./generate.php idontplaydarts.com > dirlist.txt #bash> wc -l dirlist.txt 29160 dirlist.txt #bash> cat dirlist.txt /data/web /data/web/ /data/web/idontplaydarts.com/public_html /data/web/idontplaydarts.com/public_html/ ......
But what if the session data is also encrypted with the user agent string and you want to decrypt someone else's session? Now you'll need to brute-force the user agent string as well, there are probably only a few thousand variants of the modern browsers and there are plenty of lists to choose from. If you've already compromised the system you could construct a list of agents from the servers Apache logs and use these in the attack.
If the system administrator or developer has opted to lock the session to the clients IP address using cryptraddr then you might have to test the entire 32 bit range of IP addresses. Luckily however thanks to some service providers frequently changing your IP address during the course of your Internet session it is unlikely that any more than the first two octets of the IP address will be used in the rijndael key. You can further reduce this set of IP addresses if you know what country the client is connecting from, MaxMind has a good GeoLocation database that can be used to narrow down you attack. Again, if you can view the Apache logs and extract the IP addresses of the hosts that have accessed the server it will significantly reduce your search space (if your attacking a cookie rather than a session you should already know your IP address).
You might have noticed on line 17 that Suhosin looks to the REMOTE_ADDR variable for the clients IP address so there is a potential for a further mis-configuration if Apache resides behind a load balancer or proxy such as Nginx or Varnish which is failing to update the REMOTE_ADDR header. In these situations the REMOTE_ADDR might be that of the upstream proxy which would make the attack much quicker as every client will appear to come from the same address.
Decrypting Suhosin sessions and cookies using C
I saw a good entry on ha.xxor.se that describes how to hijack a session on shared hosting however this involves manipulating internal variables in PHP which doesn't always work and tends to be fairly slow. I briefly tried decrypting the session variables using purely PHP and the mcrypt extension although I had little success.
In order to decrypt Suhosin sessions and cookie strings you really need an external program that is independent of PHP, one that can quickly brute force sessions or cookies saved in a file. I couldn't find one on the net so I wrote one in C its mainly hacked together from bits of the suhosin and PHP source code. There is plenty of scope for improvement and optimization.
The script comes with 3 examples located in the demos/ folder - I've included a compiled version that should work on x86 Linux as well as the source and make file.
Download the Suhosin decrypter.
Usage: cmd [SESSION FILE] [OPTIONS] Cracks Suhosin rijndael encryption by launching dictionary attacks on the key. The session or cookie string to be cracked must be placed in SESSION FILE. Mandatory Arguments: [SESSION FILE] File containing the session data Arguments: -UA list of user agents in txt file -DR list of directory roots -IP list of ip addresses -CK list of crypt keys -ip singular ip adddress -dr singular directory root -ua singular user agent -ck crypt key string NB. When a list of anything is specified the decrypter will also add a blank string to the list.