Problem Overview

The “Chat-with-Admin” challenge at https://ctf.mcpt.ca/problem/chat-with-admin presents a simple web application where users can send messages to an “admin” and receive responses. Upon examining the source code, we discovered a critical Jinja2 Server-Side Template Injection (SSTI) vulnerability.

Vulnerability Analysis

The vulnerability exists in the app.py file, specifically in how user input is processed and rendered:

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        message = request.form['message']
    else:
        message = 'hello'
 
    response_box = generate_response_html(message)
    html = template.format(response=response_box)
 
    return render_template_string(html)

The key issue is that the application:

  1. Takes user input from a form field named “message”
  2. Generates a response HTML with generate_response_html(message)
  3. Inserts this response into the template using string formatting
  4. Renders the result with render_template_string(html)

While the application attempts to sanitize input by processing it in generate_response_html(), the rendered response is directly inserted into a template that is later processed by Jinja2’s template engine. This creates a path for template injection attacks.

Exploitation

Since Jinja2 evaluates expressions inside {{ }} tags, we can inject a payload that accesses Python’s environment variables to extract the FLAG:

{{config.__class__.__init__.__globals__['os'].environ}}

This payload works by:

  1. Accessing the Flask config object
  2. Getting its class (__class__)
  3. Accessing the class initializer (__init__)
  4. Getting the global namespace (__globals__)
  5. Accessing the os module
  6. Retrieving all environment variables (environ)

Successful Execution

When submitted via the message form, the template engine evaluates our payload, dumps all environment variables including the FLAG, and returns it in the response.

Lessons Learned

This challenge demonstrates why you should never pass user-controlled input to render_template_string() without proper sanitization. A more secure approach would be to use context variables with render_template() instead of string formatting and render_template_string().