-
[Study] Plaid CTF 2021 Pearl's U-Stor WriteupCTF/Plaid CTF 2021 2021. 4. 20. 22:49
주어진 페이지에 접근해보면 아래와 같은 페이지가 나타난다. 파일 Up/Download 가 가능하다.
주어진 소스를 분석해보자.
from flask import Flask, render_template, url_for, request, send_from_directory, send_file, make_response, abort, redirect from forms import AppFileForm import os from io import BytesIO from werkzeug.utils import secure_filename from subprocess import Popen import uuid import sys from paste.translogger import TransLogger import waitress import time from flask_wtf.csrf import CSRFProtect app = Flask(__name__) app.config['SECRET_KEY'] = os.environ.get("APP_SECRET_KEY") app.config['UPLOAD_FOLDER'] = 'uploads' app.config['TMP_FOLDER'] = '/tmp' app.config['RECAPTCHA_DATA_ATTRS'] = {'bind': 'recaptcha-submit', 'callback': 'onSubmitCallback', 'size': 'invisible'} app.config['RECAPTCHA_PUBLIC_KEY'] = os.environ.get("APP_RECAPTCHA_PUBLIC_KEY") app.config['RECAPTCHA_PRIVATE_KEY'] = os.environ.get("APP_RECAPTCHA_PRIVATE_KEY") csrf = CSRFProtect(app) def get_cookie(cookies): if 'id' in cookies: cookie_id = cookies.get('id') if cookie_id.strip() != '' and os.path.exists(os.path.join(app.config['TMP_FOLDER'], cookie_id)): return (False, cookie_id) cookie_id = uuid.uuid4().hex os.mkdir(os.path.join(app.config['TMP_FOLDER'], cookie_id)) return (True,cookie_id) @app.route('/', methods=["GET", "POST"]) def index(): (set_cookie, cookie_id) = get_cookie(request.cookies) form = AppFileForm() if request.method == "GET": try: file_list = os.listdir(os.path.join(app.config['TMP_FOLDER'], cookie_id)) ##### ......................................................................... (1) except PermissionError: abort(404, description="Nothing here.") resp = make_response(render_template("index.html", form=form, files=file_list)) elif request.method == "POST": errors = [] if form.validate_on_submit(): myfile = request.files["myfile"] file_path = os.path.join(app.config['TMP_FOLDER'], secure_filename(cookie_id), secure_filename(myfile.filename)) ##### ......................................................................... (2) if os.path.exists(file_path): errors.append("File already exists.") elif secure_filename(cookie_id) == '': errors.append("Cannot store file.") else: try: myfile.save(file_path) cmd = ["chattr", "+r", file_path] ##### ................................................................ (4) proc = Popen(cmd, stdin=None, stderr=None, close_fds=True) except: errors.append("Cannot store file.") try: file_list = os.listdir(os.path.join(app.config['TMP_FOLDER'], cookie_id)) except PermissionError: abort(404, description="Nothing here.") resp = make_response(render_template("index.html", form=form, files=file_list, errors=errors)) if set_cookie: resp.set_cookie('id', cookie_id) return resp @app.route('/file/<path:filename>') def get_file(filename): (set_cookie, cookie_id) = get_cookie(request.cookies) filename = secure_filename(filename) if set_cookie: abort(404, description="Nothing here.") if not os.path.exists(os.path.join(app.config['TMP_FOLDER'], secure_filename(cookie_id), filename)): abort(404, description="Nothing here.") with open(os.path.join(app.config['TMP_FOLDER'], secure_filename(cookie_id), filename), "rb") as f: ##### ................................................................................ (3) memory_file = f.read() return send_file(BytesIO(memory_file), attachment_filename=filename, as_attachment=True) ... Continue ...
우선 페이지에 처음 접근하면 접속자의 uuid를 기반으로 쿠키를 만들고 그 값을 경로로 사용하여 디렉토리를 제공한다.
또한, (1) 에 의해 해당 디렉토리의 파일 리스트가 제공된다.
파일 업로드 시에는 (2)에 tmp/cookieid/filename 경로로 저장이 되는데, cookie_id와 filename에 secure_filename 처리가 되어있다.
원래 파일 이름을 통한 SSTI등의 공격을 시도하려 하였으나 사용자 정의 함수가 아닌 werkzeug.utils에서 제공하는 함수였기 때문에 이를 우회하기란 쉽지 않았다.
고민하는 과정에서 얻게 된 정보는, (2), (3) 처럼 파일 업로드, 다운로드 과정에서는 cookie_id와 filename에 secure_filename 처리가 되어있지만 (1) 에는 되어있지 않다는 점이다.
이를 이용하여 플래그 파일의 위치를 알 수 있다. ( /cygdrive/c/ )
이에 대한 해결법은 (4) 처럼 +r 옵션을 줬다는점, 확장자에 대한 검증은 없다는 점을 이용하여 심볼릭 링크를 활용하는 것이었다.
일반적인 방법으로 심볼릭 링크를 생성 후 업로드 하면 아래와 같은 현상이 나타난다.
( ln -s /cygdrive/c/flag.txt flag )
그 이유는 cygwin에서 생성한 심볼릭 링크는 기본적으로는 cygwin외부에서 사용이 불가능하기 때문이다. 이를 해결하기 위해서는 native symbolic link를 사용해야 한다.
Creation of native symlinks follows special rules to ensure the links are usable outside of Cygwin. This includes dereferencing any Cygwin-only symlinks that lie in the target path.
cygwin.com/cygwin-ug-net/using.html#pathnames-symlinks
Chapter 3. Using Cygwin
The Cygwin DLL supports both POSIX- and Win32-style paths. Directory delimiters may be either forward slashes or backslashes. Paths using backslashes or starting with a drive letter are always handled as Win32 paths. POSIX paths must only use forward slash
cygwin.com
cygwin에서 native symlinks를 생성하는 방법은,
위와같이 export CYGWIN=winsymlinks:lnk 작업을 거친 후 심볼릭 링크를 생성하면 된다.
gist.github.com/karlding/7868aac0c54fe94b7ba0a2061e0a4939
making cygwin use native symlinks
making cygwin use native symlinks. GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
그 후 생성된 심볼릭링크파일을 업로드 후 다운로드 하면 플래그를 획득 할 수 있다.
FLAG = PCTF{1f_every_symlink_w3r3_perfect_w3_wouldnt_have_m1sc_problems}
반응형