본문 바로가기

카테고리 없음

[dreamhack] simple_sqli_chatgpt

728x90

문제

SQL INJECTION 취약점을 이용한 문제이다.

작년에 SQL Injection 개념을 배웠던 기억이 난다. 간단하게 개념 복습만 하고 문제를 풀어보자 ~ !

 

SQL Injection 이란?

악의적인 사용자가 보안상의 취약점을 이용하여, 임의의 SQL 문을 주입하고 실행되게 하여 데이터베이스가 비정상적인 동작을 하도록 조작하는 행위

 

풀이

문제 화면이다.

 

Login 을 클릭하면 위와 같은 화면이 나온다.

Home, About, Contact 모두 눌러도 아무 변화가 없다.

 

주어진 문제 화면은 이게 다라서 이제 문제 파일을 다운 받아 코드를 확인해보자.

#!/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), userlevel integer);')
    db.execute(f'insert into users(userid, userpassword, userlevel) values ("guest", "guest", 0), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}", 0);')
    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:
        userlevel = request.form.get('userlevel')
        res = query_db(f"select * from users where userlevel='{userlevel}'")
        if res:
            userid = res[0]
            userlevel = res[2]
            print(userid, userlevel)
            if userid == 'admin' and userlevel == 0:
                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)

전체 코드이다.

 

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        userlevel = request.form.get('userlevel')
        res = query_db(f"select * from users where userlevel='{userlevel}'")
        if res:
            userid = res[0]
            userlevel = res[2]
            print(userid, userlevel)
            if userid == 'admin' and userlevel == 0:
                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>'

이 부분이 로그인 페이지의 코드이다.

우리가 입력한 값이 쿼리문에 삽입되어서 query_db로 가는 것을 알 수 있다.

 

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

 

데이터베이스에서는 우리가 입력한 쿼리문을 배열로 입력해서 리턴해주고 있다.

 

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

 

데이터베이스 코드를 보면 users테이블에 guest를 먼저 넣고 admin을 넣는 것을 알 수 있다.

 

로그인 페이지에서는, 리턴받은 첫 번째 요소와 세 번째 요소를 검사하고있다.

이 값이 admin이며 level이 0인지 확인하여 맞다면 flag를 보여준다.

 

이제 sql injection 구문을 이용해보아야하는데, where와 and를 이용해보면 이렇게 쓸 수 있다.

0'and userid='admin

 

"select * from users where userlevel'{uselevel}'" 여기서 userid 값만 불러오면 되는데, 위와 같이 써주면 admin의 레벨도 0이기 때문에 0과 함께 userid가 admin인 요소를 찾아올 수 있다.

 

다른 방법으로 union select 이용해도 된다.

' union select * from users;-- -

union select는 결과의 값을 합하여 출력하여 주는 집합 연산자이다. 실행 결과 guest와 admin 모두 반환할 것이다. 하지만 기본적으로 정렬은 오름차순이므로, userid가 a로 시작하는 admin이 첫번째 인덱스로 나오게 된다. 따라서 admin으로 로그인이 된다.

 

아무튼 입력해주면 플래그 값을 얻을 수 있다.

 

짠 성공 ~ !

 

 

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

 

simple_sqli_chatgpt

어딘가 이상한 로그인 서비스입니다. SQL INJECTION 취약점을 통해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다. chatGPT와 함께 풀어보세요! Reference Server-side Basic

dreamhack.io

 

728x90