mysql_real_escape_string ()을 둘러싼 SQL 주입


645

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 코드를 통해 얻을 수있는 주입에 대해 알고 있습니까?


34
@ThiefMaster-유효하지 않은 사용자 / 유효하지 않은 비밀번호와 같은 자세한 오류를 표시하지 않는 것이 좋습니다 ... 무차별 대행사에게 유효한 사용자 ID를 가지고 있으며 추측해야하는 비밀번호 일뿐입니다.
Mark Baker

18
그러나 유용성 관점에서 끔찍합니다. 때로는 기본 닉네임 / 사용자 이름 / 이메일 주소를 사용하지 못하고 일정 시간이 지나면 계정이 삭제되어 계정이 비활성 상태 인 것을 잊어 버릴 수 있습니다. 그렇다면 암호를 계속 사용하면 IP 주소가 유효하지 않은 경우에도 IP를 차단할 수 있습니다.
ThiefMaster

50
mysql_*새 코드 에서는 함수를 사용하지 마십시오 . 더 이상 유지 관리 되지 않으며 사용 중단 프로세스 가 시작되었습니다. 참고 항목 빨간색 상자 ? 에 대해 알아 준비된 문 대신 사용할 PDO 또는 MySQLi를 - 이 기사는 당신이 어떤을 결정하는 데 도움이됩니다. PDO를 선택하면 다음 은 좋은 튜토리얼 입니다.
tereško

13
@machineaddict, 5.5 이후 (최근 출시 된) mysql_*기능은 이미 E_DEPRECATED경고를 생성 합니다. ext/mysql확장은 더 다음 10 년 동안 유지되지 않았습니다. 당신은 정말 망상입니까?
tereško

13
@machineaddict 그들은 방금 PHP 7.0에서 그 확장을 제거했으며 아직 2050이 아닙니다.
GGG

답변:


379

다음 쿼리를 고려하십시오.

$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";

9
그러나 mysql_query()여러 문을 실행하지 않기 때문에 이것은 실제 문제 가되지 않습니다.
Pekka

11
@Pekka, 일반적인 예는이지만 DROP TABLE실제로 공격자는 더 가능성이 높습니다 SELECT passwd FROM users. 후자의 경우, 두 번째 쿼리는 일반적으로 UNION절 을 사용하여 실행됩니다 .
Jacco

58
(int)mysql_real_escape_string-말이되지 않습니다. 전혀 다르지 않습니다 (int). 그리고 그들은 모든 입력에 대해 동일한 결과를 산출 할 것입니다
zerkms

28
이것은 다른 것보다 기능을 잘못 사용하는 것입니다. 결국, 이름 mysql_real_escape_string이 아닌 mysql_real_escape_integer입니다. 정수 필드와 함께 사용되는 것은 아닙니다.
NullUserException

11
@ircmaxell, 그러나 대답은 완전히 오도됩니다. 분명히 질문은 따옴표 안의 내용 에 대해 묻고 있습니다. "견적은 없습니다"는 이 질문에 대한 답 이 아닙니다 .
Pacerier

629

짧은 대답은 그렇습니다. 그렇습니다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");

특정 상황에서는 둘 이상의 행이 반환됩니다. 여기서 무슨 일이 일어나고 있는지 해부하자 :

  1. 문자 세트 선택

    mysql_query('SET NAMES gbk');

    작업이 공격을 위해, 우리는 서버의 두 인코딩에 대한 연결을 기대하는 인코딩을 필요 'ASCII의 예에서와 같이 0x27 하고 , 그 최종 바이트는 ASCII 일부 문자를 가지고 \0x5c. 그것이 나오는 것에 따라, 기본적으로 MySQL의 5.6에서 지원 (5)와 같은 인코딩이있다 : big5, cp932, gb2312, gbksjis. gbk여기서 선택 하겠습니다.

    자, SET NAMES여기서 의 사용에 주목하는 것이 매우 중요합니다 . 이것은 서버에 문자 세트를 설정합니다 . C API 함수 호출을 사용했다면 mysql_set_charset()(2006 년 이후 MySQL 릴리스에서) 괜찮을 것이다. 그러나 왜 잠시 후에 더 많은 ...

  2. 페이로드

    이 주입에 사용할 페이로드는 바이트 시퀀스로 시작합니다 0xbf27. 에서 gbk유효하지 않은 멀티 바이트 문자입니다. 에서 latin1문자열 ¿'입니다. 에 그주의 latin1 하고 gbk , 0x27자신이 리터럴에 '문자.

    우리는이 페이로드를 선택했다. 왜냐하면 호출 하면 문자 앞에 addslashes()ASCII \즉을 삽입 0x5c하기 때문 '이다. 우리가 바람 것 그래서 0xbf5c27, 어떤에서이 gbk두 개의 문자 순서입니다 : 0xbf5c다음에 0x27. 즉, 유효한 문자 뒤에 이스케이프 처리되지 않은이 '있습니다. 그러나 우리는을 사용하지 않습니다 addslashes(). 다음 단계로 넘어갑니다 ...

  3. mysql_real_escape_string ()

    C API 호출 은 연결 문자 집합을 알고 있다는 mysql_real_escape_string()점과 다릅니다 addslashes(). 따라서 서버가 기대하는 문자 세트에 대해 이스케이프를 올바르게 수행 할 수 있습니다. 그러나 지금까지 클라이언트 latin1는 연결에 대해 계속 사용하고 있다고 생각합니다 . 우리는 서버에 사용하고 있다고 말 gbk했지만 클라이언트는 여전히 서버 를 사용 한다고 생각합니다 latin1.

    따라서 mysql_real_escape_string()백 슬래시 를 삽입 하라는 호출은 '"탈출 된"컨텐츠에 자유롭게 걸려있는 문자를 갖습니다 ! 우리가보고 있다면 사실, $var에서 gbk문자 집합, 우리는 볼 것 :

    縗 'OR 1 = 1 / *

    이는 정확히 공격이 필요합니다.

  4. 쿼리

    이 부분은 형식 일 뿐이지 만 렌더링 된 쿼리는 다음과 같습니다.

    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.225.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() 취약합니다 ...


3
PDO 에뮬레이션 MySQL의 준비 문장, 정말로? 드라이버가 기본적으로 지원하기 때문에 왜 그런지 알 수 없습니다. 아니?
netcoder

16
그렇습니다. 그들은 문서에서 그렇지 않다고 말합니다. 그러나 소스 코드에서 쉽게 볼 수 있고 쉽게 수정할 수 있습니다. 나는 개발자의 무능에 분필을 넣습니다.
Theodore R. Smith

5
@ TheodoreR.Smith : 해결하기 쉽지 않습니다. 나는 기본값을 변경하기 위해 노력하고 있지만 전환 할 때 보트 테스트에 실패합니다. 생각보다 큰 변화입니다. 나는 여전히 5.5 완료를 바라고 ...
ircmaxell

14
@shadyyx : 아닙니다 addslashes. 기사에 설명 된 취약점은 약 였습니다. 나는 기반의 하나에이 취약점을. 직접 해보십시오. MySQL 5.0을 다운로드하여이 익스플로잇을 실행하고 직접 확인하십시오. PUT / GET / POST에 넣는 방법은 TRIVIAL입니다. 입력 데이터는 단지 바이트 스트림입니다. char(0xBF)바이트를 생성하는 읽기 쉬운 방법입니다. 여러 회의 앞에서이 취약점을 실제로 시연했습니다. 이것에 대해 믿어주십시오 ... 그러나 당신이하지 않으면 직접 시도하십시오. 그것은 작동 ...
ircmaxell

5
@ shadyyx : $ _GET ... ?var=%BF%27+OR+1=1+%2F%2A에 URL, $var = $_GET['var'];코드 및 Bob의 삼촌이 그러한 funkiness를 전달하는 것에 관해서는 .
cHao

183

TL; DR

mysql_real_escape_string()다음 과 같은 경우 보호 기능제공하지 않으며 (또한 데이터를 조정할 수 있습니다)

  • MySQL의의 NO_BACKSLASH_ESCAPESSQL 모드가 활성화되어 (이되는 수도 수, 당신은하지 않는 명시 적으로 다른 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테이블 에서 모든 레코드를 반환 합니다. 해부 :

  1. SQL 모드 선택

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');

    문자열 리터럴에 설명 된대로 :

    문자열 내에 인용 부호를 포함시키는 방법에는 여러 가지가 있습니다.

    • " '"로 인용 된 문자열 내부의 " '"는 " "로 쓸 수 있습니다 ''.

    • " ""로 인용 된 문자열 내부의 " ""는 " "로 쓸 수 있습니다 "".

    • 따옴표 문자는 이스케이프 문자 (“ \”)로 시작합니다.

    • " '"로 인용 된 문자열 내부의 " ""는 특별한 처리가 필요하지 않으며 배가되거나 이스케이프 될 필요가 없습니다. 같은 방식으로 " ""로 인용 된 문자열 내부의 " '"는 특별한 처리가 필요하지 않습니다.

    서버의 SQL 모드에가 포함 된 NO_BACKSLASH_ESCAPES경우 이러한 옵션 중 세 번째 옵션 (일반적으로 채택 된 접근 방식)을 mysql_real_escape_string()사용할 수 없습니다. 대신 처음 두 옵션 중 하나를 사용해야합니다. 네 번째 글 머리 기호의 효과는 데이터를 녹이지 않기 위해 리터럴을 인용하는 데 사용될 문자를 반드시 알아야한다는 것입니다.

  2. 페이로드

    " OR 1=1 -- 

    페이로드는 "캐릭터 와 함께 문자 그대로이 주입을 시작합니다 . 특별한 인코딩이 없습니다. 특수 문자가 없습니다. 이상한 바이트가 없습니다.

  3. 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()에는 이스케이프가 자리를 차지하지 않았다 것처럼 - 그것은이다 전혀 .

  4. 쿼리

    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_ESCAPESANSI_QUOTES모드를 사용하는 것이 좋습니다 . 큰 따옴표로 묶인 리터럴이 사용되는 경우 SQL 삽입을 막을 수는 없습니다. 이는 정상적인 악의없는 쿼리가 실패하기 때문에 발생 가능성을 줄입니다.

PDO에서 동등한 기능 PDO::quote()과 준비된 명령문 에뮬레이터 mysql_handle_quoter()는 다음과 같이 요구합니다. 정확하게이 작업을 수행합니다. 이스케이프 된 리터럴이 작은 따옴표로 인용되므로 PDO가 항상이 버그로부터 영향을받지 않습니다.

MySQL v5.7.6부터이 버그가 수정되었습니다. 변경 로그를 참조하십시오 .

추가되거나 변경된 기능

안전한 예

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

... 그런 다음 완전히 안전 해야 합니다 (문자열 이탈 범위를 벗어난 취약점).


10
따라서 TL; DR은 "NO_BACKSLASH_ESCAPES mysql 서버 모드가있어서 작은 따옴표를 사용하지 않으면 주입이 발생할 수 있습니다.
Common Sense

bugs.mysql.com/bug.php?id=72458 에 액세스 할 수 없습니다 . 액세스 거부 페이지가 나타납니다. 보안 문제로 인해 대중에게 숨겨져 있습니까? 또한이 답변에서 귀하가 취약점을 발견 한 것임을 올바르게 이해하고 있습니까? 그렇다면 축하합니다.
Mark Amery

1
사람들은 "우선 문자열을 사용해서는 안됩니다 . SQL은 식별자에 대한 것이라고 말합니다. 그러나 어 ... "나사 표준, 내가 원하는 것은 무엇이든 할 것이다"라고 말하는 MySQL의 또 다른 예. (다행히 ANSI_QUOTES인용 부호를 수정하기 위해 모드에 포함시킬 수 있습니다 . 그러나 표준을 공개적으로 무시하는 것이 더 심각한 문제가 될 수있는 더 큰 문제입니다.)
cHao

2
@ DanAllen : 내 대답은 PDO의 기능 을 통해이 특정 버그를 피할 있다는 점에서 조금 더 넓었습니다. 그러나 준비된 문장은 일반적으로 주입을 피하는 훨씬 안전하고 적절한 방법입니다. 물론 이스케이프 처리되지 않은 변수를 SQL에 직접 연결 한 경우 이후에 어떤 방법을 사용하든 주입에 가장 취약합니다. quote()
eggyal

1
@eggyall : 우리 시스템은 위의 두 번째 안전한 예를 사용합니다. mysql_real_escape_string이 생략 된 오류가 있습니다. 비상 모드에서 사람들을 고치는 것이 올바른 길로 보입니다. 우리가 수정하기 전에 핵을 피하지 않기를 바랍니다. 저의 이론적 근거는 준비된 진술로 전환하는 것이 훨씬 더 긴 과정이 될 것입니다. 준비된 진술이 더 안전한 이유는 오류가 취약점을 생성하지 않는다는 사실입니까? 다시 말해, 위의 두 번째 예가 올바르게 구현 되었는가는 준비된 진술만큼 안전합니까?
DanAllen

18

글쎄, %와일드 카드를 제외하고는 실제로 그것을 통과 할 수있는 것은 없습니다 . LIKE침입자가 %이를 걸러 내지 않으면 로그인 과 같은 방식으로 문장을 사용하는 경우 위험 할 수 있으며 사용자의 암호를 무차별 처리해야합니다. 사람들은 종종 준비된 명령문을 사용하여 데이터를 쿼리 자체를 방해하지 않기 때문에 100 % 안전하도록 제안합니다. 그러나 이러한 간단한 쿼리의 경우 다음과 같은 작업을 수행하는 것이 더 효율적일 것입니다$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);


2
+1이지만 와일드 카드는 단순한 동등성이 아닌 LIKE 절에 대한 것입니다.
Dor

7
more efficient준비된 진술을 사용하는 것보다 간단한 대체 방법은 무엇 입니까? (준비된 명령문은 항상 작동하며 공격의 경우 라이브러리를 신속하게 수정할 수 있으며, 완전한 대체 문자열을 잘못 입력하는 등의 인적 오류를 노출하지 않으며 명령문을 재사용 할 경우 성능상의 이점이 있습니다.)
MatBailie

7
@Slava : 사용자 이름과 비밀번호를 단어 문자로만 효과적으로 제한합니다. 보안에 대해 아는 대부분의 사람들은 검색 공간이 상당히 줄어들 기 때문에 나쁜 생각이라고 생각할 것입니다. 물론 그들은 데이터베이스에 일반 텍스트 암호를 저장하는 것이 좋지 않다고 생각하지만 문제를 복잡하게 만들 필요는 없습니다. :)
cHao

2
@ cHao, 내 제안은 로그인에만 관련됩니다. 분명히 당신은 암호를 필터링 할 필요가 없습니다, 내 대답에 명확하게 명시되어 있지 않습니다. 그러나 실제로는 좋은 생각 일 수 있습니다. 기억하기 어려운 유형 대신 "a4üua3! @v \"ä90; 8f "대신"돌 무지 트리 공간 "을 사용하면 무차별 대입이 훨씬 더 어려울 수 있습니다. 당신은 정확히 4 단어를 사용했습니다-그것은 여전히 ​​대략 3.3 * 10 ^ 12 조합입니다. :)
Slava

2
@Slava : 그 아이디어를 전에 본 적이 있습니다. xkcd.com/936을 참조하십시오 . 문제는 수학이 그것을 잘 견디지 못한다는 것입니다. 17-char 암호의 예는 96 ^ 17 가능성과 같으며 움라우트를 잊어 버리고 인쇄 가능한 ASCII로 제한 한 경우입니다. 약 4.5x10 ^ 33입니다. 우리는 무차별 대입을 위해 문자 그대로 10 억 조 배 더 많은 일을하고 있습니다. 8 문자 ASCII 암호조차도 7.2x10 ^ 15 가능성을 갖습니다.
cHao
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.