[WEB] simple_sqli 문제풀이

SQL Injection 취약점이란 웹 해킹 기법 중 하나로 웹 애플리케이션의 뒷단에 있는 Database에 질의(쿼리를 보내는 것)하는 과정 사이에 일반적인 값이 아닌 악의적인 의도를 갖는 구문을 삽입하여 공격자가 원하는 SQL 쿼리문을 실행하는 기법이다. 주로 사용자가 입력한 데이터를 제대로 필터링, 이스케이핑 하지 못했을 경우에 발생한다. 거의 모든 데이터베이스 엔진은 유저 입력이 의도치 않은 동작을 하는 것을 방지하기 위해 escape 함수와 prepared statement를 제공한다.

 

문제를 확인해보자.

 

 

로그인 서비스에서 SQL Injection 취약점을 통해 FLAG를 획득하라고 한다. 한 번 접속해보자.

 

 

대문짝만하지 않게 Login 링크가 걸려있다. 클릭해보자.

 

 

우리는 여기에서 admin/admin을 테스트해보지 않을 수 있다. 가슴이 시키기 때문이다. 시켜보자.

 

 

사실 안될거 아는데 여러분들에게 보여주려고 일부러 시도한거다. 의심하지마라.

역시 감 안잡힐때는 소스코드를 봐보자.

 

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

 

코드를 훑어내려가다 보면 DB에는 guest/guest 계정과 admin/???? 계정이 있는 것을 알 수 있다.

guest 계정은 비밀번호가 나와있지만, admin 계정 비밀번호는 숨겨져있다.

SQL Injection 취약점을 이용해 admin으로 로그인에 성공해야하는(추측) 문제인 것 같다.

 

우리는 로그인을 시도할 때 SQL 쿼리가 삽입되는 곳을 발견했고, 

login 페이지에 POST 요청으로 쏘면 사용자가 입력한 계정과 비밀번호를 이용하여

데이터베이스 쿼리에 담아가는 것을 확인했다.

 

res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')

 

SQL 쿼리를 해석해보면 users 테이블에 입력한 userid와 userpassword가 일치하는 데이터를 조회한다.

내가 admin/admin 으로 로그인을 시도하면 쿼리에는 이렇게 입력될 것이다.

 

select * from users where userid="admin" and userpassword="admin"

 

우리는 admin 계정 비밀번호를 모르는 것이기 때문에 계정 입력 값을 통해 패스워드 검증 부분을 무력화 시켜야한다. 

패스워드 검증 부분을 주석처리 하면되는데 주석이 입력되었을때 SQL 쿼리는 다음과 같이 실행될 것이다.

 

select * from users where userid="admin"# and userpassword="123"

 

위와 같이 뒤에 패스워드 검증 부분은 쿼리에서 삭-제가 되어버린다. 이를 웹페이지에서 시도해보자.

 

 

아니 국민 주석 #를 넣었는데 안된다니..

하지만 나에겐 아직 국민주석 한 발 더 남았다

 

 

두 번의 시도 만에 합격했다.

공무원 시험 합격은 에듀윌

 

문제풀이 끗

 

 

 

 

 

 

복사했습니다!