짧은 대답은 그렇습니다. 그렇습니다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까지 PDOC API를 공개하지 않았기 mysql_set_charset()때문에 이전 버전에서는 가능한 모든 명령에 대해이 공격을 막을 수는 없습니다 ! 이제 DSN 매개 변수 로 노출됩니다 .
구원의 은총
처음에 말했듯이이 공격이 작동하려면 데이터베이스 연결이 취약한 문자 세트를 사용하여 인코딩되어야합니다. utf8mb4이다 취약하지 지원할 수 아직하고 모든 당신의 MySQL 5.5.3 이후 대신-하지만 그것은 단지되었습니다 사용할 수를 사용하여 선택할 수 있도록 유니 코드 문자를. 대안은 utf8또한, 이는 취약하지 및 유니 코드의 전체 지원할 수있는 기본 다국어 평면 .
또는 NO_BACKSLASH_ESCAPESSQL 모드를 활성화 할 수 있습니다.이 모드는 다른 작업 중에서의 작업을 변경합니다 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() 취약합니다 ...