궁금한게 많은 열아홉
article thumbnail
Published 2023. 6. 15. 14:37
[정보보안] sql Injection 정보보안

SQL Injection

말그대로 sql을 삽입하는 취약점으로, 클라이언트 측에서 SQL 쿼리에 신뢰할 수 없는 데이터가 입력되었을 때, 데이터가 쿼리 로직의 일부로 해석되어 DB에서 실행될 때 발생한다. 주로 사용자가 입력한 데이터를 제대로 필터링, 이스케이핑 하지 못했을 경우에 발생한다. 거의 모든 데이터베이스 엔진은 유저 입력이 의도치 않은 동작을 하는 것을 방지하기 위해 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)
​PYTHON

 

코드를 훑어내려가다 보면 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}"')PYTHON

 

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

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

 

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

 

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

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

 

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

 

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

 

 

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

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

 

 

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

profile

궁금한게 많은 열아홉

@jjin502

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!