복붙노트

mysql_real_escape_string ()을 둘러싼 SQL 주입

PHP

mysql_real_escape_string ()을 둘러싼 SQL 주입

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

해결법

  1. ==============================

    1.

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

    $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";
    
  2. ==============================

    2.

    간단히 대답하면 네, 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_real_escape_string ()을 사용하여 프로그램을 공격했습니다 ...

    더 심해진다. PDO는 기본적으로 MySQL로 준비된 문장을 에뮬레이션합니다. 이는 클라이언트 측에서 기본적으로 (C 라이브러리에서) mysql_real_escape_string ()을 통해 sprintf를 수행한다는 것을 의미합니다. 이는 다음과 같이 성공적으로 삽입 될 수 있음을 의미합니다.

    $pdo->query('SET NAMES gbk');
    $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
    $stmt->execute(array("\xbf\x27 OR 1=1 /*"));
    

    이제, 에뮬레이션 된 prepared statement를 비활성화함으로써이를 방지 할 수 있다는 점은 주목할 가치가 있습니다.

    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    

    일반적으로 실제로 준비된 진술 (즉, 데이터가 쿼리와 별도의 패킷으로 전송 됨)이됩니다. 그러나 PDO는 MySQL이 기본적으로 준비 할 수없는 문법을 모방하는 것에 조용히 전념합니다 : 매뉴얼에 나열되어 있지만 적절한 서버 버전을 선택하는 것은주의하십시오.)

    나는 처음에 SET NAMES gbk 대신에 mysql_set_charset ( 'gbk')를 사용했다면이 모든 것을 막을 수 있다고 말했다. 2006 년부터 MySQL을 사용한다면 사실입니다.

    이전 MySQL 릴리스를 사용하고 있다면, mysql_real_escape_string ()의 버그는 클라이언트가 연결 인코딩을 올바르게 알았더라도 유효하지 않은 멀티 바이트 문자가 이스케이프 목적을위한 단일 바이트로 취급된다는 것을 의미합니다 이 공격은 여전히 ​​성공할 것입니다. 이 버그는 MySQL 4.1.20, 5.0.22 및 5.1.11에서 수정되었습니다.

    그러나 PDO가 5.3.6까지 mysql_set_charset ()에 대한 C API를 공개하지 않았기 때문에 이전 버전에서는 가능한 모든 명령에 대해이 공격을 막을 수 없었습니다!  이제 DSN 매개 변수로 표시됩니다.

    앞서 말했듯이이 공격이 작동하려면 데이터베이스 연결이 취약한 문자 집합을 사용하여 인코딩되어야합니다. utf8mb4는 취약하지 않지만 모든 유니 코드 문자를 지원할 수 있습니다. 따라서 대신 유니 코드 문자를 사용할 수 있습니다. 그러나 MySQL 5.5.3부터 사용 가능합니다. 다른 대안으로 utf8도 있는데, 취약하지 않으며 Unicode Basic Multilingual Plane 전체를 지원할 수 있습니다.

    또는 NO_BACKSLASH_ESCAPES SQL 모드를 활성화 할 수 있습니다. SQL 모드는 mysql_real_escape_string ()의 동작을 변경합니다. 이 모드를 사용하면 0x27이 0x527 대신 0x2727로 바뀌므로 이스케이프 프로세스가 이전에 존재하지 않는 취약한 인코딩 (예 : 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는 항상 준비된 진술을하기 때문에.

    만약 너라면:

    또는

    당신은 100 % 안전합니다.

    그렇지 않으면 mysql_real_escape_string ()을 사용하더라도 취약하다.

  3. ==============================

    3.

    @ 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');
    

    그러면 테스트 테이블의 모든 레코드가 반환됩니다. 절개 :

    배운 친구가 축하해 주니, 축하합니다. 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 절, 즉 :: 문법 참조)과의 호환성을 위해 사용하기 때문에 야생에서 흔히 볼 수있는 것만은 아닙니다 생산과 백 슬래시에 대한 특별한 의미가 없음). 또한, ircmaxell의 게시물이 설명하는 (오래 전에 고쳐진) 버그에 대한 해결책으로 명시 적으로 사용하는 것이 좋습니다. 누가 알겠지만, 일부 DBA는 addslashes ()와 같은 잘못된 이스케이프 메소드를 사용하지 않기 위해 기본적으로이 기능을 사용하도록 구성 할 수도 있습니다.

    또한 새 연결의 SQL 모드는 구성에 따라 서버에서 설정합니다 (SUPER 사용자는 언제든지 변경할 수 있음). 따라서 서버의 동작을 확실하게하려면 연결 한 후에 항상 명시 적으로 원하는 모드를 지정해야합니다.

    NO_BACKSLASH_ESCAPES를 포함하지 않도록 SQL 모드를 명시 적으로 설정하거나 작은 따옴표 문자를 사용하여 MySQL 문자열 리터럴을 인용하면이 버그는 추악한 머리를 뒤집을 수 없습니다. 각각의 escape_quotes_for_mysql ()이 사용되지 않거나 어떤 인용문에 대한 가정 문자 반복이 정확해야합니다.

    이런 이유로 NO_BACKSLASH_ESCAPES를 사용하는 사람은 ANSI_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');
    

    ... NO_BACKSLASH_ESCAPES를 포함하지 않는 SQL 모드를 명시 적으로 선택했기 때문입니다.

    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 준비 문이이 취약점으로부터 영향을받지 않기 때문에 (ircmaxell도 PHP ≥5.3.6을 사용하고 문자 집합이 DSN에 올바르게 설정되어 있거나 준비된 문 에뮬레이션이 비활성화되어 제공됨) .

    $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 준비 문이 안전하기 때문에.

    따라서 다음과 같은 경우 :

    또는

    또는

    ... 그러면 완전히 안전해야합니다 (문자열의 범위를 벗어나는 취약점은 옆으로 벗어납니다).

  4. ==============================

    4.

    글쎄, % 와일드 카드 이외에는 실제로 전달할 수있는 것이 없습니다. LIKE 문을 사용하는 경우 위험 할 수 있습니다. 공격자가 필터링하지 않으면 로그인으로 %를 넣을 수 있으며 모든 사용자의 암호를 입력해야합니다. 사람들은 종종 데이터가 쿼리 자체를 방해 할 수 없으므로 준비된 문을 사용하여 100 % 안전하다고 제안합니다. 그러나 이러한 간단한 쿼리의 경우 $ login = preg_replace ( '/ [^ a-zA-Z0-9 _] /', '', $ login)와 같은 작업을하는 것이 더 효율적일 수 있습니다.

  5. from https://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string by cc-by-sa and MIT lisence