🗂️ INDEX

글 작성자: Universe7202

zer0pts 2020 CTF 문제 중 pwnable 분야의 notepad 이다.

 

 

 

 

문제 사이트에 접속하면 아래와 같이 출력이 된다.

 

 

 

소스코드는 아래와 같다.

import flask
import flask_bootstrap
import os
import pickle
import base64
import 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
    )

 

 

pickle의 loads() 함수에서 RCE vulnerability 가 있다.

그래서 게시물을 적는 곳에 payload를 작성하고 read 를 하면 작성된 payload를 loads() 할때 RCE가 성공할 줄 알았지만,,

 

 

취약한 부분은 아래와 같다. 404 에러일때, referer 값으로 redirection 하게 되는데, render_template_string() 함수로 문자열에 있는 특정 구문을 실행하게 된다.

@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

 

 

 

'🚩CTF' 카테고리의 다른 글

[pwnable.xyz] badayum write up  (0) 2020.03.10
[pwnable.xyz] Hero Factory write up  (0) 2020.03.10
[zer0pts 2020 CTF] - Can you guess it? write up  (0) 2020.03.09
[zer0pts 2020 CTF] hipwn write up  (0) 2020.03.09
Aero 2020 CTF write up - aerofloat  (0) 2020.03.03