과제 목표
- app.py 코드리뷰
(1) 아래의 코드들의 어떤 url들로 연결이 가능한지 설명하기
(2) 각 엔드포인트(@app.route)가 수행하는 주요 기능에 대해 설명
(3) 각 기능을 수행하기 위해 사용되는 SQL 쿼리를 이해하고 설명
(4) 이용자가 POST /write_post 엔드포인트를 통해 게시글을 생성하는 경우, 브라우저, 웹 서비스, 그리고 데이터베이스 간의 상호작용 과정을 상세히 설명
#!/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
- /
- /board
- /board/<post_id>
- /write_post
- /modify_post
- /delete_post
(2) 각 엔드포인트(@app.route)가 수행하는 주요 기능
- / : /board api로 리다이렉션
- /board : 최근에 등록된 글 순으로 페이징 처리를 하여 게시판 반환
- /board/<post_id> : 유효한 post_id가 path variable로 넘어왔다면 해당 게시글 반환, 아니라면 클라이언트 측 에러 코드 반환.
- /write_post : 게시글 작성 기능. 작성이 완료되면 /board api로 리다이렉션.
- /modify_post : 게시글 수정 기능을 수행하고 수정이 완료되면 /board/{post_id} api로 리다이렉션. 해당 게시글이 없다면 클라이언트 측 에러 코드 반환.
- /delete_post : 정말 삭제하고 싶은지 사용자에게 묻고 'y'인 경우에 게시글 삭제 후 /board api로 리다이렉션, 'y'가 아닌 경우에는 /board/{post_id} api로 리다이렉션. 해당 게시글이 없다면 클라이언트 측 에러 코드 반환.
(3) 각 기능을 수행하기 위해 사용되는 SQL 쿼리
- / : 없음
- /board : 'SELECT _id, title FROM posts ORDER BY _id DESC LIMIT %s, %s' 쿼리가 사용되었으며 페이지의 시작 위치와 한 페이지당 보여줄 항목 수를 넘겨받아 posts 테이블에서 _id와 title 컬럼을 선택함. _id를 기준으로 내림차순 정렬한 결과를 가져옴. 결국 최근 등록된 글 순으로 페이징 처리된 데이터를 가져오는 역할을 수행함.
- /board/<post_id> : 'SELECT title, content FROM posts WHERE _id = %s' 쿼리가 사용되었으며 post id 값을 통해 posts 테이블에서 _id와 title 컬럼을 선택함. 게시글 상세 조회 기능에 해당함.
- /write_post : 'INSERT INTO posts (title, content) VALUES (%s, %s)' 쿼리가 사용되었으며 posts 테이블에 title과 content를 등록함. 게시글 등록 기능에 해당함.
- /modify_post : 'SELECT title, content FROM posts WHERE _id = %s' 쿼리가 사용되었으며 이는 /board/<post_id> api 속에서 사용되는 쿼리와 동일한 기능을 수행함. 또 'UPDATE posts SET title=%s, content=%s WHERE _id = %s' 쿼리가 사용되었는데 이는 post id에 해당하는 게시글의 제목이나 내용을 수정하는 역할을 수행함.
- /delete_post : 'DELETE FROM posts WHERE _id = %s' 쿼리가 사용되었으며 post id에 해당하는 게시글을 삭제하는 역할을 수행함.
(4) 이용자가 POST /write_post 엔드포인트를 통해 게시글을 생성하는 경우, 브라우저, 웹 서비스, 그리고 데이터베이스 간의 상호작용 과정
1. 클라이언트 측에서 /write_post api를 POST 메소드를 통해 호출
2. 클라이언트가 보낸 form 데이터에 접근
3. 'title'이나 'content'가 form 데이터에 없다면 write_post.html을 다시 렌더링
4. DB connection
5. 'INSERT INTO posts (title, content) VALUES (%s, %s)' 쿼리와 conn.commit()을 통해 DB에 변경 사항 반영(posts 테이블에 게시글 등록)
6. (예외 발생 시 400번 에러 코드 반환)
7. DB connection 종료
8. /board 페이지로 리다이렉션
'Security > Web Hacking' 카테고리의 다른 글
xss-2 풀이 (0) | 2025.02.03 |
---|---|
드림핵 cookie문제 풀이 (0) | 2025.01.22 |
웹해킹 기초 문제 풀이 - 1 (0) | 2025.01.22 |
웹 해킹 기초 (0) | 2025.01.09 |
전공 세미나 수업 - 해킹과 SQL 인젝션에 대하여 (0) | 2022.12.22 |