1. ๋ฌธ์
https://dreamhack.io/wargame/challenges/37
file-download-1
File Download ์ทจ์ฝ์ ์ด ์กด์ฌํ๋ ์น ์๋น์ค์ ๋๋ค. flag.py๋ฅผ ๋ค์ด๋ก๋ ๋ฐ์ผ๋ฉด ํ๋๊ทธ๋ฅผ ํ๋ํ ์ ์์ต๋๋ค. Reference Introduction of Webhacking
dreamhack.io
File Download ์ทจ์ฝ์ ์ด ์กด์ฌํ๋ ์น ์๋น์ค๋ผ๊ณ ์ ์๋์ด ์๋ค.
2. ํด๊ฒฐ ๊ณผ์
(1) ๋ฌธ์ ํ์ผ ๋ค์ด๋ก๋
(2) ์ฝ๋ ํ์ธ
- app.py ํ์ผ ํ์ธ
#!/usr/bin/env python3
import os
import shutil
from flask import Flask, request, render_template, redirect
from flag import FLAG
APP = Flask(__name__)
UPLOAD_DIR = 'uploads'
@APP.route('/')
def index():
files = os.listdir(UPLOAD_DIR)
return render_template('index.html', files=files)
@APP.route('/upload', methods=['GET', 'POST'])
def upload_memo():
if request.method == 'POST':
filename = request.form.get('filename')
content = request.form.get('content').encode('utf-8')
if filename.find('..') != -1:
return render_template('upload_result.html', data='bad characters,,')
with open(f'{UPLOAD_DIR}/{filename}', 'wb') as f:
f.write(content)
return redirect('/')
return render_template('upload.html')
@APP.route('/read')
def read_memo():
error = False
data = b''
filename = request.args.get('name', '')
try:
with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
data = f.read()
except (IsADirectoryError, FileNotFoundError):
error = True
return render_template('read.html',
filename=filename,
content=data.decode('utf-8'),
error=error)
if __name__ == '__main__':
if os.path.exists(UPLOAD_DIR):
shutil.rmtree(UPLOAD_DIR)
os.mkdir(UPLOAD_DIR)
APP.run(host='0.0.0.0', port=8000)
/upload
- upload ํ์ด์ง์์๋ post ๋ฅผ ํตํด file name๊ณผ context๋ฅผ ํฌํจํ์ฌ ๊ธ์ ์๋ฒ์ ๋ฑ๋กํ ์ ์๋ค.
- ์ด๋, ํ์ผ ๋ช ์ ..์ด ์๋์ง ํ์ธํ๋ค. (..์ด ์์ ๊ฒฝ์ฐ, ์์ ๊ฒฝ๋ก์ ์ ๊ทผํ ์ ์์ผ๋ฏ๋ก ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํจ์ด๋ค)
@APP.route('/upload', methods=['GET', 'POST'])
def upload_memo():
if request.method == 'POST':
filename = request.form.get('filename')
content = request.form.get('content').encode('utf-8')
if filename.find('..') != -1: # filename์ '..'์ด ๋ฐ๊ฒฌ๋์ง ์์ ๊ฒฝ์ฐ์๋ง if๋ฌธ ํ์ถ
return render_template('upload_result.html', data='bad characters,,')
with open(f'{UPLOAD_DIR}/{filename}', 'wb') as f:
f.write(content)
return redirect('/')
return render_template('upload.html')
/read
- name์ ๋ฐ์์ ์๋ฒ์์ ํ์ผ์ ์ฝ์ด์ค๋ ํ์ด์ง์ด๋ค.
- ์น ํ์ด์ง์ ํ์ผ ๋ด์ฉ์ ํ์ํ ๋, ์์ ๋ค๋ฅด๊ฒ ๋ค์ด๋ก๋ ๋๋ ํ์ผ์ ๋ํด ์ด๋ ํ ๊ฒ์ฌ๋ ํ์ง ์์ผ๋ฏ๋ก ํ์ผ ๋ค์ด๋ก๋ ๊ณต๊ฒฉ์ ์ทจ์ฝํ๋ค.
@APP.route('/read')
def read_memo():
error = False
data = b''
filename = request.args.get('name', '')
try:
with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
data = f.read()
except (IsADirectoryError, FileNotFoundError):
error = True
return render_template('read.html',
filename=filename,
content=data.decode('utf-8'),
error=error)
(3) ํ์ด์ง ์ ์
http://host3.dreamhack.games:13703/
Upload My Memo ์ ์
- ํ์ผ๋ช ๊ณผ ๋ด์ฉ์ form์ผ๋ก ์ ๋ ฅ๋ฐ๋ ํ๋ฉด์ด ๋ณด์ธ๋ค.
์๋ฌด๋ด์ฉ์ด๋ ์ ๋ ฅํด๋ณด๊ณ uploadํด๋ณธ๋ค.
๋ด๊ฐ ์ ๋ก๋ํ ํ์ผ์ด ๋ฆฌ์คํธ๋ก ๋ณด์ด๊ณ , ํด๋ฆญํ๋ฉด ๋ฉ๋ชจ ๋ด์ฉ์ ํ์ธํ ์ ์๋ค.
- http://host3.dreamhack.games:13703/read?name=first
- ์์ ์ฝ๋์์ ๋ถ์ํ๋ ๊ฒ์ฒ๋ผ, ํ์ผ์ ์ฝ์ด์ฌ ๋(๋ค์ด๋ก๋ ํ ๋)
(3) ๊ฒฝ๋ก ์ฐํ
๋ฐ๋ผ์ read ํ์ด์ง๋ฅผ ์ด์ฉํ์ฌ '../' ๋ฅผ ์ ๋ ฅํด์ ๊ฒฝ๋ก ์ฐํ๋ฅผ ์๋ํ๋ค.
- http://host3.dreamhack.games:13703/read?name=../flag.py
3. ๋์ ๋ฐฉ๋ฒ
์ ํ์ด์ง์์ ํด๋น ์ทจ์ฝ์ ์ ๊ณต๊ฒฉํ ์ ์์๋ ๊ฐ์ฅ ํฐ ์ด์ ๋ ์ ๋ก๋์ ๋ฌ๋ฆฌ ๋ค์ด๋ก๋ ํ์ด์ง์์๋ '..'์ ํํฐ๋งํ์ง ์์๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ผ์ ๋ค์ด๋ก๋ ํ์ด์ง์์ ..์ ํํฐ๋ง ํ์ฌ ๊ฒฝ๋ก ์ฐํ ๊ณต๊ฒฉ์ ๋ง์์ผ ํ๋ค.
๋ํ, .. ๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ํตํด ์ฐํํ ์ ์์ผ๋ฏ๋ก ๋์ฑ ์๊ฒฉํ ํํฐ๋ง์ด ์์ด์ผ ํ ๊ฒ์ด๋ค.