Post

(PHP) SQL Escape & Bypass

SQL Escape method

mysqli::real_escape_string

이스케이프 문자 목록

1
'   "   \   \x00(NUL)   \x1a(EOF)   \n   \r

아래는 모두 동일한 함수.

1
2
3
string mysqli::escape\_string ( string $escapestr ) // alias
string mysqli::real\_escape\_string ( string $escapestr )
string mysqli\_real\_escape\_string ( mysqli $link , string $escapestr )
addslashes

이스케이프 문자 목록

1
'   "   \   \x00(NUL)

DB에서 지원해주는 함수가 정 없다면addslashes() 를 사용한다.

magic_quotes_gpc ( REMOVED as of PHP 5.4.0. )

gpc(GET, POST, COOKIE)에 자동으로 addslashes()를 적용해서 이스케이프한다. 비활성화하고 GPC+α에 일관성 있게 DB 고유의 이스케이프 함수를 사용하는 것이 좋다.

magic_quotes_runtime ( REMOVED as of PHP 5.4.0. )

적용 함수 목록 external source(e.g., db, text file)에서 데이터를 가져오는 함수의 결과 데이터가 quotes를 포함하는 경우 backslash로 이스케이프한다.

bypass

숫자형 데이터로 넘기기

query에서 $id''로 감싸지 않았을 때, 단순히 mysql\_real\_escape_strig($id)만 수행하면 숫자가 넘어올 경우 이를 숫자형 데이터로 간주해 우회된다. 따라서 다음과 같은 방법으로 Filtering / Escape 하며, query 내의 변수는 '$id'형태로 꼭 감싸주어 문자열로 인식할 수 있도록 한다.

1
2
3
4
5
$id = $\_GET['id'];
$id = stripslashes($id);
$id = mysqli\_real\_escape\_string($id);
if (is\_numeric($id)) {
...
multibyte encoding

중간에 charset을 변경하거나, escape를 다른 charset으로 지정하고 수행하는 경우, multibyte를 나타내는0x??\(0x5c)를 묶어 한 문자로 인식하도록 만들어 escape하면서 붙게되는 \를 제거할 수 있다. 이를 이용해 \'같은 escape 대상 문자를 그냥 입력하는 것이 가능하다. EUC-KR → UTF-8 변환인 경우, EUC-KR이 2 bytes를 나타내는데 \xa1 부터 사용하므로 \xa1~\xfe를 사용하면 된다. \xa1\x5c는 EUC-KR에 없는 문자이지만, 한 문자로 인식되며 UTF-8로 변환 후에도 한 문자로 인식된다.

1
2
3
$id = $mysqli->real\_escape\_string($\_GET['id']);
$id = mb\_convert\_encoding($id, 'UTF-8', 'EUC-KR');
$result = $mysqli->query("select \* from testtbl where id='$id'");

URL에 %a1~%fe를 던져도 되고, python으로 진행해도 된다.

1
2
3
url = "http://127.0.0.1/mb\_bypass.php"
payload = {'id':"p2\xa1' or 1#"}
r = requests.get(url, params=payload)
1
2
3
4
after escape : p2�\' or 1#
after mb\_convert : p2?' or 1#

ALL RECODES LEAK!

Note ) 또 다른 charset 변환 함수 iconv()에서는 없는 문자가 들어오면 에러가 발생한다.\xa1\xa0 부터 에러 발생.

1
2
$id = iconv('EUC-KR', 'UTF-8', $id);
iconv(): Detected an illegal character in input string

그러나 //IGNORE를 지정해주는 경우, bypass 가능하다. ( //TRANSLIT도 지정 가능하지만 이건 에러가 발생한다. )

1
$id = iconv('EUC-KR', 'UTF-8//IGNORE', $id);
1
2
3
4
after escape : p2�\' or 1#
after mb\_convert : p2' or 1#

ALL RECODES LEAK!

따라서 user input을 변환해야 한다면 이를 옵션 지정하지 말고 사용하는 편이 좋다.

Indirect SQL Injection

input data만 escape하고, DB에서 불러오는 데이터는 escape하지 않을 때 발생한다. 파라미터로 guest'를 전송하면 서버 단에서 다음과 같이 escape되지만, 결과적으로 DB에 저장되는 것은 guest'다.

1
2
3
4
url : ?id=guest'
php : guest\'
query : insert into member where value('guest\'');
DB : guest'

이러한 상황에서 DB에서 불러온 guest'를 escape하지 않고 그대로 쿼리에 집어넣는 코드가 있다면 guest'가 쿼리에 영향을 미치게 된다. 즉, user input이든 DB든 외부 source에서 온 데이터는 무조건 필터링과 escape 과정을 거쳐야한다.

* magic\_quotes_runtime=off일 때만 적용되나, PHP 5.4부터 제거된 옵션이다.

이렇게 Bypass가 가능하니 preparedStatement를 사용하는 것이 좋다.

SQL injection Mitigation

This post is licensed under CC BY 4.0 by the author.