[Dreamhack] simple_sqli

2025. 3. 15. 16:24·Webhacking/Dreamhack
 

simple_sqli

로그인 서비스입니다. SQL INJECTION 취약점을 통해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다. Reference Server-side Basic

dreamhack.io


app.py

#!/usr/bin/python3
from flask import Flask, request, render_template, g
import sqlite3
import os
import binascii

app = Flask(__name__)
app.secret_key = os.urandom(32)

try:
    FLAG = open('./flag.txt', 'r').read()
except:
    FLAG = '[**FLAG**]'

DATABASE = "database.db"
if os.path.exists(DATABASE) == False:
    db = sqlite3.connect(DATABASE)
    db.execute('create table users(userid char(100), userpassword char(100));')
    db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')
    db.commit()
    db.close()

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

def query_db(query, one=True):
    cur = get_db().execute(query)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

@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')
        userpassword = request.form.get('userpassword')
        res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
        if res:
            userid = res[0]
            if userid == 'admin':
                return f'hello {userid} flag is {FLAG}'
            return f'<script>alert("hello {userid}");history.go(-1);</script>'
        return '<script>alert("wrong");history.go(-1);</script>'

app.run(host='0.0.0.0', port=8000)

코드 설명

사전 탐색 & 정찰 (Reconnaissance)

  • /index 와 /login 두 탭이 존재하는 것이 확인 가능하다.
  • guest 계정, admin 계정 두가지가 생성되어 있음을 확인 가능하다.

스캐닝 및 취약점 분석 (Scanning & Enumeration)

DATABASE = "database.db"
if os.path.exists(DATABASE) == False:
    db = sqlite3.connect(DATABASE)
    db.execute('create table users(userid char(100), userpassword char(100));')
    db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')
    db.commit()
    db.close()
binascii.helify : binary 데이터 16진수 표현법
  • 위의 코드를 통해 db에 query 문이 삽입되어 계정이 생성되었음을 확인 가능하다.
  • admin 계정의 암호는 dict 형태로 16진수 난수 형태로 생성됨을 확인 가능하다.
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        userid = request.form.get('userid')
        userpassword = request.form.get('userpassword')
        res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
        if res:
            userid = res[0]
            if userid == 'admin':
                return f'hello {userid} flag is {FLAG}'
            return f'<script>alert("hello {userid}");history.go(-1);</script>'
        return '<script>alert("wrong");history.go(-1);</script>'

res 값이 userid 와 userpassword 값이 담겨 반환되는 것을 확인할 수 있다.

즉, admin 계정으로 로그인하여 기본적인 Injection을 통해 관리자(admin) 계정 탈취 or 조회하는 방식이 아닌 단순 로그인 문제이다.
따로 인증 우회를 하도록 Injection 방지 코드 or SQL 구문 필터링이 없기 때문에 간단한 SQL Injection을 통해 우회 가능하다.

문제 풀이

메인 페이지이다.

login 클릭 후, guest : guest 입력 시 hello guest 라는 alert 창이 뜬다.

해당 alert 창으로 로그인이 잘 되는 것을 확인할 수 있다.

login 클릭 후, admin : admin 입력 시 wrong 이라는 alert 창이 뜬다.

해당 alert 창으로 로그인이 잘 되지 않는 것을 확인할 수 있다.

-> admin 계정의 암호는 난수 값 (random) 이므로 서비스 이용자는 우리는 알 수 없다.

익스플로잇

//SQL의 원리
'1 'or' 1 '=' 1
1 'or' 1 'like' 1

//양쪽이 참값이 되는 코드를 query 문에 삽입

SELECT * FROM users WHERE userid="admin"--" AND userpassword="---"

//userid 가 admin 인 계정을 우리는 알고 있다.
//--(주석) 을 통해 뒤에 userpassword 조회 쿼리는 주석처리
//userid 조회 값은 뒤의 AND 구문이 사라지므로 (1) 참값

취약점 분석 항목에서 res에 담기는 쿼리문을 살펴 보았을 때

'select * from users where userid="{userid}" and userpassword="{userpassword}"’

위와 같은 쿼리문이 결과로 반환되는 것을 알 수 있다.

  • 이미 admin 계정의 userid 값이 admin인 것을 알고 있다.
  • 취약점 분석 탭에서 admin 계정이 생성되어 있는 것을 확인할 수 있다.
SQL 문의 주석 처리 문법인 - - 을 통해 ID 검색 조건만 처리하도록 하며, 뒤의 userpassword 조회 부분은 주석처리해버리면 참값이 나온다.

userid 에 admin 계정, "- - 주석처리 문을 삽입한다.

SELECT * FROM users WHERE userid="admin"-- " AND userpassword="??"

res 에 위의 쿼리문이 담긴다.

-> 쿼리 문은 참값으로 판정 된다.

결과

정상적으로 flag 값 도출을 성공할 수 있다.

저작자표시 비영리 동일조건 (새창열림)

'Webhacking > Dreamhack' 카테고리의 다른 글

[Dreamhack] command-injection-1  (0) 2025.03.16
[Dreamhack] Mango  (0) 2025.03.15
[Dreamhack] csrf-2  (0) 2025.03.15
[Dreamhack] csrf-1  (0) 2025.03.15
[Dreamhack] xss-2  (0) 2025.03.15
'Webhacking/Dreamhack' 카테고리의 다른 글
  • [Dreamhack] command-injection-1
  • [Dreamhack] Mango
  • [Dreamhack] csrf-2
  • [Dreamhack] csrf-1
배움이 머무는 곳
배움이 머무는 곳
  • 배움이 머무는 곳
    wlgus
    배움이 머무는 곳
  • 전체
    오늘
    어제
    • 분류 전체보기 (68) N
      • 이것저것.zip (7)
      • CVE (6)
      • CTF (2)
      • Wargame (23) N
      • Webhacking (19)
        • WebGoat (2)
        • Dreamhack (15)
      • Web (5)
      • Pwnable (5)
        • Dreamhack (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • hELLO· Designed By정상우.v4.10.5
배움이 머무는 곳
[Dreamhack] simple_sqli
상단으로

티스토리툴바