-
1-1. 개발자 도구의 Sources 탭 기능을 활용해 플래그를 찾아보세요. 플래그 형식은 DH{...} 입니다.
-
DH{2ed07940b6fd9b0731ef698a5f0c065be9398f7fa00f03ed9da586c3ed1d54d5}
-
1-2. 해당 파일의 app.py 코드 해석 (코드리뷰)
-
(1) 연결 가능한 URL
-
(2) 각 엔드포인트 기능 설명
-
(3) 사용되는 SQL 쿼리 설명
-
(4) POST /board/<post_id> 사용 시 브라우저, 웹 서비스, 데이터베이스 간의 상호작용 과정
-
-
2-1 https://dreamhack.io/wargame/challenges/6 개발자 도구랑 버프슈트로 풀어보기!!
-
DH{7952074b69ee388ab45432737f9b0c56}
1-1. 개발자 도구의 Sources 탭 기능을 활용해 플래그를 찾아보세요. 플래그 형식은 DH{...} 입니다.
플래그 위치 : main.4c6e144e.map
DH{2ed07940b6fd9b0731ef698a5f0c065be9398f7fa00f03ed9da586c3ed1d54d5}

1-2. 해당 파일의 app.py 코드 해석 (코드리뷰)
#!/usr/bin/env python3
import os
import pymysql
from flask import Flask, abort, redirect, render_template, request
PAGINATION_SIZE = 10
app = Flask(__name__)
app.secret_key = os.urandom(32)
def connect_mysql():
conn = pymysql.connect(host='db',
port=3306,
user=os.environ['MYSQL_USER'],
passwd=os.environ['MYSQL_PASSWORD'],
db='board',
charset='utf8mb4')
cursor = conn.cursor()
return conn, cursor
@app.route('/')
def index():
return redirect('/board')
@app.route('/board')
def board():
page = request.args.get('page')
page = int(page) if page and page.isdigit() and int(page) > 0 else 1
ret = []
conn, cursor = connect_mysql()
try:
query = 'SELECT _id, title FROM posts ORDER BY _id DESC LIMIT %s, %s'
cursor.execute(query, ((page - 1) * PAGINATION_SIZE, PAGINATION_SIZE))
ret = cursor.fetchall()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
return render_template('board.html', page=page, ret=ret)
@app.route('/board/<post_id>')
def board_post(post_id):
if not post_id or not post_id.isdigit() or int(post_id) < 1:
abort(400)
ret = None
conn, cursor = connect_mysql()
try:
query = 'SELECT title, content FROM posts WHERE _id = %s'
cursor.execute(query, (post_id))
ret = cursor.fetchone()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
if not ret:
abort(404)
return render_template('post.html', title=ret[0],
content=ret[1], post_id=post_id)
@app.route('/write_post', methods=['POST'])
def write_post():
if 'title' not in request.form or 'content' not in request.form:
return render_template('write_post.html')
title = request.form['title']
content = request.form['content']
conn, cursor = connect_mysql()
try:
query = 'INSERT INTO posts (title, content) VALUES (%s, %s)'
cursor.execute(query, (title, content))
conn.commit()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
return redirect('/board')
@app.route('/modify_post', methods=['POST'])
def modify_post():
post_id = request.form['post_id']
if not post_id or not post_id.isdigit() or int(post_id) < 1:
abort(400)
if 'title' not in request.form or 'content' not in request.form:
conn, cursor = connect_mysql()
try:
query = 'SELECT title, content FROM posts WHERE _id = %s'
cursor.execute(query, (post_id, ))
ret = cursor.fetchone()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
if not ret:
abort(404)
return render_template('modify_post.html', title=ret[0],
content=ret[1], post_id=post_id)
title = request.form['title']
content = request.form['content']
conn, cursor = connect_mysql()
try:
query = 'UPDATE posts SET title=%s, content=%s WHERE _id = %s'
cursor.execute(query, (title, content, post_id, ))
conn.commit()
except Exception as e:
print(e, flush=True)
finally:
cursor.close()
conn.close()
return redirect(f'/board/{post_id}')
@app.route('/delete_post', methods=['POST'])
def delete_post():
post_id = request.form['post_id']
if not post_id or not post_id.isdigit() or int(post_id) < 1:
abort(400)
if 'answer' not in request.form:
return render_template('delete_post.html', post_id=post_id)
if request.form['answer'] == 'y':
conn, cursor = connect_mysql()
try:
query = 'DELETE FROM posts WHERE _id = %s'
cursor.execute(query, (post_id, ))
conn.commit()
except Exception as e:
print(e, flush=True)
finally:
cursor.close()
conn.close()
return redirect('/board')
return redirect(f'/board/{post_id}')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
(1) 연결 가능한 URL
- /: 기본 URL로, /board로 리다이렉트
- /board: 게시판 목록을 출력
- 쿼리 파라미터 page로 페이지를 이동할 수 있음
- /board/<post_id>: 특정 게시글의 내용을 보여줌
- /write_post: 게시글 작성 페이지로 이동하거나, POST 요청 시 새 게시글을 생성
- /modify_post: 특정 게시글 수정 페이지로 이동하거나, POST 요청 시 게시글을 수정
- /delete_post: 특정 게시글 삭제 확인 페이지로 이동하거나, POST 요청 시 게시글을 삭제
(2) 각 엔드포인트 기능 설명
- index: 기본 페이지로, /board로 리다이렉트
- board: 게시판 목록을 불러와 board.html에 렌더링
- board_post: 특정 게시글의 제목과 내용을 불러와 post.html에 렌더링
- write_post: 게시글 작성 페이지를 보여주거나, POST 요청으로 새로운 게시글을 데이터베이스에 추가
- modify_post: 특정 게시글 수정 페이지를 보여주거나, POST 요청으로 해당 게시글을 수정
- delete_post: 특정 게시글 삭제 확인 페이지를 보여주거나, POST 요청으로 해당 게시글을 삭제
(3) 사용되는 SQL 쿼리 설명
SELECT _id, title FROM posts ORDER BY _id DESC LIMIT %s, %s;
게시글 목록 조회 (board 엔드포인트)
- 최신 게시글 순으로 정렬하여 페이지 단위로 _id와 title을 조회
- LIMIT는 페이지 번호와 페이지 크기를 기반으로 데이터 범위를 제한
SELECT title, content FROM posts WHERE _id = %s;
특정 게시글 조회 (board_post 엔드포인트)
- 특정 게시글의 _id를 기반으로 title과 content를 조회
INSERT INTO posts (title, content) VALUES (%s, %s);
새 게시글 추가 (write_post 엔드포인트)
- 새 게시글의 title과 content를 데이터베이스에 삽입합니다.
UPDATE posts SET title=%s, content=%s WHERE _id = %s;
게시글 수정 (modify_post 엔드포인트)
- 게시글의 title과 content를 수정합니다. 수정할 게시글은 _id로 식별됩니다.
DELETE FROM posts WHERE _id = %s;
게시글 삭제 (delete_post 엔드포인트)
- 특정 게시글을 _id를 기준으로 삭제합니다.
(4) POST /board/<post_id> 사용 시 브라우저, 웹 서비스, 데이터베이스 간의 상호작용 과정
- 브라우저
- 사용자가 write_post.html에서 제목과 내용을 입력하고 "post" 버튼을 클릭
- 브라우저는 입력된 데이터를 HTTP POST 요청으로 /write_post URL에 전송
- 웹 서비스 (Flask)
- Flask는 write_post 엔드포인트를 처리
- POST 요청의 form 데이터를 읽고, 제목(title)과 내용(content) 값을 추출
- 데이터베이스에 삽입하는 SQL 쿼리(INSERT INTO posts ...)를 실행
- 삽입이 완료되면 /board로 리다이렉트하여 게시판 목록을 다시 보여
- 데이터베이스 (MySQL)
- Flask에서 전달받은 쿼리를 실행하여 posts 테이블에 새 레코드를 추가
- 삽입이 성공하면 변경 사항을 커밋
- 결과 반환
- 웹 서비스는 브라우저에 리다이렉트 응답을 반환
- 브라우저는 /board 페이지를 새로고침하여 업데이트된 게시글 목록을 표시
2-1 https://dreamhack.io/wargame/challenges/6 개발자 도구랑 버프슈트로 풀어보기!!
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
app = Flask(__name__)
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
users = {
'guest': 'guest',
'admin': FLAG
}
@app.route('/')
def index():
username = request.cookies.get('username', None)
if username:
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
try:
pw = users[username]
except:
return '<script>alert("not found user");history.go(-1);</script>'
if pw == password:
resp = make_response(redirect(url_for('index')) )
resp.set_cookie('username', username)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
app.run(host='0.0.0.0', port=8000)
코드를 분석해보면 guest를 통해 웹사이트 상에서 로그인하여 guest로 로그인을 할 수 있는 것을 알 수 있다
여기서 판별자가 username의 쿠키값을 받아와서 판별을 하게 되는데 웹사이트 상에서 guest로 로그인을 한 뒤, 개발자 도구를 켜서 웹사이트의 쿠키가 admin으로 인식할 수 있도록 username의 값을 admin으로 설정을 한 뒤, 새로고침을 해보면 플래그 값이 띄워지는것을 볼 수 있다.

DH{7952074b69ee388ab45432737f9b0c56}
1-1. 개발자 도구의 Sources 탭 기능을 활용해 플래그를 찾아보세요. 플래그 형식은 DH{...} 입니다.
플래그 위치 : main.4c6e144e.map
DH{2ed07940b6fd9b0731ef698a5f0c065be9398f7fa00f03ed9da586c3ed1d54d5}

1-2. 해당 파일의 app.py 코드 해석 (코드리뷰)
#!/usr/bin/env python3
import os
import pymysql
from flask import Flask, abort, redirect, render_template, request
PAGINATION_SIZE = 10
app = Flask(__name__)
app.secret_key = os.urandom(32)
def connect_mysql():
conn = pymysql.connect(host='db',
port=3306,
user=os.environ['MYSQL_USER'],
passwd=os.environ['MYSQL_PASSWORD'],
db='board',
charset='utf8mb4')
cursor = conn.cursor()
return conn, cursor
@app.route('/')
def index():
return redirect('/board')
@app.route('/board')
def board():
page = request.args.get('page')
page = int(page) if page and page.isdigit() and int(page) > 0 else 1
ret = []
conn, cursor = connect_mysql()
try:
query = 'SELECT _id, title FROM posts ORDER BY _id DESC LIMIT %s, %s'
cursor.execute(query, ((page - 1) * PAGINATION_SIZE, PAGINATION_SIZE))
ret = cursor.fetchall()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
return render_template('board.html', page=page, ret=ret)
@app.route('/board/<post_id>')
def board_post(post_id):
if not post_id or not post_id.isdigit() or int(post_id) < 1:
abort(400)
ret = None
conn, cursor = connect_mysql()
try:
query = 'SELECT title, content FROM posts WHERE _id = %s'
cursor.execute(query, (post_id))
ret = cursor.fetchone()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
if not ret:
abort(404)
return render_template('post.html', title=ret[0],
content=ret[1], post_id=post_id)
@app.route('/write_post', methods=['POST'])
def write_post():
if 'title' not in request.form or 'content' not in request.form:
return render_template('write_post.html')
title = request.form['title']
content = request.form['content']
conn, cursor = connect_mysql()
try:
query = 'INSERT INTO posts (title, content) VALUES (%s, %s)'
cursor.execute(query, (title, content))
conn.commit()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
return redirect('/board')
@app.route('/modify_post', methods=['POST'])
def modify_post():
post_id = request.form['post_id']
if not post_id or not post_id.isdigit() or int(post_id) < 1:
abort(400)
if 'title' not in request.form or 'content' not in request.form:
conn, cursor = connect_mysql()
try:
query = 'SELECT title, content FROM posts WHERE _id = %s'
cursor.execute(query, (post_id, ))
ret = cursor.fetchone()
except Exception as e:
print(e, flush=True)
abort(400)
finally:
cursor.close()
conn.close()
if not ret:
abort(404)
return render_template('modify_post.html', title=ret[0],
content=ret[1], post_id=post_id)
title = request.form['title']
content = request.form['content']
conn, cursor = connect_mysql()
try:
query = 'UPDATE posts SET title=%s, content=%s WHERE _id = %s'
cursor.execute(query, (title, content, post_id, ))
conn.commit()
except Exception as e:
print(e, flush=True)
finally:
cursor.close()
conn.close()
return redirect(f'/board/{post_id}')
@app.route('/delete_post', methods=['POST'])
def delete_post():
post_id = request.form['post_id']
if not post_id or not post_id.isdigit() or int(post_id) < 1:
abort(400)
if 'answer' not in request.form:
return render_template('delete_post.html', post_id=post_id)
if request.form['answer'] == 'y':
conn, cursor = connect_mysql()
try:
query = 'DELETE FROM posts WHERE _id = %s'
cursor.execute(query, (post_id, ))
conn.commit()
except Exception as e:
print(e, flush=True)
finally:
cursor.close()
conn.close()
return redirect('/board')
return redirect(f'/board/{post_id}')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
(1) 연결 가능한 URL
- /: 기본 URL로, /board로 리다이렉트
- /board: 게시판 목록을 출력
- 쿼리 파라미터 page로 페이지를 이동할 수 있음
- /board/<post_id>: 특정 게시글의 내용을 보여줌
- /write_post: 게시글 작성 페이지로 이동하거나, POST 요청 시 새 게시글을 생성
- /modify_post: 특정 게시글 수정 페이지로 이동하거나, POST 요청 시 게시글을 수정
- /delete_post: 특정 게시글 삭제 확인 페이지로 이동하거나, POST 요청 시 게시글을 삭제
(2) 각 엔드포인트 기능 설명
- index: 기본 페이지로, /board로 리다이렉트
- board: 게시판 목록을 불러와 board.html에 렌더링
- board_post: 특정 게시글의 제목과 내용을 불러와 post.html에 렌더링
- write_post: 게시글 작성 페이지를 보여주거나, POST 요청으로 새로운 게시글을 데이터베이스에 추가
- modify_post: 특정 게시글 수정 페이지를 보여주거나, POST 요청으로 해당 게시글을 수정
- delete_post: 특정 게시글 삭제 확인 페이지를 보여주거나, POST 요청으로 해당 게시글을 삭제
(3) 사용되는 SQL 쿼리 설명
SELECT _id, title FROM posts ORDER BY _id DESC LIMIT %s, %s;
게시글 목록 조회 (board 엔드포인트)
- 최신 게시글 순으로 정렬하여 페이지 단위로 _id와 title을 조회
- LIMIT는 페이지 번호와 페이지 크기를 기반으로 데이터 범위를 제한
SELECT title, content FROM posts WHERE _id = %s;
특정 게시글 조회 (board_post 엔드포인트)
- 특정 게시글의 _id를 기반으로 title과 content를 조회
INSERT INTO posts (title, content) VALUES (%s, %s);
새 게시글 추가 (write_post 엔드포인트)
- 새 게시글의 title과 content를 데이터베이스에 삽입합니다.
UPDATE posts SET title=%s, content=%s WHERE _id = %s;
게시글 수정 (modify_post 엔드포인트)
- 게시글의 title과 content를 수정합니다. 수정할 게시글은 _id로 식별됩니다.
DELETE FROM posts WHERE _id = %s;
게시글 삭제 (delete_post 엔드포인트)
- 특정 게시글을 _id를 기준으로 삭제합니다.
(4) POST /board/<post_id> 사용 시 브라우저, 웹 서비스, 데이터베이스 간의 상호작용 과정
- 브라우저
- 사용자가 write_post.html에서 제목과 내용을 입력하고 "post" 버튼을 클릭
- 브라우저는 입력된 데이터를 HTTP POST 요청으로 /write_post URL에 전송
- 웹 서비스 (Flask)
- Flask는 write_post 엔드포인트를 처리
- POST 요청의 form 데이터를 읽고, 제목(title)과 내용(content) 값을 추출
- 데이터베이스에 삽입하는 SQL 쿼리(INSERT INTO posts ...)를 실행
- 삽입이 완료되면 /board로 리다이렉트하여 게시판 목록을 다시 보여
- 데이터베이스 (MySQL)
- Flask에서 전달받은 쿼리를 실행하여 posts 테이블에 새 레코드를 추가
- 삽입이 성공하면 변경 사항을 커밋
- 결과 반환
- 웹 서비스는 브라우저에 리다이렉트 응답을 반환
- 브라우저는 /board 페이지를 새로고침하여 업데이트된 게시글 목록을 표시
2-1 https://dreamhack.io/wargame/challenges/6 개발자 도구랑 버프슈트로 풀어보기!!
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
app = Flask(__name__)
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
users = {
'guest': 'guest',
'admin': FLAG
}
@app.route('/')
def index():
username = request.cookies.get('username', None)
if username:
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
try:
pw = users[username]
except:
return '<script>alert("not found user");history.go(-1);</script>'
if pw == password:
resp = make_response(redirect(url_for('index')) )
resp.set_cookie('username', username)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
app.run(host='0.0.0.0', port=8000)
코드를 분석해보면 guest를 통해 웹사이트 상에서 로그인하여 guest로 로그인을 할 수 있는 것을 알 수 있다
여기서 판별자가 username의 쿠키값을 받아와서 판별을 하게 되는데 웹사이트 상에서 guest로 로그인을 한 뒤, 개발자 도구를 켜서 웹사이트의 쿠키가 admin으로 인식할 수 있도록 username의 값을 admin으로 설정을 한 뒤, 새로고침을 해보면 플래그 값이 띄워지는것을 볼 수 있다.

DH{7952074b69ee388ab45432737f9b0c56}