mysql_real_escape_string()
함수를 사용할 때에도 SQL 삽입 가능성이 있습니까?
이 샘플 상황을 고려하십시오. SQL은 다음과 같이 PHP로 구성됩니다.
$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";
나는 많은 사람들이 그런 코드가 여전히 위험하고 mysql_real_escape_string()
사용 된 기능으로 도 해킹 할 수 있다고 말합니다 . 그러나 가능한 악용을 생각할 수 없습니까?
다음과 같은 클래식 주사 :
aaa' OR 1=1 --
작동하지 않습니다.
위의 PHP 코드를 통해 얻을 수있는 주입에 대해 알고 있습니까?
답변
다음 쿼리를 고려하십시오.
$iId = mysql_real_escape_string("1 OR 1=1");
$sSql = "SELECT * FROM table WHERE id = $iId";
mysql_real_escape_string()
이것으로부터 당신을 보호하지 않습니다.
쿼리 내 변수 주위에 작은 따옴표 ( ' '
) 를 사용한다는 사실 이이를 방지합니다. 다음도 옵션입니다.
$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
답변
짧은 대답은 그렇습니다. 그렇습니다mysql_real_escape_string()
.
매우 엣지 케이스의 경우 !!!
긴 대답은 쉽지 않습니다. 여기서 시연 된 공격을 기반으로합니다 .
공격
이제 공격을 보여 주면서 시작하겠습니다.
mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
특정 상황에서는 둘 이상의 행이 반환됩니다. 여기서 무슨 일이 일어나고 있는지 해부하자 :
-
문자 세트 선택
mysql_query('SET NAMES gbk');
작업이 공격을 위해, 우리는 서버의 두 인코딩에 대한 연결을 기대하는 인코딩을 필요
'
ASCII의 예에서와 같이0x27
하고 , 그 최종 바이트는 ASCII 일부 문자를 가지고\
즉0x5c
. 그것이 나오는 것에 따라, 기본적으로 MySQL의 5.6에서 지원 (5)와 같은 인코딩이있다 :big5
,cp932
,gb2312
,gbk
와sjis
.gbk
여기서 선택 하겠습니다.자,
SET NAMES
여기서 의 사용에 주목하는 것이 매우 중요합니다 . 이것은 서버에 문자 세트를 설정합니다 . C API 함수 호출을 사용했다면mysql_set_charset()
(2006 년 이후 MySQL 릴리스에서) 괜찮을 것이다. 그러나 왜 잠시 후에 더 많은 … -
페이로드
이 주입에 사용할 페이로드는 바이트 시퀀스로 시작합니다
0xbf27
. 에서gbk
유효하지 않은 멀티 바이트 문자입니다. 에서latin1
문자열¿'
입니다. 에 그주의latin1
하고gbk
,0x27
자신이 리터럴에'
문자.우리는이 페이로드를 선택했다. 왜냐하면 호출 하면 문자 앞에
addslashes()
ASCII\
즉을 삽입0x5c
하기 때문'
이다. 우리가 바람 것 그래서0xbf5c27
, 어떤에서이gbk
두 개의 문자 순서입니다 :0xbf5c
다음에0x27
. 즉, 유효한 문자 뒤에 이스케이프 처리되지 않은이'
있습니다. 그러나 우리는을 사용하지 않습니다addslashes()
. 다음 단계로 넘어갑니다 … -
mysql_real_escape_string ()
C API 호출 은 연결 문자 집합을 알고 있다는
mysql_real_escape_string()
점과 다릅니다addslashes()
. 따라서 서버가 기대하는 문자 세트에 대해 이스케이프를 올바르게 수행 할 수 있습니다. 그러나 지금까지 클라이언트latin1
는 연결에 대해 계속 사용하고 있다고 생각합니다 . 우리는 서버에 사용하고 있다고 말gbk
했지만 클라이언트는 여전히 서버 를 사용 한다고 생각합니다latin1
.따라서
mysql_real_escape_string()
백 슬래시 를 삽입 하라는 호출은'
“탈출 된”컨텐츠에 자유롭게 걸려있는 문자를 갖습니다 ! 우리가보고 있다면 사실,$var
에서gbk
문자 집합, 우리는 볼 것 :縗 'OR 1 = 1 / *
이는 정확히 공격이 필요합니다.
-
쿼리
이 부분은 형식 일 뿐이지 만 렌더링 된 쿼리는 다음과 같습니다.
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
축하합니다, 당신은 성공적으로 mysql_real_escape_string()
…
나쁜
악화된다. PDO
기본적으로 MySQL 을 사용 하여 준비된 명령문 을 에뮬레이트 합니다. 즉, 클라이언트 측에서는 기본적으로 mysql_real_escape_string()
(C 라이브러리에서) 스프린트를 수행하므로 다음과 같이 성공적으로 주입됩니다.
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
이제 에뮬레이트 된 준비된 문장을 비활성화하여 이것을 막을 수 있다는 점에 주목할 가치가 있습니다.
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
이로 인해 일반적으로 준비된 명령문이 생성됩니다 (예 : 데이터가 쿼리와 별도의 패킷으로 전송 됨). 그러나 PDO는 MySQL이 기본적으로 준비 할 수없는 명령문 ( 매뉴얼에 나열 될 수 있지만 적절한 서버 버전을 선택하도록주의 해야하는 명령문)을 에뮬레이션하는 것으로 자동으로 대체 됩니다 .
못난이
나는 처음에 우리가 mysql_set_charset('gbk')
대신에 사용했다면이 모든 것을 막을 수 있다고 말했다 SET NAMES gbk
. 2006 년 이후로 MySQL 릴리스를 사용하는 경우에도 마찬가지입니다.
당신은 이전 MySQL의 릴리스, 다음 사용하는 경우 버그 에서 mysql_real_escape_string()
같은 우리의 페이로드에있는 것과 같은 잘못된 멀티 바이트 문자를 목적으로 탈출을위한 단일 바이트로 처리 된 것을 의미 클라이언트가 올바르게 연결 인코딩을 통보했다하더라도 그래서이 공격 것 등을 여전히 성공합니다. 버그는 MySQL 4.1.20 , 5.0.22 및 5.1.11 에서 수정되었습니다 .
그러나 최악의 부분은 5.3.6까지 PDO
C API를 공개하지 않았기 mysql_set_charset()
때문에 이전 버전에서는 가능한 모든 명령에 대해이 공격을 막을 수는 없습니다 ! 이제 DSN 매개 변수 로 노출됩니다 .
구원의 은총
처음에 말했듯이이 공격이 작동하려면 데이터베이스 연결이 취약한 문자 세트를 사용하여 인코딩되어야합니다. utf8mb4
이다 취약하지 지원할 수 아직하고 모든 당신의 MySQL 5.5.3 이후 대신-하지만 그것은 단지되었습니다 사용할 수를 사용하여 선택할 수 있도록 유니 코드 문자를. 대안은 utf8
또한, 이는 취약하지 및 유니 코드의 전체 지원할 수있는 기본 다국어 평면 .
또는 NO_BACKSLASH_ESCAPES
SQL 모드를 활성화 할 수 있습니다.이 모드는 다른 작업 중에서의 작업을 변경합니다 mysql_real_escape_string()
. 이 모드가 활성화 된 상태 0x27
로 대체됩니다 0x2727
보다는 0x5c27
탈출 과정이 이렇게하고 할 수없는 그들이 이전에 존재하지 않았던 취약한 인코딩의 유효 문자를 생성 (즉 0xbf27
여전히 0xbf27
등.) – 서버, 그래서 여전히 유효로 문자열을 거부합니다 . 그러나이 SQL 모드를 사용하여 발생할 수있는 다른 취약점에 대해서는 @eggyal의 답변 을 참조하십시오 .
안전한 예
다음 예는 안전합니다.
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
서버가 기대하기 때문에 utf8
…
mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
클라이언트와 서버가 일치하도록 문자 세트를 올바르게 설정했기 때문입니다.
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
우리는 에뮬레이트 된 준비된 진술을 해제했기 때문에.
$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
문자 세트를 올바르게 설정했기 때문입니다.
$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
MySQLi는 항상 진실 된 준비된 진술을하기 때문입니다.
마무리
만약 너라면:
- 최신 버전의 MySQL (최근 5.1, 모든 5.5, 5.6 등) 및
mysql_set_charset()
/$mysqli->set_charset()
/ PDO의 DSN 문자 세트 매개 변수 사용 (PHP ≥ 5.3.6)
또는
- 연결 인코딩에 취약한 문자 세트를 사용하지 마십시오 (
utf8
/latin1
/ascii
/ 등 만 사용 )
당신은 100 % 안전합니다.
그렇지 않으면, 당신 이 사용하더라도mysql_real_escape_string()
취약합니다 …
답변
TL; DR
mysql_real_escape_string()
다음 과 같은 경우 보호 기능 을 제공하지 않으며 (또한 데이터를 조정할 수 있습니다)
MySQL의의
NO_BACKSLASH_ESCAPES
SQL 모드가 활성화되어 (이되는 수도 수, 당신은하지 않는 명시 적으로 다른 SQL 모드를 선택 하면 연결할 때마다 ); 과SQL 문자열 리터럴은 큰 따옴표
"
문자를 사용하여 인용 됩니다.이것은 버그 # 72458 로 제출 되었으며 MySQL v5.7.6에서 수정되었습니다 ( 아래 ” 저축 유예 ” 섹션 참조).
이것은 또 다른 (아마도 덜?) 모호한 EDGE CASE입니다!
에 경의에서 @ ircmaxell 우수한 대답 (정말, 이것은 아첨하지 표절 있어야하는데!), 나는 그의 형식을 채택한다 :
공격
데모로 시작하는 중 …
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
test
테이블 에서 모든 레코드를 반환 합니다. 해부 :
-
SQL 모드 선택
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
문자열 리터럴에 설명 된대로 :
문자열 내에 인용 부호를 포함시키는 방법에는 여러 가지가 있습니다.
-
”
'
“로 인용 된 문자열 내부의 ”'
“는 ” “로 쓸 수 있습니다''
. -
”
"
“로 인용 된 문자열 내부의 ”"
“는 ” “로 쓸 수 있습니다""
. -
따옴표 문자는 이스케이프 문자 (“
\
”)로 시작합니다. -
”
'
“로 인용 된 문자열 내부의 ”"
“는 특별한 처리가 필요하지 않으며 배가되거나 이스케이프 될 필요가 없습니다. 같은 방식으로 ”"
“로 인용 된 문자열 내부의 ”'
“는 특별한 처리가 필요하지 않습니다.
서버의 SQL 모드에가 포함 된
NO_BACKSLASH_ESCAPES
경우 이러한 옵션 중 세 번째 옵션 (일반적으로 채택 된 접근 방식)을mysql_real_escape_string()
사용할 수 없습니다. 대신 처음 두 옵션 중 하나를 사용해야합니다. 네 번째 글 머리 기호의 효과는 데이터를 녹이지 않기 위해 리터럴을 인용하는 데 사용될 문자를 반드시 알아야한다는 것입니다. -
-
페이로드
" OR 1=1 --
페이로드는
"
캐릭터 와 함께 문자 그대로이 주입을 시작합니다 . 특별한 인코딩이 없습니다. 특수 문자가 없습니다. 이상한 바이트가 없습니다. -
mysql_real_escape_string ()
$var = mysql_real_escape_string('" OR 1=1 -- ');
다행히
mysql_real_escape_string()
SQL 모드를 확인하고 그에 따라 동작을 조정합니다. 참조libmysql.c
:ulong STDCALL mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, ulong length) { if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) return escape_quotes_for_mysql(mysql->charset, to, 0, from, length); return escape_string_for_mysql(mysql->charset, to, 0, from, length); }
따라서 SQL 모드가 사용중인
escape_quotes_for_mysql()
경우 다른 기본 함수 인을 호출합니다NO_BACKSLASH_ESCAPES
. 위에서 언급 한 바와 같이, 그러한 함수는 다른 인용 문자가 문자 그대로 반복되지 않고 문자를 반복하기 위해 문자를 인용하는 데 사용될 문자를 알아야합니다.그러나이 함수는 임의로 작은 따옴표 문자를 사용하여 문자열을 인용 한다고 가정 합니다
'
. 참조charset.c
:/* Escape apostrophes by doubling them up // [ deletia 839-845 ] DESCRIPTION This escapes the contents of a string by doubling up any apostrophes that it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in effect on the server. // [ deletia 852-858 ] */ size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info, char *to, size_t to_length, const char *from, size_t length) { // [ deletia 865-892 ] if (*from == '\'') { if (to + 2 > to_end) { overflow= TRUE; break; } *to++= '\''; *to++= '\''; }
따라서 리터럴을 인용하는 데 사용되는 실제 문자에 관계없이 큰 따옴표
"
문자를 그대로 유지하고 모든 작은 따옴표'
문자를 두 배로 만듭니다 ! 우리의 경우에$var
제공 한 인수와 정확히 동일하게 유지mysql_real_escape_string()
에는 이스케이프가 자리를 차지하지 않았다 것처럼 – 그것은이다 전혀 . -
쿼리
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
형식적인 것으로 렌더링 된 쿼리는 다음과 같습니다.
SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
배운 친구가 말했듯이 축하합니다. 방금 mysql_real_escape_string()
…
나쁜
mysql_set_charset()
이것은 문자 세트와 관련이 없으므로 도움을 줄 수 없습니다. mysqli::real_escape_string()
이 함수를 둘러싼 다른 래퍼 일 뿐이므로 수 없습니다 .
문제는 아직 명확하지 않은 경우 문자를 인용 할 문자를 mysql_real_escape_string()
알 수 없다는 요청 은 개발자가 나중에 결정해야한다는 것입니다. 따라서 NO_BACKSLASH_ESCAPES
모드에서는 이 함수가 임의의 인용 부호와 함께 사용하기 위해 모든 입력을 안전하게 이스케이프 할 수있는 방법 이 없습니다.
못난이
악화된다. NO_BACKSLASH_ESCAPES
표준 SQL과의 호환성에 대한 사용의 필요성 때문에 야생에서 드문 일이 아닐 수도 있습니다 (예 : SQL-92 사양 의 섹션 5.3 , 즉 <quote symbol> ::= <quote><quote>
문법 생성 및 백 슬래시에 주어진 특별한 의미가 없음). 또한 ircmaxell의 게시물에서 설명 하는 (고정 된 이후의) 버그에 대한 해결 방법 으로 사용하는 것이 명시 적으로 권장되었습니다 . 누가 알 수 있듯이 일부 DBA는와 같은 잘못된 이스케이프 메소드 사용을 권장하지 않기 위해 기본적으로 사용하도록 구성 할 수도 있습니다 .addslashes()
또한 새 연결 의 SQL 모드 는 구성에 따라 서버에서 설정합니다 ( SUPER
사용자는 언제든지 변경할 수 있음). 따라서 서버의 동작을 확실하게하려면 연결 후 항상 원하는 모드를 명시 적으로 지정해야합니다.
구원의 은총
작은 따옴표 문자를 사용하여 MySQL 문자열 리터럴 을 포함 하거나 인용 하지 않도록 SQL 모드 를 항상 명시 적으로 설정하는 NO_BACKSLASH_ESCAPES
한이 버그는 추한 머리를 깎을 수 없습니다. 각각 escape_quotes_for_mysql()
사용되지 않거나 따옴표 문자를 반복 해야하는 가정 정확하다.
이러한 이유로 작은 인용 문자열 리터럴을 습관적으로 사용하므로 모드를 사용하는 사람 NO_BACKSLASH_ESCAPES
도 ANSI_QUOTES
모드를 사용하는 것이 좋습니다 . 큰 따옴표로 묶인 리터럴이 사용되는 경우 SQL 삽입을 막을 수는 없습니다. 이는 정상적인 악의없는 쿼리가 실패하기 때문에 발생 가능성을 줄입니다.
PDO에서 동등한 기능 PDO::quote()
과 준비된 명령문 에뮬레이터 mysql_handle_quoter()
는 다음과 같이 요구합니다. 정확하게이 작업을 수행합니다. 이스케이프 된 리터럴이 작은 따옴표로 인용되므로 PDO가 항상이 버그로부터 영향을받지 않습니다.
MySQL v5.7.6부터이 버그가 수정되었습니다. 변경 로그를 참조하십시오 .
추가되거나 변경된 기능
호환되지 않는 변경 : SQL 모드가 활성화 된경우 후자의 함수가 문자를 올바르게 인코딩하지 못하기 때문에새로운 C API 함수 인
mysql_real_escape_string_quote()
대체기능이 구현되었습니다. 이 경우따옴표 문자를 두 배로 늘리지 않는 한 따옴표 문자를 이스케이프 할 수 없으며 올바르게 수행하려면 인용 컨텍스트에 대한 정보를 사용 가능한 것보다 많이 알아야합니다. 인용 문맥을 지정하기 위해 추가 인수를 취합니다. 사용법에 대한 자세한 내용은 mysql_real_escape_string_quote ()를 참조하십시오.mysql_real_escape_string()
NO_BACKSLASH_ESCAPES
mysql_real_escape_string()
mysql_real_escape_string_quote()
노트
mysql_real_escape_string_quote()
대신 에을 (를) 사용하도록 응용 프로그램을 수정해야합니다 .이 기능이 활성화 되면 오류가 발생mysql_real_escape_string()
하고CR_INSECURE_API_ERR
오류가 발생합니다NO_BACKSLASH_ESCAPES
.참조 : 버그 # 19211994 참조.
안전한 예
ircmaxell에 의해 설명 된 버그와 함께, 다음 예제는 완전히 안전합니다 (4.1.20, 5.0.22, 5.1.11 이후의 MySQL을 사용하거나 GBK / Big5 연결 인코딩을 사용하지 않는 것으로 가정). :
mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
… 포함되지 않은 SQL 모드를 명시 적으로 선택했기 때문 NO_BACKSLASH_ESCAPES
입니다.
mysql_set_charset($charset);
$var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
… 문자열 리터럴을 작은 따옴표로 인용하기 때문입니다.
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);
… PDO 준비된 명령문은이 취약점으로부터 영향을받지 않기 때문에 (PHP≥5.3.6을 사용하고 문자 세트가 DSN에서 올바르게 설정되었거나 준비된 명령문 에뮬레이션이 비활성화 된 경우 ircmaxell도 마찬가지 임) .
$var = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");
… PDO의 quote()
함수는 리터럴을 이스케이프 할뿐만 아니라 작은 따옴표 '
문자로 도 인용하기 때문에 ; 이 경우 ircmaxell의 버그를 방지 할 수 있습니다, 당신은 해야한다 PHP≥5.3.6를 사용 하고 올바르게 DSN의 문자 집합을 설정했습니다.
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
… MySQLi 준비 문장이 안전하기 때문에.
마무리
따라서 다음과 같은 경우 :
- 기본 준비된 진술을 사용하십시오
또는
- MySQL v5.7.6 이상 사용
또는
-
에서 또한 적어도 하나의 ircmaxell의 요약에있는 솔루션 중 하나, 사용을 사용하기 :
- PDO;
- 작은 따옴표로 묶인 문자열 리터럴; 또는
- 포함되지 않은 명시 적으로 설정된 SQL 모드
NO_BACKSLASH_ESCAPES
… 그런 다음 완전히 안전 해야 합니다 (문자열 이탈 범위를 벗어난 취약점).
답변
글쎄, %
와일드 카드를 제외하고는 실제로 그것을 통과 할 수있는 것은 없습니다 . LIKE
침입자가 %
이를 걸러 내지 않으면 로그인 과 같은 방식으로 문장을 사용하는 경우 위험 할 수 있으며 사용자의 암호를 무차별 처리해야합니다. 사람들은 종종 준비된 명령문을 사용하여 데이터를 쿼리 자체를 방해하지 않기 때문에 100 % 안전하도록 제안합니다. 그러나 이러한 간단한 쿼리의 경우 다음과 같은 작업을 수행하는 것이 더 효율적일 것입니다$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);