GREETINGS
Table of Contents
Overview
- Solved by: @siunam
- 234 solves / 50 points
- Author: @skyv3il
- Difficulty: Warmup
- Overall difficulty for me (From 1-10 stars): ★☆☆☆☆☆☆☆☆☆
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!
- Flag:
TFCCTF{a6afc419a8d18207ca9435a38cb64f42fef108ad2b24c55321be197b767f0409}
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:
- PugJs Server-Side Template Injection (SSTI)