SQL tiêm xung quanh mysql_real_escape_opes ()


644

Có khả năng tiêm SQL ngay cả khi sử dụng mysql_real_escape_string()hàm không?

Xem xét tình huống mẫu này. SQL được xây dựng trong PHP như thế này:

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

Tôi đã nghe nhiều người nói với tôi rằng mã như thế vẫn nguy hiểm và có thể hack ngay cả với mysql_real_escape_string()chức năng được sử dụng. Nhưng tôi không thể nghĩ ra bất kỳ khai thác có thể?

Tiêm cổ điển như thế này:

aaa' OR 1=1 --

đừng làm việc.

Bạn có biết bất kỳ phép tiêm nào có thể có được thông qua mã PHP ở trên không?


34
@ThiefMaster - Tôi không muốn đưa ra các lỗi dài dòng như mật khẩu người dùng không hợp lệ / mật khẩu không hợp lệ ... nó nói với các thương nhân vũ phu rằng họ có ID người dùng hợp lệ và đó chỉ là mật khẩu họ cần đoán
Đánh dấu

18
Thật kinh khủng từ quan điểm khả dụng. Đôi khi bạn không thể sử dụng biệt danh / tên người dùng / địa chỉ email chính của mình và quên điều này sau một thời gian hoặc trang web đã xóa tài khoản của bạn vì không hoạt động. Sau đó, sẽ vô cùng khó chịu nếu bạn tiếp tục thử mật khẩu và thậm chí có thể bị chặn IP của bạn mặc dù đó chỉ là tên người dùng của bạn không hợp lệ.
ThiefMaster

50
Xin vui lòng, không sử dụng các mysql_*chức năng trong mã mới . Chúng không còn được duy trì và quá trình khấu hao đã bắt đầu trên đó. Thấy hộp đỏ không? Thay vào đó, hãytìm hiểu về các câu lệnh được chuẩn bị và sử dụng PDO hoặc MySQLi - bài viết này sẽ giúp bạn quyết định. Nếu bạn chọn PDO, đây là một hướng dẫn tốt .
tereško

13
@machineaddict, kể từ 5.5 (được phát hành gần đây), các mysql_*chức năng đã tạo E_DEPRECATEDcảnh báo. Việc ext/mysqlgia hạn đã không được duy trì trong hơn 10 năm. Bạn thực sự rất ảo tưởng?
tereško

13
@machineaddict Họ mới gỡ bỏ phần mở rộng đó trên PHP 7.0 và nó chưa đến năm 2050.
GGG

Câu trả lời:


379

Hãy xem xét các truy vấn sau:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()sẽ không bảo vệ bạn chống lại điều này. Việc bạn sử dụng các dấu ngoặc đơn ( ' ') xung quanh các biến trong truy vấn của bạn là điều bảo vệ bạn chống lại điều này. Sau đây cũng là một tùy chọn:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";

9
Nhưng đây sẽ không phải là một vấn đề thực sự, bởi vì mysql_query()không thực hiện nhiều câu lệnh, phải không?
Pekka

11
@Pekka, Mặc dù ví dụ thông thường là vậy DROP TABLE, trong thực tế kẻ tấn công có nhiều khả năng SELECT passwd FROM users. Trong trường hợp sau, truy vấn thứ hai thường được thực hiện bằng cách sử dụng UNIONmệnh đề.
Jacco

58
(int)mysql_real_escape_string- điều này không có ý nghĩa. Nó không khác biệt (int)chút nào. Và họ sẽ tạo ra kết quả tương tự cho mọi đầu vào
zerkms

28
Đây là một sự lạm dụng chức năng hơn bất cứ điều gì khác. Rốt cuộc, nó được đặt tên mysql_real_escape_string, không mysql_real_escape_integer. Nó không có nghĩa là được sử dụng với các trường số nguyên.
NullUserException

11
@ircmaxell, Tuy nhiên, câu trả lời là hoàn toàn sai lệch. Rõ ràng câu hỏi là hỏi về nội dung trong trích dẫn. "Trích dẫn không có" không phải là câu trả lời cho câu hỏi này.
Pacerier

629

Câu trả lời ngắn gọn là có, có một cách để đi lạimysql_real_escape_string() .

Đối với các TRƯỜNG HỢP EDGE Rất HẤP DẪN !!!

Câu trả lời dài không quá dễ. Nó dựa trên một cuộc tấn công được chứng minh ở đây .

Cuộc tấn công

Vì vậy, hãy bắt đầu bằng cách hiển thị cuộc tấn công ...

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

Trong một số trường hợp nhất định, điều đó sẽ trả về hơn 1 hàng. Hãy phân tích những gì đang diễn ra ở đây:

  1. Chọn một bộ ký tự

    mysql_query('SET NAMES gbk');

    Để cuộc tấn công này hoạt động, chúng ta cần mã hóa mà máy chủ mong đợi trên cả kết nối để mã hóa 'như trong ASCII tức là 0x27 để có một số ký tự có byte cuối cùng là ASCII \tức là 0x5c. Khi nó quay ra, có 5 mã hóa như hỗ trợ trong MySQL 5.6 theo mặc định: big5, cp932, gb2312, gbksjis. Chúng tôi sẽ chọn gbkở đây.

    Bây giờ, rất quan trọng để lưu ý việc sử dụng SET NAMESở đây. Cái này đặt bộ ký tự TRÊN MÁY CHỦ . Nếu chúng tôi sử dụng lệnh gọi hàm C API mysql_set_charset(), chúng tôi sẽ ổn (trên các bản phát hành MySQL từ năm 2006). Nhưng nhiều hơn về lý do tại sao trong một phút ...

  2. Tải trọng

    Tải trọng mà chúng ta sẽ sử dụng cho phép tiêm này bắt đầu bằng chuỗi byte 0xbf27. Trong gbkđó, đó là một nhân vật đa bào không hợp lệ; trong latin1, đó là chuỗi ¿'. Lưu ý rằng trong latin1 gbk , 0x27trên chính nó là một 'nhân vật theo nghĩa đen .

    Chúng tôi đã chọn tải trọng này bởi vì, nếu chúng tôi gọi addslashes()nó, chúng tôi sẽ chèn ASCII \tức là 0x5ctrước 'ký tự. Vì vậy, chúng tôi sẽ kết thúc 0xbf5c27, trong đó gbklà một chuỗi hai ký tự: 0xbf5ctheo sau 0x27. Hay nói cách khác, một nhân vật hợp lệ theo sau là một người không được giải thoát '. Nhưng chúng tôi không sử dụng addslashes(). Vì vậy, bước tiếp theo ...

  3. mysql_real_escape_opes ()

    Lệnh gọi API C mysql_real_escape_string()khác với addslashes()ở chỗ nó biết bộ ký tự kết nối. Vì vậy, nó có thể thực hiện thoát đúng cho bộ ký tự mà máy chủ đang mong đợi. Tuy nhiên, cho đến thời điểm này, khách hàng nghĩ rằng chúng tôi vẫn đang sử dụng latin1cho kết nối, bởi vì chúng tôi không bao giờ nói với nó khác. Chúng tôi đã nói với máy chủ chúng tôi đang sử dụng gbk, nhưng khách hàng vẫn nghĩ rằng nó latin1.

    Do đó, lệnh gọi mysql_real_escape_string()chèn dấu gạch chéo ngược và chúng tôi có một 'ký tự treo miễn phí trong nội dung "thoát" của chúng tôi! Thực tế, nếu chúng ta nhìn vào $varbộ gbkký tự, chúng ta sẽ thấy:

    縗 'HOẶC 1 = 1 / *

    Đó chính xácnhững gì cuộc tấn công yêu cầu.

  4. Truy vấn

    Phần này chỉ là một hình thức, nhưng đây là truy vấn được kết xuất:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1

Xin chúc mừng, bạn vừa tấn công thành công một chương trình bằng mysql_real_escape_string()...

Những người xấu

Nó trở nên tồi tệ hơn. PDOmặc định để mô phỏng các câu lệnh đã chuẩn bị với MySQL. Điều đó có nghĩa là về phía khách hàng, về cơ bản, nó thực hiện chạy nước rút mysql_real_escape_string()(trong thư viện C), điều đó có nghĩa là sau đây sẽ dẫn đến việc tiêm thành công:

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

Bây giờ, đáng chú ý là bạn có thể ngăn chặn điều này bằng cách vô hiệu hóa các câu lệnh được chuẩn bị mô phỏng:

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

Điều này thường sẽ dẫn đến một tuyên bố được chuẩn bị thực sự (tức là dữ liệu được gửi trong một gói riêng biệt từ truy vấn). Tuy nhiên, lưu ý rằng PDO sẽ âm thầm dự phòng các giả lập mà MySQL không thể chuẩn bị một cách tự nhiên: những thứ mà nó có thể được liệt kê trong hướng dẫn, nhưng hãy cẩn thận để chọn phiên bản máy chủ phù hợp).

Xấu xí

Tôi đã nói ngay từ đầu rằng chúng ta có thể đã ngăn chặn tất cả những điều này nếu chúng ta đã sử dụng mysql_set_charset('gbk')thay vì SET NAMES gbk. Và điều đó đúng với điều kiện bạn đang sử dụng bản phát hành MySQL từ năm 2006.

Nếu bạn đang sử dụng một phiên bản MySQL trước, sau đó là một lỗi trong mysql_real_escape_string()có nghĩa là ký tự nhiều byte không hợp lệ như những người trong Payload của chúng tôi bị đối xử như byte duy nhất cho mục đích thoát ngay cả khi khách hàng đã được thông báo một cách chính xác của mã hóa kết nối và do đó cuộc tấn công này sẽ vẫn thành công Lỗi này đã được cố định trong MySQL 4.1.20 , 5.0.225.1.11 .

Nhưng điều tồi tệ nhất là PDOđã không để lộ API C cho mysql_set_charset()đến ngày 5.3.6, vì vậy trong các phiên bản trước, nó không thể ngăn chặn cuộc tấn công này cho mọi lệnh có thể! Bây giờ nó được hiển thị dưới dạng tham số DSN .

Ân điển cứu rỗi

Như chúng ta đã nói ngay từ đầu, để cuộc tấn công này hoạt động, kết nối cơ sở dữ liệu phải được mã hóa bằng bộ ký tự dễ bị tổn thương. utf8mb4không dễ bị tổn thương và chưa thể hỗ trợ tất cả các ký tự Unicode: vì vậy bạn có thể chọn để sử dụng mà thay vào đó, nhưng nó đã chỉ được đưa ra từ MySQL 5.5.3. Một cách khác là utf8, nó cũng không dễ bị tổn thương và có thể hỗ trợ toàn bộ Mặt phẳng đa ngôn ngữ Unicode Basic .

Ngoài ra, bạn có thể kích hoạt NO_BACKSLASH_ESCAPESchế độ SQL, trong đó (trong số những thứ khác) làm thay đổi hoạt động của mysql_real_escape_string(). Khi bật chế độ này, 0x27sẽ được thay thế bằng 0x2727thay vì 0x5c27và do đó, quá trình thoát không thể tạo các ký tự hợp lệ trong bất kỳ mã hóa dễ bị tổn thương nào mà chúng không tồn tại trước đó (nghĩa 0xbf27là vẫn 0xbf27v.v.) - vì vậy máy chủ vẫn sẽ từ chối chuỗi là không hợp lệ . Tuy nhiên, hãy xem câu trả lời của @ eggyal để biết một lỗ hổng khác có thể phát sinh từ việc sử dụng chế độ SQL này.

Ví dụ an toàn

Các ví dụ sau đây là an toàn:

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

Bởi vì máy chủ đang mong đợi 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");

Bởi vì chúng tôi đã đặt đúng bộ ký tự sao cho máy khách và máy chủ khớp.

$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 /*"));

Bởi vì chúng tôi đã tắt các tuyên bố chuẩn bị mô phỏng.

$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 /*"));

Bởi vì chúng tôi đã thiết lập đúng ký tự.

$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();

Bởi vì MySQLi luôn luôn chuẩn bị các tuyên bố chuẩn bị.

Kết thúc

Nếu bạn:

  • Sử dụng các phiên bản hiện đại của MySQL (cuối 5.1, tất cả 5.5, 5.6, v.v.) Tham số bộ ký tự DSN của AND mysql_set_charset() / $mysqli->set_charset()/ PDO (trong PHP ≥ 5.3.6)

HOẶC LÀ

  • Không sử dụng bộ ký tự dễ bị tổn thương để mã hóa kết nối (bạn chỉ sử dụng utf8/ latin1/ ascii/ etc)

Bạn an toàn 100%.

Mặt khác, bạn dễ bị tổn thương mặc dù bạn đang sử dụngmysql_real_escape_string() ...


3
PDO thi đua chuẩn bị tuyên bố cho MySQL, thực sự? Tôi không thấy bất kỳ lý do tại sao nó sẽ làm điều đó vì trình điều khiển hỗ trợ nó nguyên bản. Không?
netcoder

16
Nó làm. Họ nói trong tài liệu không. Nhưng trong mã nguồn, nó dễ thấy và dễ sửa. Tôi đánh phấn nó lên sự bất tài của các nhà phát triển.
Theodore R. Smith

5
@ TheodoreR.Smith: Không dễ để sửa chữa. Tôi đã làm việc để thay đổi mặc định, nhưng nó không tải được các bài kiểm tra khi chuyển đổi. Vì vậy, đó là một thay đổi lớn hơn nó có vẻ. Tôi vẫn hy vọng nó sẽ hoàn thành trước 5.5 ...
ircmaxell

14
@shadyyx: Không, lỗ hổng mà bài báo mô tả là về addslashes. Tôi dựa trên lỗ hổng này. Hãy thử nó. Đi lấy MySQL 5.0 và chạy khai thác này và tự mình xem. Theo như cách đưa nó vào PUT / GET / POST, thì đó là TRIVIAL. Dữ liệu đầu vào chỉ là luồng byte. char(0xBF)chỉ là một cách dễ đọc để tạo ra một byte. Tôi đã demo lỗ hổng này trực tiếp trước nhiều hội nghị. Hãy tin tôi vào điều này ... Nhưng nếu bạn không, hãy tự mình thử nó. Nó hoạt động ...
ircmaxell

5
@shadyyx: Đối với việc vượt qua sự vui nhộn như vậy trong $ _GET ... ?var=%BF%27+OR+1=1+%2F%2Atrong URL, $var = $_GET['var'];trong mã và Bob là chú của bạn.
cHao

183

TL; DR

mysql_real_escape_string()sẽ không bảo vệ bất cứ điều gì (và có thể làm mất dữ liệu của bạn) nếu:

  • NO_BACKSLASH_ESCAPESChế độ SQL của MySQL được bật ( có thể là như vậy, trừ khi bạn rõ ràng chọn một chế độ SQL khác mỗi khi bạn kết nối ); và

  • chuỗi ký tự SQL của bạn được trích dẫn bằng cách sử dụng các ký tự trích dẫn kép ".

Điều này đã được gửi là lỗi # 72458 và đã được sửa trong MySQL v5.7.6 (xem phần có tiêu đề " The Saving Grace ", bên dưới).

Đây là một cái khác, (có lẽ ít hơn?) TRƯỜNG HỢP EDGE tối nghĩa !!!

Để tỏ lòng tôn kính với câu trả lời xuất sắc của @ ircmaxell (thực sự, điều này được cho là nịnh hót và không đạo văn!), Tôi sẽ chấp nhận định dạng của anh ấy:

Cuộc tấn công

Bắt đầu với một cuộc biểu tình ...

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

Điều này sẽ trả lại tất cả các hồ sơ từ testbảng. Một mổ xẻ:

  1. Chọn Chế độ SQL

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

    Như tài liệu dưới Chuỗi ký tự :

    Có một số cách để bao gồm các ký tự trích dẫn trong một chuỗi:

    • Một phạm vi phạm lỗi 'bên trong một chuỗi được trích dẫn với phạm lỗi 'có thể được viết là ''hung phạm.

    • Một phạm vi phạm lỗi "bên trong một chuỗi được trích dẫn với phạm lỗi "có thể được viết là ""hung phạm.

    • Đứng trước nhân vật trích dẫn bởi một nhân vật thoát hiểm (Hồi phạm \).

    • Một phạm vi hướng 'nội trong một chuỗi được trích dẫn với ( "không cần xử lý đặc biệt và không cần phải tăng gấp đôi hoặc thoát. Theo cách tương tự, phạm lỗi "trong phạm vi một chuỗi được trích dẫn với phạm vi 'không cần điều trị đặc biệt.

    Nếu chế độ SQL của máy chủ bao gồm NO_BACKSLASH_ESCAPES, thì thứ ba trong số các tùy chọn này là cách tiếp cận thông thường được áp dụng bởi siêu vi mysql_real_escape_string()không có sẵn: thay vào đó, một trong hai tùy chọn đầu tiên phải được sử dụng. Lưu ý rằng tác dụng của viên đạn thứ tư là người ta nhất thiết phải biết ký tự sẽ được sử dụng để trích dẫn nghĩa đen để tránh làm mất dữ liệu của một người.

  2. Tải trọng

    " OR 1=1 -- 

    Tải trọng bắt đầu tiêm này hoàn toàn theo nghĩa đen với "nhân vật. Không có mã hóa cụ thể. Không có nhân vật đặc biệt. Không có byte lạ.

  3. mysql_real_escape_opes ()

    $var = mysql_real_escape_string('" OR 1=1 -- ');

    May mắn thay, mysql_real_escape_string()không kiểm tra chế độ SQL và điều chỉnh hành vi của nó cho phù hợp. Xem 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);
    }

    Do đó, một hàm cơ bản khác escape_quotes_for_mysql(), được gọi nếu NO_BACKSLASH_ESCAPESchế độ SQL được sử dụng. Như đã đề cập ở trên, một chức năng như vậy cần phải biết ký tự nào sẽ được sử dụng để trích dẫn nghĩa đen để lặp lại nó mà không làm cho ký tự trích dẫn khác không được lặp lại theo nghĩa đen.

    Tuy nhiên, hàm này tùy ý giả định rằng chuỗi sẽ được trích dẫn bằng cách sử dụng 'ký tự trích dẫn đơn . Xem 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++= '\'';
        }

    Vì vậy, nó để lại các "ký tự trích dẫn kép không bị ảnh hưởng (và nhân đôi tất cả các 'ký tự trích dẫn đơn ) bất kể ký tự thực tế được sử dụng để trích dẫn theo nghĩa đen ! Trong trường hợp của chúng tôi $varvẫn giống hệt như cuộc tranh luận được cung cấp cho nhóm mysql_real_escape_string()củaititit như thể không có sự trốn thoát nào xảy ra cả .

  4. Truy vấn

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

    Một cái gì đó của một hình thức, truy vấn được kết xuất là:

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1

Như người bạn đã học của tôi nói: xin chúc mừng, bạn vừa tấn công thành công một chương trình bằng cách sử dụng mysql_real_escape_string()...

Những người xấu

mysql_set_charset()không thể giúp đỡ, vì điều này không liên quan gì đến các bộ ký tự; cũng không thể mysqli::real_escape_string(), vì đó chỉ là một trình bao bọc khác nhau xung quanh cùng chức năng này.

Vấn đề, nếu chưa rõ ràng, đó là lời kêu gọi mysql_real_escape_string() không thể biết được nhân vật nào sẽ được trích dẫn, vì điều đó còn lại để nhà phát triển quyết định sau đó. Vì vậy, trong NO_BACKSLASH_ESCAPESchế độ, theo nghĩa đen, không có cách nào mà chức năng này có thể thoát an toàn mọi đầu vào để sử dụng với trích dẫn tùy ý (ít nhất, không phải không nhân đôi các ký tự không yêu cầu nhân đôi và do đó trộn dữ liệu của bạn).

Xấu xí

Nó trở nên tồi tệ hơn. NO_BACKSLASH_ESCAPEScó thể không phải là điều gì quá phổ biến do sự cần thiết phải sử dụng để tương thích với SQL tiêu chuẩn (ví dụ, xem phần 5.3 của đặc tả SQL-92 , cụ thể là <quote symbol> ::= <quote><quote>sản xuất ngữ pháp và không có ý nghĩa đặc biệt nào được đưa ra cho dấu gạch chéo ngược). Hơn nữa, việc sử dụng nó được khuyến nghị rõ ràng là một cách khắc phục lỗi (đã được sửa từ lâu) mà bài đăng của ircmaxell mô tả. Ai biết được, một số DBA thậm chí có thể cấu hình nó để được bật theo mặc định như là phương tiện không khuyến khích sử dụng các phương thức thoát không chính xác như addslashes().

Ngoài ra, chế độ SQL của kết nối mới được máy chủ đặt theo cấu hình của nó ( SUPERngười dùng có thể thay đổi bất cứ lúc nào); do đó, để chắc chắn về hành vi của máy chủ, bạn phải luôn chỉ định rõ ràng chế độ mong muốn của mình sau khi kết nối.

Ân điển cứu rỗi

Vì vậy, miễn là bạn luôn luôn đặt chế độ SQL một cách rõ ràng không bao gồm NO_BACKSLASH_ESCAPEShoặc trích dẫn các chuỗi ký tự chuỗi MySQL sử dụng ký tự trích dẫn đơn, lỗi này escape_quotes_for_mysql()không thể sử dụng cái đầu xấu xí của nó: tương ứng sẽ không được sử dụng hoặc giả định về ký tự trích dẫn nào cần lặp lại đúng

Vì lý do này, tôi khuyên mọi người sử dụng NO_BACKSLASH_ESCAPEScũng kích hoạt ANSI_QUOTESchế độ, vì nó sẽ buộc sử dụng theo nghĩa đen của chuỗi ký tự trích dẫn. Lưu ý rằng điều này không ngăn SQL tiêm trong trường hợp các chữ được trích dẫn kép được sử dụng, nó chỉ làm giảm khả năng xảy ra (vì các truy vấn bình thường, không độc hại sẽ thất bại).

Trong PDO, cả chức năng tương đương PDO::quote()và trình giả lập câu lệnh đã chuẩn bị của nó đều yêu cầu mysql_handle_quoter()chính xác điều này: nó đảm bảo rằng chữ được thoát được trích dẫn trong dấu ngoặc đơn, vì vậy bạn có thể chắc chắn rằng PDO luôn miễn nhiễm với lỗi này.

Kể từ MySQL v5.7.6, lỗi này đã được sửa. Xem nhật ký thay đổi :

Chức năng được thêm hoặc thay đổi

Ví dụ an toàn

Được xử lý cùng với lỗi được giải thích bởi ircmaxell, các ví dụ sau đây hoàn toàn an toàn (giả sử rằng một người đang sử dụng MySQL muộn hơn 4.1.20, 5.0.22, 5.1.11; hoặc một người không sử dụng mã hóa kết nối 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');

... bởi vì chúng tôi đã chọn rõ ràng một chế độ SQL không bao gồm 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");

... bởi vì chúng tôi trích dẫn chuỗi ký tự của chúng tôi bằng dấu ngoặc đơn.

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);

... bởi vì các câu lệnh được chuẩn bị PDO miễn nhiễm với lỗ hổng này (và ircmaxell cũng vậy, miễn là bạn đang sử dụng PHP≥5.3.6 và bộ ký tự đã được đặt chính xác trong DSN; hoặc mô phỏng câu lệnh được chuẩn bị đã bị vô hiệu hóa) .

$var  = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

... bởi vì quote()chức năng của PDO không chỉ thoát khỏi nghĩa đen mà còn trích dẫn nó (bằng các 'ký tự trích dẫn đơn ); lưu ý rằng để tránh lỗi của ircmaxell trong trường hợp này, bạn phải sử dụng PHP≥5.3.6 đã đặt chính xác bộ ký tự trong DSN.

$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

... bởi vì các tuyên bố chuẩn bị của MySQL là an toàn.

Kết thúc

Do đó, nếu bạn:

  • sử dụng báo cáo chuẩn bị bản địa

HOẶC LÀ

  • sử dụng MySQL v5.7.6 trở lên

HOẶC LÀ

  • trong Ngoài ra để sử dụng một trong những giải pháp trong tóm tắt ircmaxell của, sử dụng tại một thiểu số:

    • PDO;
    • chuỗi ký tự đơn trích dẫn; hoặc là
    • một chế độ SQL được thiết lập rõ ràng không bao gồm NO_BACKSLASH_ESCAPES

... thì bạn nên hoàn toàn an toàn (các lỗ hổng bên ngoài phạm vi chuỗi thoát sang một bên).


10
Vì vậy, TL; DR sẽ giống như "có chế độ máy chủ mysql NO_BACKSLASH_ESCAPES có thể gây ra tiêm nếu bạn không sử dụng dấu ngoặc đơn.
Ý thức chung của bạn

Tôi không thể truy cập bug.mysql.com/orms.php?id=72458 ; Tôi chỉ nhận được một trang bị từ chối truy cập. Có phải nó đang bị ẩn khỏi công chúng do là một vấn đề bảo mật? Ngoài ra, tôi có hiểu chính xác từ câu trả lời này rằng bạn là người phát hiện ra lỗ hổng không? Nếu vậy, xin chúc mừng.
Đánh dấu Amery

1
Mọi người không nên sử dụng "cho chuỗi ở nơi đầu tiên. SQL nói rằng đó là định danh. Nhưng eh ... chỉ là một ví dụ khác của MySQL nói rằng "tiêu chuẩn vít, tôi sẽ làm bất cứ điều gì tôi muốn". (May mắn thay, bạn có thể đưa ANSI_QUOTESvào chế độ để khắc phục lỗi trích dẫn. Mặc dù vậy, sự coi thường các tiêu chuẩn là một vấn đề lớn hơn có thể đòi hỏi các biện pháp nghiêm trọng hơn.)
cHao

2
@DanAllen: câu trả lời của tôi rộng hơn một chút, ở chỗ bạn có thể tránh được lỗi đặc biệt này thông qua quote()chức năng PDO nhưng các câu lệnh được chuẩn bị là cách an toàn hơn và phù hợp hơn để tránh tiêm nói chung. Tất nhiên, nếu bạn đã kết hợp trực tiếp các biến không được giải mã vào SQL của mình thì bạn chắc chắn dễ bị tiêm cho dù bạn sử dụng phương pháp nào sau đó.
eggyal

1
@eggyall: Hệ thống của chúng tôi dựa vào ví dụ an toàn thứ 2 ở trên. Có lỗi, trong đó mysql_real_escape_opes đã bị bỏ qua. Sửa chữa những người trong chế độ khẩn cấp dường như là con đường thận trọng, hy vọng chúng ta không bị bắt nạt trước khi sửa chữa. Lý do của tôi là chuyển đổi sang các báo cáo đã chuẩn bị sẽ là một quá trình dài hơn nhiều sẽ phải đến sau. Là lý do chuẩn bị tuyên bố là an toàn hơn thực tế là lỗi không tạo lỗ hổng? Nói cách khác, việc thực hiện đúng ví dụ thứ 2 ở trên có an toàn như các tuyên bố đã chuẩn bị không?
Dan ALLen

18

Chà, thực sự không có gì có thể vượt qua điều đó, ngoài %ký tự đại diện. Sẽ rất nguy hiểm nếu bạn đang sử dụng LIKEcâu lệnh vì kẻ tấn công có thể đặt %tên đăng nhập nếu bạn không lọc nó ra và sẽ phải xác nhận lại mật khẩu của bất kỳ người dùng nào của bạn. Mọi người thường đề xuất sử dụng các câu lệnh được chuẩn bị để làm cho nó an toàn 100%, vì dữ liệu không thể can thiệp vào chính truy vấn theo cách đó. Nhưng đối với các truy vấn đơn giản như vậy, có thể sẽ hiệu quả hơn khi làm một cái gì đó như$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);


2
+1, nhưng các ký tự đại diện là cho mệnh đề THÍCH, không phải là đẳng thức đơn giản.
Dor

7
Bằng biện pháp nào bạn xem xét một sự thay thế đơn giản more efficienthơn là sử dụng các tuyên bố đã chuẩn bị? (Các câu lệnh được chuẩn bị luôn hoạt động, thư viện có thể được sửa chữa nhanh chóng trong trường hợp bị tấn công, không để lộ lỗi của con người [chẳng hạn như gõ sai chuỗi thay thế hoàn chỉnh] và có lợi ích hiệu suất đáng kể nếu câu lệnh được sử dụng lại.)
MatBailie

7
@Slava: Bạn chỉ giới hạn hiệu quả tên người dùng và mật khẩu cho các ký tự từ. Hầu hết những người biết bất cứ điều gì về bảo mật sẽ coi đó là một ý tưởng tồi, vì nó thu hẹp không gian tìm kiếm đáng kể. Tất nhiên họ cũng coi đó là một ý tưởng tồi để lưu trữ mật khẩu Cleartext trong cơ sở dữ liệu, nhưng chúng ta không cần phải giải quyết vấn đề. :)
cHao

2
@cHao, đề nghị của tôi chỉ liên quan đến đăng nhập. Rõ ràng là bạn không cần phải lọc mật khẩu, xin lỗi, nó không được nêu rõ trong câu trả lời của tôi. Nhưng thực sự đó có thể là ý tưởng tốt. Sử dụng "không gian cây không biết gì về đá" thay vì "a4üua3! @V \" ä90; 8f "khó nhớ và khó đọc hơn. bạn đã sử dụng chính xác 4 từ - đó vẫn sẽ là khoảng 3,3 * 10 ^ 12 kết hợp. :)
Slava

2
@Slava: Tôi đã thấy ý tưởng đó trước đây; xem xkcd.com/936 . Vấn đề là, toán học không hoàn toàn chịu đựng được. Ví dụ mật khẩu 17 ký tự của bạn sẽ có 96 ^ 17 khả năng, và đó là nếu bạn quên các ô và giới hạn bản thân với ASCII có thể in được. Đó là khoảng 4,5x10 ^ 33. Chúng ta đang nói về nghĩa đen gấp hàng nghìn tỷ lần làm việc với lực lượng vũ trang. Ngay cả mật khẩu ASCII 8 ký tự cũng có 7.2x10 ^ 15 khả năng - gấp 3 nghìn lần.
cHao
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.