1. 엔트포인트 분석
/vuln
csrf1과 마찬가지로 파라미터를 소문자로 변경 후에 xss 공격을 막기 위해 필터링을 합니다.
@app.route("/vuln")
def vuln():
param = request.args.get("param", "").lower()
# 이용자가 입력한 param 파라미터를 소문자로 변경
xss_filter = ["frame", "script", "on"]
# 세 가지 필터링 키워드
for _ in xss_filter:
param = param.replace(_, "*")
# 이용자가 입력한 값 중에 필터링 키워드가 있는 경우, '*'로 치환
return param
/flag
GET 요청일 경우에는 flag 페이지를 렌더링하고,
POST 요청일 경우 세션 ID를 무작위로 생성하여 admin 값의 세션 ID 로 저장한다. 그리고 만약 토큰이 유효하지 않다면 wrong?? 이라는 알람을 띄운다.
@app.route("/flag", methods=["GET", "POST"])
# flag 페이지 라우팅 (GET, POST 요청을 모두 받음)
def flag():
if request.method == "GET":
return render_template("flag.html")
elif request.method == "POST":
param = request.form.get("param", "")
session_id = os.urandom(16).hex()
# 무작위 세션 ID 생성 후 16진수 문자열로 변환
session_storage[session_id] = 'admin'
# 세션 ID를 키로 사용하여 'admin' 값을 session_storage 딕셔너리에 저장
if not check_csrf(param, {"name":"sessionid", "value": session_id}):
# CSRF 토큰 (세션 ID)이 유효한지 확인
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
/login
GET 요청일 경우 login 페이지로 렌더링되며,
POST 요청일 경우 사용자가 폼에 입력한 사용자 이름과 비밀번호를 가지고 온다. 가져온 후에는 사용자가 입력한 이름이 존재하는지 확인하고, 존재한다면 비밀번호가 일치하는지 검사한다. 로그인에 성공할 시에 사용자에 해당하는 세션 아이디를 무작위로 발급하여 저장 후 사용자에게 전달한다. 사용자는 index 페이지로 리다이렉트 된다.
@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')
# POST 요청의 form 데이터에서 'username'을 가져옴
password = request.form.get('password')
# POST 요청의 form 데이터에서 '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')))
session_id = os.urandom(8).hex()
# 무작위 세션 ID를 생성하고 16진수 문자열로 변환
session_storage[session_id] = username
# 세션 ID를 키로 사용하여 현재 사용자를 'session_storage'에 저장
resp.set_cookie('sessionid', session_id)
# 생성된 세션 ID를 쿠키로 설정하여 사용자에게 전달
return resp # 로그인이 성공한 경우 리디렉션 응답을 반환
return '<script>alert("wrong password");history.go(-1);</script>'
# 비밀번호가 일치하지 않는 경우 경고를 표시 후 이전 페이지로 이동
/change_password
토큰이 유효한지 확인한 후에 사용자가 입력한 새로운 비밀번호로 변경됩니다.
@app.route("/change_password")
def change_password():
pw = request.args.get("pw", "")
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
# 세션 ID가 유효하지 않거나 세션을 찾을 수 없는 경우
users[username] = pw
# 세션에 연결된 사용자의 비밀번호를 'pw'로 변경
return 'Done'
2. 문제 풀이
/flag 엔드포인트에서 관리자의 토큰을 생성하기 때문에 해당 페이지에서 아래와 같은 공격 코드를 삽입해야 합니다.
<img src="/change_password?pw=admin">
해당 공격코드가 /vuln 엔드포이트에 삽입되게 된다면 admin의 비밀번호는 admin으로 변경되고,
로그인 페이지에서 변경된 비밀번호와 사용자 이름을 입력하게 되면 우리는 플래그 값을 얻을 수 있습니다.


1. 엔트포인트 분석
/vuln
csrf1과 마찬가지로 파라미터를 소문자로 변경 후에 xss 공격을 막기 위해 필터링을 합니다.
@app.route("/vuln") def vuln(): param = request.args.get("param", "").lower() # 이용자가 입력한 param 파라미터를 소문자로 변경 xss_filter = ["frame", "script", "on"] # 세 가지 필터링 키워드 for _ in xss_filter: param = param.replace(_, "*") # 이용자가 입력한 값 중에 필터링 키워드가 있는 경우, '*'로 치환 return param
/flag
GET 요청일 경우에는 flag 페이지를 렌더링하고,
POST 요청일 경우 세션 ID를 무작위로 생성하여 admin 값의 세션 ID 로 저장한다. 그리고 만약 토큰이 유효하지 않다면 wrong?? 이라는 알람을 띄운다.
@app.route("/flag", methods=["GET", "POST"]) # flag 페이지 라우팅 (GET, POST 요청을 모두 받음) def flag(): if request.method == "GET": return render_template("flag.html") elif request.method == "POST": param = request.form.get("param", "") session_id = os.urandom(16).hex() # 무작위 세션 ID 생성 후 16진수 문자열로 변환 session_storage[session_id] = 'admin' # 세션 ID를 키로 사용하여 'admin' 값을 session_storage 딕셔너리에 저장 if not check_csrf(param, {"name":"sessionid", "value": session_id}): # CSRF 토큰 (세션 ID)이 유효한지 확인 return '<script>alert("wrong??");history.go(-1);</script>' return '<script>alert("good");history.go(-1);</script>'
/login
GET 요청일 경우 login 페이지로 렌더링되며,
POST 요청일 경우 사용자가 폼에 입력한 사용자 이름과 비밀번호를 가지고 온다. 가져온 후에는 사용자가 입력한 이름이 존재하는지 확인하고, 존재한다면 비밀번호가 일치하는지 검사한다. 로그인에 성공할 시에 사용자에 해당하는 세션 아이디를 무작위로 발급하여 저장 후 사용자에게 전달한다. 사용자는 index 페이지로 리다이렉트 된다.
@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') # POST 요청의 form 데이터에서 'username'을 가져옴 password = request.form.get('password') # POST 요청의 form 데이터에서 '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'))) session_id = os.urandom(8).hex() # 무작위 세션 ID를 생성하고 16진수 문자열로 변환 session_storage[session_id] = username # 세션 ID를 키로 사용하여 현재 사용자를 'session_storage'에 저장 resp.set_cookie('sessionid', session_id) # 생성된 세션 ID를 쿠키로 설정하여 사용자에게 전달 return resp # 로그인이 성공한 경우 리디렉션 응답을 반환 return '<script>alert("wrong password");history.go(-1);</script>' # 비밀번호가 일치하지 않는 경우 경고를 표시 후 이전 페이지로 이동
/change_password
토큰이 유효한지 확인한 후에 사용자가 입력한 새로운 비밀번호로 변경됩니다.
@app.route("/change_password") def change_password(): pw = request.args.get("pw", "") session_id = request.cookies.get('sessionid', None) try: username = session_storage[session_id] except KeyError: return render_template('index.html', text='please login') # 세션 ID가 유효하지 않거나 세션을 찾을 수 없는 경우 users[username] = pw # 세션에 연결된 사용자의 비밀번호를 'pw'로 변경 return 'Done'
2. 문제 풀이
/flag 엔드포인트에서 관리자의 토큰을 생성하기 때문에 해당 페이지에서 아래와 같은 공격 코드를 삽입해야 합니다.
<img src="/change_password?pw=admin">
해당 공격코드가 /vuln 엔드포이트에 삽입되게 된다면 admin의 비밀번호는 admin으로 변경되고,
로그인 페이지에서 변경된 비밀번호와 사용자 이름을 입력하게 되면 우리는 플래그 값을 얻을 수 있습니다.

