[WARGAME][드림핵 CTF] login - 1 (풀었지만 이해 필요)

jckim22·2022년 12월 8일
0

[WEBHACKING] STUDY (WARGAME)

목록 보기
112/114

아래는 서버 코드이다.

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

먼저 페이지를 분석해보자.

뭐 별게 없다.

코드를 살펴보니 login, logout, /user/<int:useridx>, /admin 이라는 페이지가 숨겨져 있다.

아래는 login페이지이다.
sql injection을 시도해보았지만 코드 상에서 ? 를 이용한 sql문을 사용하기 때문에 내가 알기로는 쿼터에 관한 것을 자동으로 처리해주는 것으로 알고 있다.

따라서 sql injection은 거의 불가하다.

forgot password 페이지로 들어가 보았다.
아래를 보면 특이하게 backupcode를 요구한다.
이게 실마리가 될 것이라고 의심을 해본다.

계정 하나를 등록해보려고 Register페이지에 들어가서 가입을 해보았다.

아래처럼 BackupCode를 부여해준다.

코드를 살펴보니 sleep이 있다.
브루트포스를 방지하기 위함인 것 같다.

그리고 아래 코드에서는 userLevel이 있다.
1이면 admin이고 MAXRESETCOUNT라는 것이 있다.
일단 5인데 어쨌든 카운트가 되는 것 같다.

아직은 잘모르겠다.
BackupCode의 range는 100이다.
굉장히 짧으므로 역시 이걸로 푸는게 맞나보다.

아래는 또 특이한 주소가 있었다.
일단 접속해보겠다.

useridx를 1로 하고 접속해보았는데 그에 대한 유저의 정보가 나온다.
UserLevel이 1인 것을 보니 Apple은 admin인가 보다.
password를 안알려주니 파이썬 코드로 backupcode를 브루트 포싱 하면 풀리는 문제라고 생각했다.


파이썬 코드를 짜던 중 Count가 신경 쓰여서 아무 백업코드를 사용해서 해보았는데 아래처럼 Count가 하나 줄었다.

그럼 파이썬 스크립트로도 안되는 것이라고 생각했다.

그래도 일단 짜던 것이 있으니 짜보았는데.

import requests

url="http://host3.dreamhack.games:14328/forgot_password"



for i in range(100):
    data={
        'userid':'Apple',
        'newpassword':'1234',
        'backupCode':f'{i}'
    }

    res=requests.post(url,data)

    print(f"try{i}")

위와 같은 익스플로잇을 실행했다.

그리고 로그인 해보았는데...

admin계정으 login에 성공하였다.
분명 카운트가 5개인데 왜 성공한지는 아직도 의문이다.

CTF가 끝나게 되면 그 이유를 찾도록 하겠다.

얼떨결에 문제를 풀었지만 CTF이기 때문에 일단 /admin으로 접속해서 flag를 구할 수 있었다.

궁금증은 이후에 해소하겠다.

profile
개발/보안

0개의 댓글