Câu hỏi Làm thế nào tôi có thể ngăn chặn SQL injection trong PHP?


Nếu đầu vào của người dùng được chèn mà không sửa đổi vào một truy vấn SQL, thì ứng dụng sẽ trở nên dễ bị tổn thương SQL injection, như trong ví dụ sau:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

Đó là bởi vì người dùng có thể nhập một cái gì đó như value'); DROP TABLE table;--và truy vấn trở thành:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

Điều gì có thể được thực hiện để ngăn chặn điều này xảy ra?


2781


gốc




Các câu trả lời:


Sử dụng câu lệnh đã chuẩn bị và truy vấn được tham số hóa. Đây là những câu lệnh SQL được gửi đến và phân tích cú pháp bởi máy chủ cơ sở dữ liệu riêng biệt với bất kỳ tham số nào. Bằng cách này, kẻ tấn công không thể tiêm SQL độc hại là điều không thể.

Về cơ bản bạn có hai lựa chọn để đạt được điều này:

  1. Sử dụng PDO (đối với bất kỳ trình điều khiển cơ sở dữ liệu được hỗ trợ nào):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. Sử dụng MySQLi (đối với MySQL):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }
    

Nếu bạn đang kết nối với cơ sở dữ liệu khác với MySQL, có tùy chọn thứ hai dành riêng cho trình điều khiển mà bạn có thể tham khảo (ví dụ: pg_prepare() và pg_execute() cho PostgreSQL). PDO là lựa chọn phổ quát.

Thiết lập kết nối đúng cách

Lưu ý rằng khi sử dụng PDO truy cập cơ sở dữ liệu MySQL thực báo cáo chuẩn bị là không được sử dụng theo mặc định. Để khắc phục điều này, bạn phải vô hiệu hóa mô phỏng các câu lệnh đã chuẩn bị. Một ví dụ về việc tạo kết nối bằng PDO là:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Trong ví dụ trên, chế độ lỗi không thực sự cần thiết, nhưng bạn nên thêm nó. Bằng cách này, tập lệnh sẽ không dừng lại với Fatal Error khi có sự cố. Và nó mang lại cho nhà phát triển cơ hội catch bất kỳ lỗi nào thrown là PDOExceptionS.

Những gì là bắt buộc, tuy nhiên, là lần đầu tiên setAttribute() line, cho PDO biết để vô hiệu hóa các câu lệnh được chuẩn bị và sử dụng được mô phỏng thực báo cáo chuẩn bị. Điều này đảm bảo rằng câu lệnh và các giá trị không được phân tích cú pháp bằng PHP trước khi gửi nó đến máy chủ MySQL (cho phép kẻ tấn công có thể không có cơ hội để tiêm SQL độc hại).

Mặc dù bạn có thể đặt charset trong các tùy chọn của hàm dựng, điều quan trọng cần lưu ý là các phiên bản 'cũ' của PHP (<5.3.6) âm thầm bỏ qua tham số ký tự trong DSN.

Giải trình

Điều gì xảy ra là câu lệnh SQL bạn chuyển tới prepare được phân tích và biên dịch bởi máy chủ cơ sở dữ liệu. Bằng cách chỉ định các thông số (hoặc là ? hoặc tham số được đặt tên như :name trong ví dụ trên) bạn nói cho cơ sở dữ liệu nơi bạn muốn lọc. Sau đó, khi bạn gọi execute, câu lệnh đã chuẩn bị được kết hợp với các giá trị tham số mà bạn chỉ định.

Điều quan trọng ở đây là các giá trị tham số được kết hợp với câu lệnh được biên dịch, không phải là một chuỗi SQL. SQL injection hoạt động bằng cách lừa kịch bản lệnh bao gồm các chuỗi độc hại khi nó tạo SQL để gửi tới cơ sở dữ liệu. Vì vậy, bằng cách gửi SQL thực tế một cách riêng biệt với các tham số, bạn sẽ hạn chế nguy cơ kết thúc với một cái gì đó mà bạn không có ý định. Bất kỳ tham số nào bạn gửi khi sử dụng câu lệnh đã chuẩn bị sẽ chỉ được coi là chuỗi (mặc dù công cụ cơ sở dữ liệu có thể thực hiện một số tối ưu hóa để các tham số có thể kết thúc dưới dạng số, dĩ nhiên). Trong ví dụ trên, nếu $namebiến chứa 'Sarah'; DELETE FROM employees kết quả sẽ đơn giản là tìm kiếm chuỗi "'Sarah'; DELETE FROM employees"và bạn sẽ không kết thúc với một cái bàn trống.

Một lợi ích khác của việc sử dụng câu lệnh đã chuẩn bị là nếu bạn thực thi cùng một câu lệnh nhiều lần trong cùng một phiên, nó sẽ chỉ được phân tích cú pháp và biên dịch một lần, cho bạn một số tăng tốc.

Oh, và kể từ khi bạn hỏi về làm thế nào để làm điều đó cho một chèn, đây là một ví dụ (sử dụng PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

Các câu lệnh chuẩn bị có thể được sử dụng cho các truy vấn động?

Mặc dù bạn vẫn có thể sử dụng các câu lệnh đã chuẩn bị cho các tham số truy vấn, cấu trúc của truy vấn động tự nó không thể được parametrized và các tính năng truy vấn nhất định không thể được parametrized.

Đối với các tình huống cụ thể này, điều tốt nhất cần làm là sử dụng bộ lọc danh sách trắng hạn chế các giá trị có thể có.

// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

7954



Chỉ cần thêm bởi vì tôi không thấy nó ở bất cứ nơi nào khác ở đây, một tuyến phòng thủ khác là tường lửa ứng dụng web (WAF) có thể có các quy tắc được thiết lập để tìm kiếm các cuộc tấn công tiêm sql: - jkerak
Ngoài ra, tài liệu chính thức của mysql_query chỉ cho phép thực hiện một truy vấn, do đó, bất kỳ truy vấn nào khác bên cạnh; bị bỏ qua. Ngay cả khi điều này đã không được chấp nhận thì có rất nhiều hệ thống theo PHP 5.5.0 và có thể sử dụng chức năng này. php.net/manual/en/function.mysql-query.php - Randall Valenciano
Đây là một thói quen xấu nhưng là một giải pháp sau vấn đề: Không chỉ cho SQL injection mà còn cho bất kỳ loại tiêm nào (ví dụ như có một lỗ tiêm khuôn mẫu xem trong khung F3 v2) nếu bạn có một trang web hoặc ứng dụng cũ đã sẵn sàng từ các lỗi tiêm, một giải pháp là gán lại các giá trị của các vars được xác định trước của bạn như $ _POST với các giá trị được thoát tại bootstrap. Bởi PDO, vẫn có thể thoát (cũng cho các khung ngày nay): substr ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1) - Alix
Câu trả lời này thiếu lời giải thích về tuyên bố chuẩn bị là gì - một điều - đó là một hiệu suất trúng nếu bạn sử dụng nhiều câu lệnh chuẩn bị sẵn sàng trong yêu cầu của mình và đôi khi nó chiếm tới hiệu suất 10x. Trường hợp tốt hơn sẽ được sử dụng PDO với tham số ràng buộc tắt, nhưng chuẩn bị tuyên bố tắt. - donis
Sử dụng PDO tốt hơn, trong trường hợp bạn đang sử dụng truy vấn trực tiếp, hãy đảm bảo bạn sử dụng mysqli :: escape_string - Kassem Itani


Cảnh báo:   Mã mẫu của câu trả lời này (giống như mã mẫu của câu hỏi) sử dụng PHP mysql mở rộng, không được chấp nhận trong PHP 5.5.0 và được gỡ bỏ hoàn toàn trong PHP 7.0.0.

Nếu bạn đang sử dụng phiên bản PHP gần đây, mysql_real_escape_string tùy chọn được nêu bên dưới sẽ không còn khả dụng (mặc dù mysqli::escape_string là một tương đương hiện đại). Những ngày này mysql_real_escape_string tùy chọn sẽ chỉ có ý nghĩa đối với mã kế thừa trên một phiên bản cũ của PHP.


Bạn có hai tùy chọn - thoát khỏi các ký tự đặc biệt trong unsafe_variablehoặc sử dụng truy vấn được tham số hóa. Cả hai sẽ bảo vệ bạn khỏi việc tiêm SQL. Truy vấn tham số được coi là thực hành tốt hơn nhưng sẽ yêu cầu thay đổi sang một phần mở rộng mysql mới hơn trong PHP trước khi bạn có thể sử dụng nó.

Chúng tôi sẽ bao gồm chuỗi tác động thấp hơn thoát khỏi một chuỗi đầu tiên.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

Xem thêm, các chi tiết của mysql_real_escape_string chức năng.

Để sử dụng truy vấn được tham số hóa, bạn cần sử dụng MySQLi thay vì MySQL chức năng. Để viết lại ví dụ của bạn, chúng tôi sẽ cần một cái gì đó như sau.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

Chức năng chính mà bạn muốn đọc trên đó sẽ là mysqli::prepare.

Ngoài ra, như những người khác đã đề xuất, bạn có thể thấy nó hữu ích / dễ dàng hơn để nâng cấp một lớp trừu tượng với một cái gì đó như PDO.

Xin lưu ý rằng trường hợp bạn hỏi là một trường hợp khá đơn giản và các trường hợp phức tạp hơn có thể yêu cầu các cách tiếp cận phức tạp hơn. Đặc biệt:

  • Nếu bạn muốn thay đổi cấu trúc của SQL dựa trên đầu vào của người dùng, các truy vấn được tham số hóa sẽ không giúp đỡ, và yêu cầu thoát không được bao hàm bởi mysql_real_escape_string. Trong trường hợp này, bạn sẽ tốt hơn khi truyền đầu vào của người dùng thông qua một danh sách trắng để đảm bảo chỉ các giá trị 'an toàn' được cho phép thông qua.
  • Nếu bạn sử dụng số nguyên từ đầu vào của người dùng trong một điều kiện và lấy mysql_real_escape_string cách tiếp cận, bạn sẽ bị vấn đề được mô tả bởi Đa thức trong các bình luận bên dưới. Trường hợp này là phức tạp hơn vì số nguyên sẽ không được bao quanh bởi dấu ngoặc kép, vì vậy bạn có thể giải quyết bằng cách xác thực rằng đầu vào của người dùng chỉ chứa chữ số.
  • Có khả năng các trường hợp khác mà tôi không biết. Bạn có thể tìm thấy điều này là một tài nguyên hữu ích về một số vấn đề tinh tế hơn mà bạn có thể gặp phải.

1514



sử dụng mysql_real_escape_string là đủ hay tôi phải sử dụng tham số quá? - peiman F.
@peimanF. giữ một thực hành tốt bằng cách sử dụng truy vấn parametrized, ngay cả trên một dự án địa phương. Với các truy vấn được parametrized bạn được đảm bảo rằng sẽ không có SQL injection. Nhưng hãy nhớ rằng bạn nên vệ sinh dữ liệu để tránh truy xuất không có thật (ví dụ: tiêm XSS, chẳng hạn như đặt mã HTML vào văn bản) với htmlentities ví dụ - Goufalite
@peimanF. Thực hành tốt cho các truy vấn được tham số và giá trị liên kết, nhưng chuỗi thoát thực sự là tốt cho bây giờ - Richard


Mọi câu trả lời ở đây chỉ bao gồm một phần của vấn đề.
Trong thực tế, có bốn các phần truy vấn khác nhau mà chúng ta có thể thêm vào động đó:

  • một chuỗi
  • một số
  • một số nhận dạng
  • một từ khóa cú pháp.

và các câu lệnh chuẩn bị chỉ bao gồm 2 trong số chúng

Nhưng đôi khi chúng tôi phải làm cho truy vấn của chúng tôi thậm chí còn năng động hơn, thêm các toán tử hoặc số nhận dạng.
Vì vậy, chúng tôi sẽ cần các kỹ thuật bảo vệ khác nhau.

Nói chung, cách tiếp cận bảo vệ như vậy dựa trên danh sách trắng. Trong trường hợp này, mọi thông số động phải được mã hóa cứng trong tập lệnh của bạn và được chọn từ tập hợp đó.
Ví dụ: để đặt hàng động:

$orders  = array("name","price","qty"); //field names
$key     = array_search($_GET['sort'],$orders)); // see if we have such a name
$orderby = $orders[$key]; //if not, first one will be set automatically. smart enuf :)
$query   = "SELECT * FROM `table` ORDER BY $orderby"; //value is safe

Tuy nhiên, có một cách khác để bảo mật mã định danh - thoát. Miễn là bạn có một số nhận dạng được trích dẫn, bạn có thể thoát khỏi các dấu gạch chéo bên trong bằng cách nhân đôi chúng.

Như một bước nữa, chúng ta có thể mượn một ý tưởng thực sự tuyệt vời khi sử dụng một số trình giữ chỗ (một proxy đại diện cho giá trị thực tế trong truy vấn) từ các câu lệnh đã chuẩn bị và tạo ra một trình giữ chỗ của một kiểu khác - một trình giữ chỗ định danh.

Vì vậy, để làm cho câu chuyện dài ngắn gọn: đó là trình giữ chỗ, không phải tuyên bố chuẩn bị có thể được coi là một viên đạn bạc.

Vì vậy, đề xuất chung có thể được diễn đạt như
Miễn là bạn đang thêm các phần động vào truy vấn bằng trình giữ chỗ (và các trình giữ chỗ này được xử lý đúng cách), bạn có thể chắc chắn rằng truy vấn của mình an toàn.

Tuy nhiên, có một vấn đề với các từ khóa cú pháp SQL (chẳng hạn như AND, DESC và như vậy) nhưng danh sách trắng dường như là cách tiếp cận duy nhất trong trường hợp này.

Cập nhật

Mặc dù có một thỏa thuận chung về các phương pháp hay nhất về bảo vệ chống tiêm SQL, có vẫn còn nhiều thực hành xấu. Và một số trong số chúng quá sâu bắt nguồn từ tâm trí của người dùng PHP. Ví dụ, trên trang này có (mặc dù vô hình cho hầu hết khách truy cập) hơn 80 câu trả lời đã xóa - tất cả bị xóa bởi cộng đồng do chất lượng kém hoặc quảng bá các thực tiễn xấu và lỗi thời. Tệ hơn nữa, một số câu trả lời xấu không bị xóa mà là thịnh vượng.

Ví dụ, đó (1)  là (2)  vẫn còn (3)  nhiều (4)  câu trả lời (5), kể cả câu trả lời lớn thứ hai gợi ý bạn thoát chuỗi thủ công - một cách tiếp cận lỗi thời đã được chứng minh là không an toàn.

Hoặc có một câu trả lời tốt hơn một chút cho thấy một phương pháp định dạng chuỗi khác và thậm chí tự hào nó như thuốc chữa bách bệnh tối thượng. Trong khi tất nhiên, nó không phải là. Phương pháp này không tốt hơn định dạng chuỗi thông thường nhưng nó giữ tất cả các nhược điểm của nó: nó chỉ áp dụng cho các chuỗi và, như bất kỳ định dạng thủ công nào khác, về cơ bản là tùy chọn, không bắt buộc, dễ bị lỗi của con người.

Tôi nghĩ rằng tất cả điều này vì một mê tín dị đoan rất cũ, được hỗ trợ bởi các cơ quan như vậy OWASP hoặc là Hướng dẫn sử dụng PHP, tuyên bố sự bình đẳng giữa bất cứ điều gì "thoát" và bảo vệ khỏi việc tiêm SQL.

Bất kể những gì PHP hướng dẫn sử dụng cho các lứa tuổi, *_escape_string không có nghĩa là làm cho dữ liệu an toàn và không bao giờ được dự định. Bên cạnh việc vô dụng đối với bất kỳ phần SQL nào khác ngoài chuỗi, việc thoát bằng tay là sai bởi vì nó là thủ công ngược lại với tự động.

Và OWASP làm cho nó thậm chí tệ hơn, nhấn mạnh vào việc thoát đầu vào của người dùng đó là một điều vô nghĩa hoàn toàn: không nên có những từ như vậy trong bối cảnh bảo vệ tiêm. Mỗi biến có khả năng gây nguy hiểm - bất kể nguồn nào! Hoặc, nói cách khác - mọi biến phải được định dạng đúng để được đưa vào truy vấn - bất kể nguồn nào lại. Đó là điểm đến quan trọng. Khoảnh khắc một nhà phát triển bắt đầu tách con cừu ra khỏi dê (suy nghĩ xem một số biến cụ thể có "an toàn" hay không) anh ta bước đầu tiên hướng tới thảm họa. Chưa kể rằng ngay cả những từ ngữ cho thấy số lượng lớn thoát tại điểm nhập cảnh, giống như các tính năng báo giá rất kỳ diệu - đã bị coi thường, không được chấp nhận và loại bỏ.

Vì vậy, không giống như bất cứ điều gì "thoát", báo cáo chuẩn bị  biện pháp thực sự bảo vệ khỏi việc tiêm SQL (nếu có).

Nếu bạn vẫn chưa thuyết phục, đây là một lời giải thích từng bước tôi đã viết, Hướng dẫn của Hitchhiker để ngăn chặn SQL Injection, nơi tôi đã giải thích tất cả những vấn đề này một cách chi tiết và thậm chí biên soạn một phần hoàn toàn dành riêng cho các thực hành xấu và tiết lộ của chúng.


946



Tuyệt vời, cũng nghĩ ra bài viết. Tôi có thể thêm rằng bằng cách sử dụng các bộ lọc Sanitize của PHP là loại (nhưng không chính xác) một danh sách trắng các loại. Ví dụ, FILTER_SANITIZE_NUMBER_INT chỉ cho phép các ký tự số, do đó các ký tự danh sách trắng, không phải toàn bộ chuỗi. Kết hợp với các phát biểu đã được chuẩn bị, nó tạo ra một phương pháp tiếp cận "đai và treo" tốt. - Sablefoste
@Sablefoste bạn không cần danh sách trắng ở đây. Bất kỳ vệ sinh nào cũng sẽ dư thừa. Ít quy tắc để làm theo, những sai lầm ít hơn bạn sẽ thực hiện. Mặc dù bạn có thể thực hiện bất kỳ xác nhận hợp lệ nào, hãy làm điều đó vì lợi ích của logic ứng dụng của bạn, nhưng không phải cho cơ sở dữ liệu. - Your Common Sense


Tôi khuyên bạn nên sử dụng PDO (Các đối tượng dữ liệu PHP) để chạy các truy vấn SQL được tham số hóa.

Điều này không chỉ bảo vệ chống lại việc tiêm SQL mà còn tăng tốc các truy vấn.

Và bằng cách sử dụng PDO thay vì mysql_, mysqli_pgsql_ chức năng, bạn làm cho ứng dụng của bạn trừu tượng hơn một chút từ cơ sở dữ liệu, trong trường hợp hiếm hoi mà bạn phải chuyển đổi nhà cung cấp cơ sở dữ liệu.


770



sao PDO không bọc mysqli cho MySQL DBs? Trong trường hợp đó chắc chắn nó không thể nhanh hơn mysqli. Tôi vẫn muốn giới thiệu nó mặc dù. Đó là giao diện tốt hơn nhiều so với API mysqli. - Peter Bagnall
Sử dụng truy vấn tham số là những gì tăng tốc các truy vấn. Về mặt kỹ thuật, mysqli thậm chí có thể nhanh hơn bằng một biên độ rất nhỏ. Lượng thời gian thực tế mà máy chủ thực hiện để trả lời truy vấn sẽ làm lu mờ bất kỳ sự khác biệt nào về thời gian có thể xảy ra do bạn đang sử dụng trình bao bọc. Nhưng mysqli được gắn với cơ sở dữ liệu. Nếu bạn muốn sử dụng một công cụ cơ sở dữ liệu khác, bạn phải thay đổi tất cả các cuộc gọi sử dụng mysqli. Không phải như vậy cho PDO. - Kibbee
PDO không hỗ trợ động order by không may :( - Horse


Sử dụng PDO và các truy vấn được chuẩn bị.

($conn là một PDO vật)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

567



Từ wikipedia: Các câu lệnh được chuẩn bị có khả năng đàn hồi chống lại SQL injection, bởi vì các giá trị tham số, được truyền sau này bằng cách sử dụng một giao thức khác, không cần phải được thoát đúng cách. Nếu mẫu câu lệnh ban đầu không được bắt nguồn từ đầu vào bên ngoài, thì việc chèn SQL không thể xảy ra. - Imran


Như bạn có thể thấy, mọi người đề nghị bạn sử dụng các câu lệnh chuẩn bị nhiều nhất. Nó không sai, nhưng khi truy vấn của bạn được thực hiện chỉ một lần cho mỗi quá trình, sẽ có một hình phạt hiệu suất nhỏ.

Tôi đã phải đối mặt với vấn đề này, nhưng tôi nghĩ rằng tôi đã giải quyết nó trong rất cách tinh vi - cách tin tặc sử dụng để tránh sử dụng dấu ngoặc kép. Tôi đã sử dụng điều này cùng với các câu lệnh được mô phỏng đã được mô phỏng. Tôi sử dụng nó để ngăn chặn tất cả các các loại tấn công SQL injection có thể xảy ra.

Cách tiếp cận của tôi:

  • Nếu bạn mong đợi đầu vào là số nguyên, hãy đảm bảo có thật không số nguyên. Trong một ngôn ngữ kiểu biến như PHP nó là cái này rất quan trọng. Bạn có thể sử dụng ví dụ như giải pháp rất đơn giản nhưng mạnh mẽ này: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input); 

  • Nếu bạn mong đợi bất cứ điều gì khác từ số nguyên hex nó. Nếu bạn hex nó, bạn sẽ hoàn toàn thoát khỏi tất cả các đầu vào. Trong C / C ++ có một hàm gọi là mysql_hex_string(), trong PHP bạn có thể sử dụng bin2hex().

    Đừng lo lắng về việc chuỗi thoát sẽ có kích thước gấp 2 lần chiều dài ban đầu bởi vì ngay cả khi bạn sử dụng mysql_real_escape_string, PHP phải phân bổ công suất tương tự ((2*input_length)+1), là như nhau.

  • Phương pháp hex này thường được sử dụng khi bạn chuyển dữ liệu nhị phân, nhưng tôi không thấy lý do tại sao không sử dụng nó trên tất cả dữ liệu để ngăn chặn các cuộc tấn công SQL injection. Lưu ý rằng bạn phải thêm dữ liệu trước 0x hoặc sử dụng hàm MySQL UNHEX thay thế.

Vì vậy, ví dụ, truy vấn:

SELECT password FROM users WHERE name = 'root'

Sẽ trở thành:

SELECT password FROM users WHERE name = 0x726f6f74

hoặc là

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex là lối thoát hoàn hảo. Không có cách nào để tiêm.

Sự khác biệt giữa hàm UNHEX và tiền tố 0x

Đã có một số cuộc thảo luận trong các ý kiến, vì vậy cuối cùng tôi muốn làm cho nó rõ ràng. Hai cách tiếp cận này rất giống nhau, nhưng chúng khác nhau một chút theo một số cách:

Tiền tố ** 0x ** chỉ có thể được sử dụng cho các cột dữ liệu như char, varchar, văn bản, khối, nhị phân, v.v ....
Ngoài ra, việc sử dụng nó là một chút phức tạp nếu bạn sắp chèn một chuỗi rỗng. Bạn sẽ phải thay thế hoàn toàn bằng ''hoặc bạn sẽ gặp lỗi.

UNHEX () hoạt động trên bất kì cột; bạn không phải lo lắng về chuỗi rỗng.


Phương pháp Hex thường được sử dụng như các cuộc tấn công

Lưu ý rằng phương thức hex này thường được sử dụng như là một tấn công SQL injection trong đó các số nguyên giống như các chuỗi và được thoát chỉ bằng mysql_real_escape_string. Sau đó, bạn có thể tránh việc sử dụng dấu ngoặc kép.

Ví dụ, nếu bạn chỉ làm một cái gì đó như thế này:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

một cuộc tấn công có thể tiêm bạn rất dễ dàng. Hãy xem xét mã được tiêm sau đây được trả về từ tập lệnh của bạn:

CHỌN ... WHERE id = -1 union tất cả chọn table_name từ information_schema.tables

và bây giờ chỉ cần trích xuất cấu trúc bảng:

SELECT ... WHERE id = -1 union tất cả chọn column_name từ information_schema.column nơi table_name = 0x61727469636c65

Và sau đó chỉ cần chọn bất kỳ dữ liệu nào muốn. Không phải là nó mát mẻ?

Nhưng nếu coder của một trang web có thể tiêm được, nó sẽ không thể tiêm vì truy vấn sẽ trông như sau: SELECT ... WHERE id = UNHEX('2d312075...3635')


501



@SumitGupta Yea, bạn đã làm. MySQL không liên kết với + nhưng vơi CONCAT. Và để thực hiện: Tôi không nghĩ rằng nó ảnh hưởng đến hiệu suất vì mysql đã phân tích dữ liệu và nó không quan trọng nếu nguồn gốc là chuỗi hoặc hex - Zaffy
@YourCommonSense Bạn gặp phải lỗi gì? Hãy cụ thể. - Zaffy
@YourCommonSense Bạn không hiểu khái niệm ... Nếu bạn muốn có chuỗi trong mysql bạn trích dẫn nó như thế này 'root' hoặc bạn có thể hex nó 0x726f6f74 NHƯNG nếu bạn muốn một số và gửi nó như là chuỗi bạn có thể sẽ viết '42' không CHAR (42) ... '42' trong hex sẽ được 0x3432không phải 0x42 - Zaffy
@YourCommonSense của tôi Tôi không có gì để nói ... chỉ cần lol ... nếu bạn vẫn muốn thử hex trên các lĩnh vực số, xem bình luận thứ hai. Tôi đặt cược với bạn rằng nó sẽ hoạt động. - Zaffy
@YourCommonSense bạn vẫn không hiểu? Bạn không thể sử dụng 0x và concat vì nếu chuỗi rỗng, bạn sẽ kết thúc bằng một lỗi. Nếu bạn muốn thay thế đơn giản cho truy vấn của bạn, hãy thử cái này SELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ') - Zaffy


QUAN TRỌNG

Cách tốt nhất để ngăn chặn SQL Injection là sử dụng Báo cáo chuẩn bị  thay vì thoát, như câu trả lời được chấp nhận chứng minh.

Có các thư viện như Aura.Sql và EasyDB cho phép các nhà phát triển sử dụng các câu lệnh chuẩn bị dễ dàng hơn. Để tìm hiểu thêm về lý do tại sao các báo cáo được chuẩn bị tốt hơn tại dừng SQL injection, tham khảo điều này mysql_real_escape_string() bỏ qua và gần đây đã sửa lỗ hổng Unicode SQL Injection trong WordPress.

Phòng chống tiêm - mysql_real_escape_string ()

PHP có một chức năng đặc biệt được thực hiện để ngăn chặn các cuộc tấn công. Tất cả những gì bạn cần làm là sử dụng hàm của một hàm, mysql_real_escape_string.

mysql_real_escape_string lấy một chuỗi sẽ được sử dụng trong một truy vấn MySQL và trả về cùng một chuỗi với tất cả các lần thử SQL injection đã thoát một cách an toàn. Về cơ bản, nó sẽ thay thế những dấu ngoặc kép phiền hà (') một người dùng có thể nhập với một thay thế MySQL an toàn, một báo giá thoát'.

CHÚ THÍCH: bạn phải được kết nối với cơ sở dữ liệu để sử dụng chức năng này!

// Kết nối với MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

Bạn có thể tìm thêm chi tiết trong MySQL - Ngăn chặn SQL Injection.


455



Đây là cách tốt nhất bạn có thể làm với phần mở rộng mysql cũ. Đối với mã mới, bạn nên chuyển sang mysqli hoặc PDO. - Álvaro González
Tôi không đồng ý với điều này 'một chức năng đặc biệt được thực hiện để ngăn chặn các cuộc tấn công này'. tôi nghĩ vậy mysql_real_escape_string mục đích là cho phép xây dựng truy vấn SQL đúng cho mọi chuỗi dữ liệu đầu vào. Phòng ngừa sql-injection là tác dụng phụ của chức năng này. - sectus
bạn không sử dụng các hàm để viết các chuỗi dữ liệu đầu vào chính xác. Bạn chỉ cần viết chính xác những người không cần phải thoát hoặc đã được trốn thoát. mysql_real_escape_string () có thể đã được thiết kế với mục đích bạn đề cập đến trong tâm trí, nhưng giá trị duy nhất của nó là ngăn chặn tiêm. - Nazca
CẢNH BÁO!  mysql_real_escape_string()  không thể sai lầm. - eggyal
mysql_real_escape_string hiện không được chấp nhận, do đó, nó không còn là lựa chọn khả thi nữa. Nó sẽ bị loại bỏ trong tương lai từ PHP. Tốt nhất là chuyển sang những gì mà người dùng PHP hoặc MySQL khuyên dùng. - jww


Cảnh báo an ninh: Câu trả lời này không phù hợp với thực tiễn bảo mật tốt nhất. Thoát là không đủ để ngăn chặn việc tiêm SQL, sử dụng báo cáo chuẩn bị thay thế. Sử dụng chiến lược được nêu dưới đây có nguy cơ của riêng bạn. (Cũng thế, mysql_real_escape_string() đã bị xóa trong PHP 7.)

Bạn có thể làm một cái gì đó cơ bản như thế này:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Điều này sẽ không giải quyết được mọi vấn đề, nhưng đó là một bước đệm rất tốt. Tôi đã bỏ qua các mục rõ ràng như kiểm tra sự tồn tại của biến, định dạng (số, chữ cái, v.v.).


413



Tôi đã thử ví dụ của bạn và nó làm việc tốt cho tôi. Bạn có thể rõ ràng "điều này sẽ không giải quyết mọi vấn đề" - Chinook
Nếu bạn không trích dẫn chuỗi, nó vẫn có thể tiêm được. Lấy $q = "SELECT col FROM tbl WHERE x = $safe_var"; ví dụ. Cài đặt $safe_var đến 1 UNION SELECT password FROM users hoạt động trong trường hợp này vì thiếu dấu ngoặc kép. Cũng có thể chèn các chuỗi vào truy vấn bằng CONCAT và CHR. - Polynomial
@ Polynomial Hoàn toàn đúng, nhưng tôi sẽ thấy điều này chỉ là sử dụng sai. Miễn là bạn sử dụng nó một cách chính xác, nó chắc chắn sẽ hoạt động. - glglgl
CẢNH BÁO!  mysql_real_escape_string()  không thể sai lầm. - eggyal
mysql_real_escape_string hiện không được chấp nhận, do đó, nó không còn là lựa chọn khả thi nữa. Nó sẽ bị loại bỏ trong tương lai từ PHP. Tốt nhất là chuyển sang những gì mà người dùng PHP hoặc MySQL khuyên dùng. - jww


Dù bạn sử dụng gì, hãy đảm bảo rằng bạn kiểm tra đầu vào của mình chưa bị xáo trộn bởi magic_quotes hoặc một số rác có ý nghĩa khác, và nếu cần thiết, hãy chạy nó qua stripslashes hoặc bất cứ điều gì để khử trùng nó.


347



Thật; chạy với magic_quotes bật chỉ khuyến khích thực hành kém. Tuy nhiên, đôi khi bạn không thể kiểm soát môi trường ở mức đó - hoặc bạn không có quyền truy cập để quản lý máy chủ hoặc ứng dụng của bạn phải cùng tồn tại với các ứng dụng (rùng mình) phụ thuộc vào cấu hình đó. Vì những lý do này, tốt nhất là viết các ứng dụng di động - mặc dù rõ ràng nỗ lực bị lãng phí nếu bạn kiểm soát môi trường triển khai, ví dụ: bởi vì nó là một ứng dụng trong nhà, hoặc chỉ được sử dụng trong môi trường cụ thể của bạn. - Rob
Kể từ PHP 5.4, abomination được gọi là 'magic quotes' đã được giết chết. Và tốt riddance để xấu rác. - BryanH


Truy vấn tham số và xác thực đầu vào là cách để đi. Có rất nhiều kịch bản mà theo đó SQL injection có thể xảy ra, mặc dù mysql_real_escape_string() đã được dùng.

Những ví dụ này dễ bị tấn công SQL injection:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

hoặc là

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

Trong cả hai trường hợp, bạn không thể sử dụng ' để bảo vệ việc đóng gói.

Nguồn: SQL Injection bất ngờ (Khi thoát không đủ)


330



Bạn có thể ngăn chặn SQL injection nếu bạn áp dụng một kỹ thuật xác thực đầu vào trong đó đầu vào của người dùng được xác thực dựa trên một tập hợp các quy tắc được xác định cho chiều dài, kiểu và cú pháp và cũng chống lại các quy tắc nghiệp vụ. - Josip Ivic


Theo tôi, cách tốt nhất để ngăn chặn SQL injection trong ứng dụng PHP của bạn (hoặc bất kỳ ứng dụng web nào, cho vấn đề đó) là suy nghĩ về kiến ​​trúc của ứng dụng của bạn. Nếu cách duy nhất để bảo vệ chống lại SQL injection là hãy nhớ sử dụng một phương thức hay hàm đặc biệt để thực hiện điều đúng đắn mỗi khi bạn nói chuyện với cơ sở dữ liệu, bạn đang làm sai. Bằng cách đó, nó chỉ là một vấn đề thời gian cho đến khi bạn quên định dạng chính xác truy vấn của bạn tại một số điểm trong mã của bạn.

Thông qua mô hình MVC và một khung như CakePHP hoặc là CodeIgniter có lẽ là cách đúng đắn để đi: Các tác vụ phổ biến như tạo các truy vấn cơ sở dữ liệu an toàn đã được giải quyết và triển khai tập trung ở các khung như vậy. Chúng giúp bạn tổ chức ứng dụng web của bạn một cách hợp lý và làm cho bạn suy nghĩ thêm về việc tải và lưu các đối tượng hơn là xây dựng một cách an toàn các truy vấn SQL đơn lẻ.


281



Tôi nghĩ đoạn đầu tiên của bạn là quan trọng. Hiểu biết là chìa khóa. Ngoài ra, mọi người đều không làm việc cho một công ty. Đối với một dải người lớn, các khuôn khổ thực sự đi ngược lại ý tưởng sự hiểu biết. Nhận được thân mật với các nguyên tắc cơ bản có thể không được đánh giá cao trong khi làm việc dưới thời hạn, nhưng những người tự làm ra có thích thú với đôi tay của họ bị bẩn. Các nhà phát triển khung không được đặc quyền như vậy mà mọi người khác phải cúi đầu và cho rằng họ không bao giờ phạm sai lầm. Quyền quyết định vẫn còn quan trọng. Ai nói rằng khung của tôi sẽ không thay thế một số kế hoạch khác trong tương lai? - Anthony Rutledge
@AnthonyRutledge Bạn hoàn toàn chính xác. Điều rất quan trọng đối với hiểu không những gì đang xảy ra và tại sao. Tuy nhiên, cơ hội mà một khuôn khổ thực sự và cố gắng và tích cực sử dụng và phát triển đã chạy vào và giải quyết rất nhiều vấn đề và vá rất nhiều lỗ hổng bảo mật đã được khá cao. Bạn nên xem mã nguồn để có được cảm nhận về chất lượng mã. Nếu đó là một mớ hỗn độn chưa được kiểm tra thì có lẽ nó không an toàn. - Johannes Fahrenkrug
Đây. Đây. Điểm tốt. Tuy nhiên, bạn có đồng ý rằng nhiều người có thể nghiên cứu và học cách áp dụng một hệ thống MVC, nhưng không phải ai cũng có thể tái tạo nó bằng tay (bộ điều khiển và máy chủ). Người ta có thể đi quá xa với điểm này. Tôi có cần phải hiểu lò vi sóng của mình trước khi tôi làm nóng bánh quy bơ đậu phộng của tôi, người bạn gái của tôi đã làm tôi? ;-) - Anthony Rutledge
@AnthonyRutledge Tôi đồng ý! Tôi nghĩ rằng trường hợp sử dụng cũng tạo nên sự khác biệt: Tôi có đang xây dựng một thư viện ảnh cho trang chủ cá nhân của mình hay tôi đang xây dựng một ứng dụng web ngân hàng trực tuyến không? Trong trường hợp thứ hai, điều rất quan trọng là phải hiểu các chi tiết về bảo mật và cách mà một khung công tác mà tôi đang sử dụng đang giải quyết chúng. - Johannes Fahrenkrug
Ah, ngoại lệ an ninh để tự mình làm điều đó. Xem nào, tôi có xu hướng sẵn sàng mạo hiểm tất cả và đi cho phá vỡ. :-) Đùa. Với đủ thời gian, mọi người có thể học cách tạo ra một ứng dụng an toàn khá darn. Quá nhiều người đang vội vàng. Họ giơ tay lên và giả định rằng các khung công tác an toàn hơn. Xét cho cùng, họ không có đủ thời gian để kiểm tra và tìm ra mọi thứ. Hơn nữa, an ninh là một lĩnh vực đòi hỏi phải nghiên cứu chuyên dụng. Nó không phải là một cái gì đó chỉ lập trình biết sâu sắc bởi đức hạnh của sự hiểu biết các thuật toán và các mẫu thiết kế. - Anthony Rutledge