Làm cách nào tôi có thể ngăn SQL tiêm trong PHP?


2773

Nếu đầu vào của người dùng được chèn mà không sửa đổi thành truy vấn SQL, thì ứng dụng sẽ trở nên dễ bị tấn công SQL , 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;--')

Có thể làm gì để ngăn chặn điều này xảy ra?

Câu trả lời:


8960

Sử dụng các câu lệnh được chuẩn bị và các truy vấn tham số. Đây là các 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.

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

  1. Sử dụng PDO (cho mọi trình điều khiển cơ sở dữ liệu được hỗ trợ):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute([ 'name' => $name ]);
    
    foreach ($stmt as $row) {
        // Do something with $row
    }
  2. Sử dụng MySQLi (cho 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ông phải là MySQL, có một 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()pg_execute()đối với PostgreQuery). PDO là tùy chọn phổ quát.


Cài đặt chính xác kết nối

Lưu ý rằng khi sử dụng PDOđể truy cập cơ sở dữ liệu MySQL, các câu lệnh chuẩn bị thực 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 việc 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', 'password');

$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 nên thêm nó . Bằng cách này, kịch bản sẽ không dừng lại Fatal Errorkhi có sự cố. Và nó cung cấp cho nhà phát triển cơ hội cho catchbất kỳ lỗi nào là thrown là PDOExceptions.

Tuy nhiên, điều bắt buộcsetAttribute()dòng đầu tiên , cho biết PDO vô hiệu hóa các câu lệnh được chuẩn bị mô phỏng và sử dụng các câu lệnh được chuẩn bị thực sự . Đ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ởi PHP trước khi gửi nó đến máy chủ MySQL (khiến 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 các charsettùy chọn của hàm tạo, nhưng điều quan trọng cần lưu ý là các phiên bản PHP cũ hơn (trước 5.3.6) đã âm thầm bỏ qua tham số bộ ký tự trong DSN.


Giải trình

Câu lệnh SQL bạn chuyển đến prepaređược phân tích cú pháp và biên dịch bởi máy chủ cơ sở dữ liệu. Bằng cách chỉ định các tham số (có thể là một ?hoặc một tham số có tên như :nametrong ví dụ trên), bạn báo cho công cụ cơ sở dữ liệu nơi bạn muốn lọc. Sau đó, khi bạn gọi execute, câu lệnh được chuẩn bị được kết hợp với các giá trị tham số 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 tiêm hoạt động bằng cách lừa tập lệnh bao gồm các chuỗi độc hại khi nó tạo SQL để gửi đến cơ sở dữ liệu. Vì vậy, bằng cách gửi SQL thực tế riêng biệt với các tham số, bạn hạn chế rủi ro kết thúc với thứ 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ũng có thể kết thúc dưới dạng số, tất nhiên). Trong ví dụ trên, nếu $namebiến chứa 'Sarah'; DELETE FROM employeeskết quả sẽ chỉ đơ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 bảng trống .

Một lợi ích khác của việc sử dụng các câu lệnh được chuẩn bị là nếu bạn thực hiện 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, giúp bạn tăng tốc.

Ồ, và vì bạn đã hỏi về cách thực hiện để chèn, đây là một ví dụ (sử dụng PDO):

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

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

Báo cáo 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, nhưng cấu trúc của chính truy vấn động không thể được tham số hóa và các tính năng truy vấn nhất định không thể được tham số hóa.

Đối với các kịch bản 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 giới hạn các giá trị có thể.

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

86
Chỉ cần thêm vào vì tôi không thấy nó ở bất cứ nơi nào khác ở đây, một dòng phòng thủ khác là tường lửa ứng dụng web (WAF) có thể có các quy tắc được đặt để tìm kiếm các cuộc tấn công tiêm sql:
jkerak

37
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 ngoài; bị bỏ qua. Ngay cả khi điều này đã bị phản đối, vẫn 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/feft.mysql-query.php
Randall Valenciano

12
Đây là một thói quen xấu nhưng là một giải pháp cho vấn đề hậu kỳ: Không chỉ đối với việc tiêm SQL mà còn đối với bất kỳ loại tiêm nào (ví dụ: có một lỗ tiêm 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 bị ảnh hưở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 supperglobal được xác định trước của bạn như $ _POST với các giá trị thoát tại bootstrap. Bằng PDO, vẫn có thể thoát (cũng cho các khung ngày hôm nay): chất nền ($ pdo-> quote ($ str, \ PDO :: PARAM_STR), 1, -1)
Alix

15
Câu trả lời này thiếu phần giải thích về câu lệnh được chuẩn bị là gì - một điều - đó là một cú đánh hiệu suất nếu bạn sử dụng nhiều câu lệnh được chuẩn bị trong yêu cầu của bạn và đôi khi nó chiếm tới 10 lần nhấn hiệu suất. Trường hợp tốt hơn sẽ là sử dụng PDO với ràng buộc tham số, nhưng chuẩn bị câu lệnh tắt.
donis

6
Sử dụng PDO sẽ 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 :: esc_opes
Kassem Itani

1652

Cảnh báo không dùng nữa: Mã mẫu của câu trả lời này (như mã mẫu của câu hỏi) sử dụng MySQLphần mở rộng của PHP , không được dùng trong PHP 5.5.0 và bị xóa hoàn toàn trong PHP 7.0.0.

Cảnh báo bảo mật : 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 SQL tiêm , thay vào đó hãy sử dụng các câu lệnh được chuẩn bị . Sử dụng chiến lược được nêu dưới đây có nguy cơ của riêng bạn. (Ngoài ra, mysql_real_escape_string()đã bị xóa trong PHP 7.)

Nếu bạn đang sử dụng một phiên bản PHP gần đây, mysql_real_escape_stringtùy chọn được nêu dưới đây sẽ không còn khả dụng (mặc dù mysqli::escape_stringlà một phiên bản hiện đại). Ngày nay, mysql_real_escape_stringtùy chọn này 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 các ký tự đặc biệt trong bạn unsafe_variablehoặc sử dụng truy vấn được tham số hóa. Cả hai sẽ bảo vệ bạn khỏi SQL tiêm. Truy vấn được tham số hóa được coi là cách thực hành tốt hơn nhưng sẽ yêu cầu thay đổi sang 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 ra trước 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_stringchức năng.

Để sử dụng truy vấn được tham số hóa, bạn cần sử dụng MySQLi thay vì các chức năng của MySQL . Để 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();
?>

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

Ngoài ra, như những người khác đã đề xuất, bạn có thể thấy hữu ích / dễ dàng hơn để đẩy mạnh 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 về 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 phương pháp 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 có ích và việc thoát yêu cầu không được đề cập mysql_real_escape_string. Trong trường hợp này, bạn nên chuyển đầu vào của người dùng qua danh sách trắng để đảm bảo chỉ các giá trị 'an toàn' mới được 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à thực hiện mysql_real_escape_stringphương pháp tiếp cận, bạn sẽ gặp phải vấn đề được mô tả bởi Polynomial trong các bình luận bên dưới. Trường hợp này phức tạp hơn vì các 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 các chữ số.
  • Có nhiều trường hợp khác tôi không biết. Bạn có thể thấy đâ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.

1
sử dụng mysql_real_escape_stringlà đủ hoặc tôi phải sử dụng tham số quá?
peiman F.

5
@peimanF. giữ một thực hành tốt về việc sử dụng các truy vấn tham số, ngay cả trên một dự án địa phương. Với các truy vấn tham số, bạn được đảm bảo rằng sẽ không có SQL tiêm. 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ụ như tiêm XSS, chẳng hạn như đặt mã HTML vào văn bản) htmlentitieschẳng hạn
Goufalite

2
@peimanF. Thực hành tốt để truy vấn tham số và giá trị ràng buộc, nhưng chuỗi thoát thực sự tốt cho đến bây giờ
Richard

Tôi hiểu sự bao gồm của tính mysql_real_escape_string()đầy đủ, nhưng không phải là người thích liệt kê cách tiếp cận dễ bị lỗi nhất trước tiên. Người đọc có thể nhanh chóng lấy ví dụ đầu tiên. Điều tốt là nó không được chấp nhận ngay bây giờ :)
Steen Schütt

1
@ SteenSchütt - Tất cả các mysql_*chức năng đều không được chấp nhận. Chúng đã được thay thế bởi các mysqli_* chức năng tương tự , chẳng hạn như mysqli_real_escape_string.
Rick James

1073

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 phần truy vấn khác nhau mà chúng ta có thể thêm vào SQL một cách linh hoạt: -

  • một chuỗi
  • một số
  • một định danh
  • một từ khóa cú pháp

Và báo cáo chuẩn bị chỉ bao gồm hai trong số họ.

Nhưng đôi khi chúng ta phải làm cho truy vấn của mình trở nên năng động hơn, thêm các toán tử hoặc mã định danh. Vì vậy, chúng ta 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 tham 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ụ: để thực hiện đặt hàng động:

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

Để giảm bớt quá trình tôi đã viết một hàm trợ giúp danh sách trắng thực hiện tất cả công việc trong một dòng:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe

Có một cách khác để bảo mật định danh - thoát nhưng tôi thích bám vào danh sách trắng như một cách tiếp cận rõ ràng và mạnh mẽ hơn. Tuy nhiên, miễn là bạn có một định danh được trích dẫn, bạn có thể thoát khỏi ký tự trích dẫn để làm cho nó an toàn. Ví dụ, theo mặc định cho mysql bạn phải nhân đôi ký tự trích dẫn để thoát khỏi nó . Đối với các quy tắc thoát DBMS khác sẽ khác.

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, DESCvà 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.

Vì vậy, một khuyến nghị chung có thể được đặt ra là

  • Bất kỳ biến nào đại diện cho một dữ liệu SQL bằng chữ, (hoặc, để đặt nó đơn giản - một chuỗi SQL hoặc một số) phải được thêm vào thông qua một câu lệnh được chuẩn bị. Không có ngoại lệ.
  • Bất kỳ phần truy vấn nào khác, chẳng hạn như từ khóa SQL, bảng hoặc tên trường hoặc toán tử - phải được lọc qua danh sách trắng.

Cập nhật

Mặc dù có một thỏa thuận chung về các thực tiễn tốt nhất liên quan đến bảo vệ tiêm SQL, nhưng vẫn còn nhiều thực tiễn xấu. Và một số trong số họ đã ăn sâu vào tâm trí của người dùng PHP. Chẳng hạn, trên chính trang này có (mặc dù vô hình đối với hầu hết khách truy cập), hơn 80 câu trả lời đã bị xóa - tất cả đều bị cộng đồng xóa do chất lượng kém hoặc thúc đẩy 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 không được xóa, nhưng khá thịnh vượng.

Ví dụ: có (1) vẫn có (2) (3) nhiều (4) câu trả lời (5) , bao gồm câu trả lời được đánh giá cao thứ hai cho thấy 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 chỉ đề xuất một phương pháp định dạng chuỗi khác và thậm chí tự hào nó là thuốc chữa bách bệnh cuối cù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ó vẫn giữ được tất cả các nhược điểm của nó: nó chỉ áp dụng cho các chuỗi và, giống như bất kỳ định dạng thủ công nào khác, về cơ bản, đó là tùy chọn, biện pháp 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 là do một sự mê tín rất cũ, được hỗ trợ bởi các cơ quan như OWASP hoặc hướng dẫn sử dụng PHP , trong đó 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ì hướng dẫn sử dụng PHP đã nói từ lâu, *_escape_stringkhô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 ngoài chuỗi, thoát thủ công là sai, vì nó là thủ công ngược lại với tự động.

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

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


848

Tôi khuyên bạn nên sử dụng PDO (Đố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 hơn mysql_, mysqli_pgsql_chức năng, bạn thực hiện ứng dụng của bạn một chút trừu tượng nhiều hơn từ các cơ sở dữ liệu, trong sự xuất hiện hiếm hoi mà bạn phải cung cấp cơ sở dữ liệu chuyển đổi.


619

Sử dụng PDOvà chuẩn bị các truy vấn.

( $connlà một PDOđối tượng)

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

549

Như bạn có thể thấy, mọi người đề nghị bạn sử dụng các tuyên bố đã chuẩn bị nhiều nhất. Điều đó 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 quy 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ó theo cách rất tinh vi - cách mà 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 kết hợp với các tuyên bố chuẩn bị mô phỏng. Tôi sử dụng nó để ngăn chặn tất cả các loại tấn công SQL 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 chắc chắn rằng nó thực sự là số nguyên. Trong một ngôn ngữ kiểu biến như PHP, điều này rất quan trọng. Bạn có thể sử dụng ví dụ 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 được gọi 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 đôi chiều dài ban đầu của nó bởi vì ngay cả khi bạn sử dụng mysql_real_escape_string, PHP vẫn phải phân bổ cùng dung lượng ((2*input_length)+1), giống nhau.

  • Phương thức hex này thường được sử dụng khi bạn chuyển dữ liệu nhị phân, nhưng tôi thấy không có 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 tiêm nhiễm SQL. Lưu ý rằng bạn phải thêm trước dữ liệu bằng 0xhoặc sử dụng chức năng MySQL UNHEXthay 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 hơi khác nhau theo một số cách:

Các ** 0x ** tiền tố 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, vv .
Ngoài ra, việc sử dụng nó hơi 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 nào ; bạn không phải lo lắng về chuỗi trống.


Các phương thức hex thường được sử dụng làm 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ư một cuộc tấn công tiêm nhiễm SQL trong đó các số nguyên giống như các chuỗi và thoát chỉ với 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 cho 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 đều chọn tên_bảng từ information_schema.tables

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

CHỌN ... WHERE id = -1 union đều chọn cột_name từ information_schema.column trong đó tên_bảng = 0x61727469636c65

Và sau đó chỉ cần chọn bất cứ dữ liệu nào người ta muốn. Nó không tuyệt sao?

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


6
@SumitGupta Yea, bạn đã làm. MySQL không nối với +nhưng với CONCAT. Và đối với hiệu suất: Tôi không nghĩ nó ảnh hưởng đến hiệu suất vì mysql phải phân tích dữ liệu và không thành vấn đề nếu nguồn gốc là chuỗi hoặc hex
Zaffy

11
@YourCommonSense Bạn không hiểu khái niệm này ... 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ó 0x726f6f74NHƯNG nếu bạn muốn một số và gửi dưới dạng chuỗi, bạn có thể sẽ viết '42' chứ không phải CHAR (42 ) ... '42' trong hex sẽ 0x3432không0x42
Zaffy

7
@YourCommonSense Tôi không có gì để nói ... chỉ là lol ... nếu bạn vẫn muốn thử hex trên các trường số, xem bình luận thứ hai. Tôi cá với bạn rằng nó sẽ hoạt động.
Zaffy

2
Câu trả lời nêu rõ rằng nó sẽ không hoạt động theo cách đó với các giá trị nguyên, lý do là bin2hex chuyển đổi giá trị được chuyển thành một chuỗi (và do đó bin2hex (0) là 0x30 chứ không phải 0x03) - đó có lẽ là phần khiến bạn bối rối . Nếu bạn làm theo điều đó, nó hoạt động hoàn hảo (ít nhất là trên trang web của tôi, được thử nghiệm với 4 phiên bản mysql khác nhau trên các máy debian, 5.1.x đến 5.6.x). Xét cho cùng, thập lục phân chỉ là cách biểu diễn, không phải giá trị;)
griffin

5
@YourCommonSense bạn vẫn chưa hiểu? Bạn không thể sử dụng 0x và concat vì nếu chuỗi trố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 mình, hãy thử truy vấn nàySELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ')
Zaffy

499

Cảnh báo không dùng nữa: Mã mẫu của câu trả lời này (như mã mẫu của câu hỏi) sử dụng MySQLphần mở rộng của PHP , không được dùng trong PHP 5.5.0 và bị xóa hoàn toàn trong PHP 7.0.0.

Cảnh báo bảo mật : 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 SQL tiêm , thay vào đó hãy sử dụng các câu lệnh được chuẩn bị . Sử dụng chiến lược được nêu dưới đây có nguy cơ của riêng bạn. (Ngoài ra, mysql_real_escape_string()đã bị xóa trong PHP 7.)

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.SqlEasyDB cho phép các nhà phát triển sử dụng các câu lệnh được chuẩn bị dễ dàng hơn. Để tìm hiểu thêm về lý do tại sao các câu lệnh được chuẩn bị tốt hơn trong việc dừng SQL tiêm , hãy tham khảo cách bỏ qua nàymysql_real_escape_string()các lỗ hổng Unicode SQL Injection được sửa gần đây trong WordPress .

Ngăn ngừa tiêm - mysql_real_escape_opes ()

PHP có một chức năng được chế tạo đặc biệt để ngăn chặn các cuộc tấn công này. Tất cả bạn cần làm là sử dụng một hàm đầy đủ , mysql_real_escape_string.

mysql_real_escape_stringlấy một chuỗi sẽ được sử dụng trong truy vấn MySQL và trả về cùng một chuỗi với tất cả các lần thử SQL được thoát an toàn. Về cơ bản, nó sẽ thay thế những trích dẫn rắc rối đó (') mà người dùng có thể nhập bằng một thay thế an toàn cho MySQL, một trích dẫn đã thoát \'.

LƯU Ý: bạn phải 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 - SQL Tiêm phòng .


30
Đây là cách tốt nhất bạn có thể làm với phần mở rộng mysql kế thừa. Đối với mã mới, bạn nên chuyển sang mysqli hoặc PDO.
Álvaro González

7
Tôi không đồng ý với điều này 'một chức năng được chế tạo đặc biệt để ngăn chặn các cuộc tấn công này'. Tôi nghĩ rằng mysql_real_escape_stringmục đích đó là cho phép xây dựng truy vấn SQL chính xác cho mỗi chuỗi dữ liệu đầu vào. Ngăn ngừa tiêm sql là tác dụng phụ của chức năng này.
phái

4
bạn không sử dụng các hàm để ghi chuỗi dữ liệu đầu vào chính xác. Bạn chỉ cần viết đúng những cái không cần thoát hoặc đã thoát. mysql_real_escape_opes () có thể đã được thiết kế với mục đích bạn đề cập trong đầu, nhưng giá trị duy nhất của nó là ngăn chặn tiêm.
Nazca

17
CẢNH BÁO! mysql_real_escape_string() không phải là không thể sai lầm .
eggyal

9
mysql_real_escape_stringbây giờ không được dùng nữa, vì vậy nó không còn là một lựa chọn khả thi. Nó sẽ bị xóa trong tương lai khỏi PHP. Tốt nhất là chuyển sang những gì mà mọi người PHP hoặc MySQL khuyên dùng.
jww

462

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

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Điều này sẽ không giải quyết mọi vấn đề, nhưng nó 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, định dạng của biến (số, chữ cái, v.v.).


28
Tôi đã thử ví dụ của bạn và nó hoạt động tốt với tôi. Bạn có thể làm rõ "điều này sẽ không giải quyết mọi vấn đề"
Chinook

14
Nếu bạn không trích dẫn chuỗi, nó vẫn có thể được tiêm. Lấy $q = "SELECT col FROM tbl WHERE x = $safe_var";ví dụ. Thiết lập $safe_varđể 1 UNION SELECT password FROM usershoạt động trong trường hợp này vì thiếu dấu ngoặc kép. Cũng có thể đưa chuỗi vào truy vấn bằng cách sử dụng CONCATCHR.
Đa thức

4
@Polynomial Hoàn toàn đúng, nhưng tôi chỉ xem đây là cách sử dụng sai. Miễn là bạn sử dụng đúng cách, nó chắc chắn sẽ hoạt động.
glglgl

22
CẢNH BÁO! mysql_real_escape_string() không phải là không thể sai lầm .
eggyal

8
mysql_real_escape_stringbây giờ không được dùng nữa, vì vậy nó không còn là một lựa chọn khả thi. Nó sẽ bị xóa trong tương lai khỏi PHP. Tốt nhất là chuyển sang những gì mà mọi người PHP hoặc MySQL khuyên dùng.
jww

380

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


11
Thật; chạy với Magic_quotes được bật chỉ khuyến khích thực hành kém. Tuy nhiên, đôi khi bạn không thể luôn 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, thật tốt khi viết các ứng dụng di động - mặc dù rõ ràng là nỗ lực bị lãng phí nếu bạn kiểm soát môi trường triển khai, ví dụ như vì đó là ứng dụng nội bộ hoặc chỉ được sử dụng trong môi trường cụ thể của bạn.
Cướp

24
Kể từ phiên bản PHP 5.4, tên gớm ghiếc được gọi là 'trích dẫn ma thuật' đã bị giết chết . Và câu đố tốt để rác xấu.
BryanH

363

Cảnh báo không dùng nữa: Mã mẫu của câu trả lời này (như mã mẫu của câu hỏi) sử dụng MySQLphần mở rộng của PHP , không được dùng trong PHP 5.5.0 và bị xóa hoàn toàn trong PHP 7.0.0.

Cảnh báo bảo mật : 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 SQL tiêm , thay vào đó hãy sử dụng các câu lệnh được chuẩn bị . Sử dụng chiến lược được nêu dưới đây có nguy cơ của riêng bạn. (Ngoài ra, mysql_real_escape_string()đã bị xóa trong PHP 7.)

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

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

$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 SQL bất ngờ (Khi thoát không đủ)


2
Bạn có thể ngăn SQL tiêm nếu bạn áp dụng 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 theo một bộ quy tắc được xác định về độ dài, loại và cú pháp và cũng chống lại quy tắc kinh doanh.
Josip Ivic

312

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

Việc chấp nhận mẫu MVC và một khung như CakePHP hoặc CodeIgniter có lẽ là cách đúng đắn: 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à thực hiện tập trung trong các khung như vậy. Chúng giúp bạn sắp xếp ứng dụng web của bạn một cách hợp lý và khiến bạn suy nghĩ nhiều hơn về việc tải và lưu các đối tượng hơn là về việc xây dựng các truy vấn SQL đơn lẻ một cách an toàn.


5
Tôi nghĩ rằng đoạn đầu tiên của bạn là quan trọng. Hiểu là chìa khóa. Ngoài ra, mọi người không làm việc cho một công ty. Đối với một lượng lớn người, các khung thực sự đi ngược lại ý tưởng về sự hiểu biết . Trở nên thân thiết với các nguyên tắc cơ bản có thể không được coi trọng khi làm việc theo thời hạn, nhưng những người tự làm ngoài kia thích được làm bẩn tay. Các nhà phát triển khung không được đặc quyền đến mức 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. Sức mạnh để đưa ra quyết định vẫn còn quan trọng. Ai sẽ nói rằng khuôn khổ của tôi sẽ không thay thế một số chương trình 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 để hiểu những gì đang xảy ra và tại sao. Tuy nhiên, cơ hội mà một khung công tác thực sự và đã được thử và sử dụng tích cực đã gặp phải và giải quyết rất nhiều vấn đề và vá rất nhiều lỗ hổng bảo mật đã là khá cao. Đó là một ý tưởng tốt để xem nguồn để 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.
Julian Fahrenkrug

3
Đâ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ể sao chép 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 bơ mà bạn gái của tôi đã làm cho tôi không? ;-)
Anthony Rutledge

3
@AnthonyRutledge Tôi đồng ý! Tôi nghĩ trường hợp sử dụng cũng tạo ra sự khác biệt: Tôi đang xây dựng một bộ sưu tập ả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? Trong trường hợp sau, đ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 những vấn đề đó.
Julian Fahrenkrug

3
Ah, ngoại lệ bảo mật để tự làm điều đó. Hãy xem, tôi có xu hướng sẵn sàng mạo hiểm tất cả và 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 khá an toàn. Quá nhiều người đang vội vàng. Họ giơ tay lên và cho rằng các khung an toàn hơn . Rốt cuộc, họ không có đủ thời gian để kiểm tra và tìm ra mọi thứ. Hơn nữa, bảo mật 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ỉ các lập trình viên biết sâu sắc nhờ hiểu biết các thuật toán và các mẫu thiết kế.
Anthony Rutledge

298

Tôi ủng hộ các thủ tục được lưu trữ ( MySQL đã có hỗ trợ thủ tục lưu trữ từ 5.0 ) từ quan điểm bảo mật - ưu điểm là -

  1. Hầu hết các cơ sở dữ liệu (bao gồm cả MySQL ) cho phép người dùng bị hạn chế quyền truy cập để thực hiện các thủ tục được lưu trữ. Kiểm soát truy cập bảo mật chi tiết là hữu ích để ngăn chặn các cuộc tấn công đặc quyền leo thang. Điều này ngăn các ứng dụng bị xâm nhập có thể chạy SQL trực tiếp với cơ sở dữ liệu.
  2. Họ trừu tượng truy vấn SQL thô từ ứng dụng để có ít thông tin hơn về cấu trúc cơ sở dữ liệu có sẵn cho ứng dụng. Điều này khiến mọi người khó hiểu hơn về cấu trúc cơ sở dữ liệu và thiết kế các cuộc tấn công phù hợp.
  3. Họ chỉ chấp nhận các tham số, vì vậy lợi thế của các truy vấn tham số là có. Tất nhiên - IMO bạn vẫn cần vệ sinh đầu vào của mình - đặc biệt nếu bạn đang sử dụng SQL động bên trong thủ tục được lưu trữ.

Những nhược điểm là -

  1. Chúng (thủ tục lưu trữ) rất khó để duy trì và có xu hướng nhân lên rất nhanh. Điều này làm cho việc quản lý chúng là một vấn đề.
  2. Chúng không phù hợp lắm cho các truy vấn động - nếu chúng được xây dựng để chấp nhận mã động làm tham số thì rất nhiều lợi thế bị phủ nhận.

297

Có nhiều cách để ngăn chặn việc tiêm SQL và các vụ hack SQL khác. Bạn có thể dễ dàng tìm thấy nó trên Internet (Google Tìm kiếm). Tất nhiên PDO là một trong những giải pháp tốt. Nhưng tôi muốn đề nghị bạn một số phòng ngừa liên kết tốt từ SQL tiêm.

SQL tiêm là gì và cách phòng ngừa

Hướng dẫn sử dụng PHP để tiêm SQL

Microsoft giải thích về việc tiêm và ngăn chặn SQL trong PHP

Và một số khác như Ngăn chặn SQL tiêm với MySQL và PHP .

Bây giờ, tại sao bạn cần ngăn truy vấn của bạn khỏi SQL tiêm?

Tôi muốn cho bạn biết: Tại sao chúng tôi cố gắng ngăn SQL tiêm bằng một ví dụ ngắn dưới đây:

Truy vấn để xác thực đăng nhập khớp:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

Bây giờ, nếu ai đó (một hacker) đặt

$_POST['email']= admin@emali.com' OR '1=1

và mật khẩu bất cứ điều gì ....

Truy vấn sẽ được phân tích cú pháp vào hệ thống tối đa:

$query="select * from users where email='admin@emali.com' OR '1=1';

Phần khác sẽ bị loại bỏ. Vậy, chuyện gì sẽ xảy ra? Người dùng không được ủy quyền (tin tặc) sẽ có thể đăng nhập với tư cách quản trị viên mà không cần mật khẩu của anh ấy / cô ấy. Bây giờ, anh ấy / cô ấy có thể làm bất cứ điều gì mà quản trị viên / người email có thể làm. Hãy xem, sẽ rất nguy hiểm nếu SQL không được ngăn chặn.


267

Tôi nghĩ rằng nếu ai đó muốn sử dụng PHP và MySQL hoặc một số máy chủ dataBase khác:

  1. Hãy suy nghĩ về việc học PDO (Đối tượng dữ liệu PHP) - đây là lớp truy cập cơ sở dữ liệu cung cấp một phương thức truy cập thống nhất vào nhiều cơ sở dữ liệu.
  2. Nghĩ về việc học MySQLi
  3. Sử dụng các hàm PHP gốc như: dải_tags , mysql_real_escape_opes hoặc nếu biến số, chỉ (int)$foo. Đọc thêm về loại biến trong PHP ở đây . Nếu bạn đang sử dụng các thư viện như PDO hoặc MySQLi, hãy luôn sử dụng PDO :: quote ()mysqli_real_escape_opes () .

Thư viện ví dụ:

---- PDO

----- Không có chỗ dành sẵn - chín muồi cho SQL tiêm! Thật tệ

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- giữ chỗ không tên

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- Giữ chỗ

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQL

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

PS :

PDO thắng trận chiến này một cách dễ dàng. Với sự hỗ trợ cho mười hai trình điều khiển cơ sở dữ liệu khác nhau và các tham số được đặt tên, chúng ta có thể bỏ qua việc mất hiệu suất nhỏ và làm quen với API của nó. Từ quan điểm bảo mật, cả hai đều an toàn miễn là nhà phát triển sử dụng chúng theo cách họ được sử dụng

Nhưng trong khi cả PDO và MySQLi đều khá nhanh, MySQLi thực hiện nhanh hơn đáng kể về điểm chuẩn - ~ 2,5% cho các câu lệnh không được chuẩn bị và ~ 6,5% cho các câu lệnh được chuẩn bị.

Và vui lòng kiểm tra mọi truy vấn vào cơ sở dữ liệu của bạn - đó là cách tốt hơn để ngăn ngừa tiêm.


rằng mysqli không chính xác. Param đầu tiên thể hiện các loại dữ liệu.
mickmackusa

257

Nếu có thể, bỏ các loại tham số của bạn. Nhưng nó chỉ hoạt động trên các loại đơn giản như int, bool và float.

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

3
Đây là một trong số ít trường hợp tôi sẽ sử dụng "giá trị thoát" thay vì tuyên bố đã chuẩn bị. Và chuyển đổi loại số nguyên là cực kỳ hiệu quả.
Hold OfferHunger

233

Nếu bạn muốn tận dụng các công cụ bộ đệm, như Redis hoặc Memcached , có lẽ DALMP có thể là một lựa chọn. Nó sử dụng MySQLi thuần túy . Kiểm tra điều này: Lớp trừu tượng cơ sở dữ liệu DALMP cho MySQL bằng PHP.

Ngoài ra, bạn có thể 'chuẩn bị' các đối số của mình trước khi chuẩn bị truy vấn để có thể tạo các truy vấn động và cuối cùng có một truy vấn câu được chuẩn bị đầy đủ. Lớp trừu tượng cơ sở dữ liệu DALMP cho MySQL bằng PHP.


224

Đối với những người không chắc chắn về cách sử dụng PDO (đến từ các mysql_chức năng), tôi đã tạo một trình bao bọc PDO rất, rất đơn giản chỉ là một tệp. Nó tồn tại để cho thấy thật dễ dàng để làm tất cả những điều phổ biến mà các ứng dụng cần phải được thực hiện. Hoạt động với PostgreSQL, MySQL và SQLite.

Về cơ bản, hãy đọc nó trong khi bạn đọc hướng dẫn để xem cách đặt các hàm PDO để sử dụng trong cuộc sống thực để đơn giản hóa việc lưu trữ và truy xuất các giá trị theo định dạng bạn muốn.

Tôi muốn một cột duy nhất

$count = DB::column('SELECT COUNT(*) FROM `user`);

Tôi muốn một kết quả mảng (key => value) (nghĩa là để tạo một hộp chọn)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

Tôi muốn một kết quả hàng duy nhất

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

Tôi muốn một loạt các kết quả

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

222

Sử dụng chức năng PHP này, mysql_escape_string()bạn có thể có được một phòng ngừa tốt một cách nhanh chóng.

Ví dụ:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - Thoát khỏi một chuỗi để sử dụng trong mysql_query

Để phòng ngừa nhiều hơn, bạn có thể thêm vào cuối ...

wHERE 1=1   or  LIMIT 1

Cuối cùng bạn nhận được:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

220

Một vài hướng dẫn để thoát các ký tự đặc biệt trong các câu lệnh SQL.

Đừng sử dụng MySQL . Phần mở rộng này không được chấp nhận. Sử dụng MySQLi hoặc PDO thay thế.

MySQL

Để thoát thủ công các ký tự đặc biệt trong một chuỗi, bạn có thể sử dụng hàm mysqli_real_escape_opes . Hàm sẽ không hoạt động đúng trừ khi bộ ký tự chính xác được đặt với mysqli_set_charset .

Thí dụ:

$mysqli = new mysqli('host', 'user', 'password', 'database');
$mysqli->set_charset('charset');

$string = $mysqli->real_escape_string($string);
$mysqli->query("INSERT INTO table (column) VALUES ('$string')");

Để tự động thoát các giá trị với các câu lệnh đã chuẩn bị, hãy sử dụng mysqli_preparemysqli_stmt_bind_param trong đó các loại cho các biến liên kết tương ứng phải được cung cấp cho một chuyển đổi phù hợp:

Thí dụ:

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

$stmt->bind_param("is", $integer, $string);

$stmt->execute();

Bất kể bạn sử dụng báo cáo đã chuẩn bị hay mysqli_real_escape_string, bạn luôn phải biết loại dữ liệu đầu vào bạn đang làm việc.

Vì vậy, nếu bạn sử dụng một câu lệnh được chuẩn bị, bạn phải chỉ định các loại biến cho mysqli_stmt_bind_paramhàm.

Và việc sử dụng mysqli_real_escape_stringlà, như tên gọi, thoát khỏi các ký tự đặc biệt trong một chuỗi, vì vậy nó sẽ không làm cho số nguyên an toàn. Mục đích của chức năng này là để ngăn chặn việc phá vỡ các chuỗi trong các câu lệnh SQL và làm hỏng cơ sở dữ liệu mà nó có thể gây ra. mysqli_real_escape_stringlà một chức năng hữu ích khi được sử dụng đúng cách, đặc biệt là khi kết hợp với sprintf.

Thí dụ:

$string = "x' OR name LIKE '%John%";
$integer = '5 OR id != 0';

$query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5

$integer = '99999999999999999999';
$query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);

echo $query;
// SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647

3
Câu hỏi rất chung chung. Một số câu trả lời tuyệt vời ở trên, nhưng hầu hết đề nghị tuyên bố chuẩn bị. MySQLi async không hỗ trợ các câu lệnh được chuẩn bị, vì vậy sprintf trông giống như một lựa chọn tuyệt vời cho tình huống này.
Dustin Graham

183

Giải pháp thay thế đơn giản cho vấn đề này có thể được giải quyết bằng cách cấp quyền phù hợp trong chính cơ sở dữ liệu. Ví dụ: nếu bạn đang sử dụng cơ sở dữ liệu MySQL thì hãy nhập vào cơ sở dữ liệu thông qua thiết bị đầu cuối hoặc giao diện người dùng được cung cấp và chỉ cần làm theo lệnh này:

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

Điều này sẽ hạn chế người dùng chỉ bị giới hạn với chỉ truy vấn đã chỉ định. Xóa quyền xóa và do đó dữ liệu sẽ không bao giờ bị xóa khỏi truy vấn được bắn từ trang PHP. Điều thứ hai cần làm là tuôn ra các đặc quyền để MySQL làm mới các quyền và cập nhật.

FLUSH PRIVILEGES; 

thêm thông tin về tuôn ra .

Để xem các đặc quyền hiện tại cho người dùng kích hoạt truy vấn sau.

select * from mysql.user where User='username';

Tìm hiểu thêm về CẤP .


25
Câu trả lời này về cơ bảnsai , vì nó không giúp ngăn ngừa tiêm phòng mà chỉ cố gắng làm dịu hậu quả. Vô ích.
Ý thức chung của bạn

1
Đúng, nó không cung cấp giải pháp, nhưng là những gì bạn có thể làm trước khi ra tay để tránh mọi thứ.
Apurv Nerlekar

1
@Apurv Nếu mục tiêu của tôi là đọc thông tin cá nhân từ cơ sở dữ liệu của bạn, thì việc không có quyền XÓA có nghĩa là không có gì.
Alex Holsgrove

1
@AlexHolsgrove: Hãy dễ dàng, tôi chỉ đề xuất các thực hành tốt để làm dịu hậu quả.
Apurv Nerlekar

2
@Apurv Bạn không muốn "làm dịu hậu quả", bạn muốn làm mọi thứ có thể để bảo vệ chống lại nó. Mặc dù công bằng mà nói, việc thiết lập quyền truy cập người dùng chính xác là rất quan trọng, nhưng thực sự không phải là những gì OP yêu cầu.
Alex Holsgrove

177

Về nhiều câu trả lời hữu ích, tôi hy vọng sẽ thêm một số giá trị cho chủ đề này.

SQL tiêm là một cuộc tấn công có thể được thực hiện thông qua đầu vào của người dùng (đầu vào được điền bởi người dùng và sau đó được sử dụng bên trong các truy vấn). Các mẫu tiêm SQL là cú pháp truy vấn chính xác trong khi chúng ta có thể gọi nó là: truy vấn xấu vì lý do xấu và chúng tôi cho rằng có thể có một người xấu cố lấy thông tin bí mật (bỏ qua kiểm soát truy cập) ảnh hưởng đến ba nguyên tắc bảo mật (bảo mật , tính toàn vẹn, và tính sẵn sàng).

Bây giờ, quan điểm của chúng tôi là ngăn chặn các mối đe dọa bảo mật như các cuộc tấn công tiêm nhiễm SQL, câu hỏi đặt ra (làm thế nào để ngăn chặn một cuộc tấn công tiêm nhiễm SQL bằng PHP), thực tế hơn, lọc dữ liệu hoặc xóa dữ liệu đầu vào là trường hợp khi sử dụng dữ liệu đầu vào của người dùng bên trong truy vấn như vậy, sử dụng PHP hoặc bất kỳ ngôn ngữ lập trình nào khác không phải là trường hợp hoặc theo khuyến cáo của nhiều người hơn là sử dụng công nghệ hiện đại như tuyên bố đã chuẩn bị hoặc bất kỳ công cụ nào hiện đang hỗ trợ phòng ngừa tiêm SQL, xem xét các công cụ này không còn khả dụng nữa? Làm thế nào để bạn bảo mật ứng dụng của bạn?

Cách tiếp cận của tôi chống lại SQL tiêm là: xóa dữ liệu đầu vào của người dùng trước khi gửi nó đến cơ sở dữ liệu (trước khi sử dụng nó trong bất kỳ truy vấn nào).

Lọc dữ liệu cho (chuyển đổi dữ liệu không an toàn thành dữ liệu an toàn)

Hãy xem xét rằng PDOMySQLi không có sẵn. Làm thế nào bạn có thể bảo mật ứng dụng của bạn? Bạn có ép tôi sử dụng chúng không? Còn các ngôn ngữ khác ngoài PHP thì sao? Tôi thích cung cấp các ý tưởng chung vì nó có thể được sử dụng cho biên giới rộng hơn, không chỉ cho một ngôn ngữ cụ thể.

  1. Người dùng SQL (giới hạn đặc quyền người dùng): hầu hết các hoạt động SQL phổ biến là (CHỌN, CẬP NHẬT, CHERTN), tại sao, tại sao lại cung cấp đặc quyền CẬP NHẬT cho người dùng không yêu cầu? Ví dụ: các trang đăng nhập và tìm kiếm chỉ sử dụng CHỌN, vậy thì tại sao lại sử dụng người dùng DB trong các trang này với các đặc quyền cao?

Quy tắc: không tạo một người dùng cơ sở dữ liệu cho tất cả các đặc quyền. Đối với tất cả các hoạt động SQL, bạn có thể tạo lược đồ của mình như (deluser, selectuser, updateuser) dưới dạng tên người dùng để dễ sử dụng.

Xem nguyên tắc đặc quyền tối thiểu .

  1. Lọc dữ liệu: trước khi xây dựng bất kỳ đầu vào người dùng truy vấn nào, nó cần được xác nhận và lọc. Đối với lập trình viên, điều quan trọng là xác định một số thuộc tính cho từng biến đầu vào của người dùng: kiểu dữ liệu, mẫu dữ liệu và độ dài dữ liệu . Trường có số nằm giữa (x và y) phải được xác thực chính xác bằng quy tắc chính xác và đối với trường là chuỗi (văn bản): mẫu là trường hợp, ví dụ: tên người dùng chỉ chứa một số ký tự, hãy để nói [a-zA-Z0-9_-.]. Độ dài khác nhau giữa (x và n) trong đó x và n (số nguyên, x <= n). Quy tắc: tạo bộ lọc chính xác và quy tắc xác thực là những thực tiễn tốt nhất đối với tôi.

  2. Sử dụng các công cụ khác: Ở đây, tôi cũng sẽ đồng ý với bạn rằng một câu lệnh đã được chuẩn bị (truy vấn tham số) và các thủ tục được lưu trữ. Nhược điểm ở đây là những cách này đòi hỏi những kỹ năng tiên tiến không tồn tại đối với hầu hết người dùng. Ý tưởng cơ bản ở đây là phân biệt giữa truy vấn SQL và dữ liệu được sử dụng bên trong. Cả hai cách tiếp cận có thể được sử dụng ngay cả với dữ liệu không an toàn, vì dữ liệu đầu vào của người dùng ở đây không thêm bất cứ điều gì vào truy vấn ban đầu, chẳng hạn như (bất kỳ hoặc x = x).

Để biết thêm thông tin, xin vui lòng đọc tờ Cheat phòng chống tiêm chích OWASP SQL .

Bây giờ, nếu bạn là người dùng nâng cao, hãy bắt đầu sử dụng biện pháp bảo vệ này theo ý muốn, nhưng, đối với người mới bắt đầu, nếu họ không thể nhanh chóng thực hiện quy trình được lưu trữ và chuẩn bị câu lệnh, tốt hơn hết là lọc dữ liệu đầu vào càng nhiều càng tốt.

Cuối cùng, hãy xem xét rằng người dùng gửi văn bản này bên dưới thay vì nhập tên người dùng của anh ấy / cô ấy:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

Đầu vào này có thể được kiểm tra sớm mà không cần bất kỳ tuyên bố chuẩn bị và quy trình được lưu trữ nào, nhưng để đảm bảo an toàn, sử dụng chúng bắt đầu sau khi lọc và xác thực dữ liệu người dùng.

Điểm cuối cùng là phát hiện hành vi bất ngờ đòi hỏi nhiều nỗ lực và phức tạp hơn; nó không được khuyến khích cho các ứng dụng web thông thường.

Hành vi bất ngờ trong đầu vào của người dùng ở trên là CHỌN, ĐOÀN, NẾU, ĐĂNG KÝ, BẾN, SHA và root. Một khi những từ này được phát hiện, bạn có thể tránh đầu vào.

CẬP NHẬT 1:

Một người dùng nhận xét rằng bài đăng này là vô ích, OK! Đây là những gì OWASP.ORG cung cấp :

Bảo vệ chính:

Lựa chọn # 1: Sử dụng câu lệnh chuẩn bị (tham số truy vấn)
Lựa chọn # 2: Sử dụng Stored Procedure
Lựa chọn # 3: Thoát tất cả các tài khoản Cung cấp Input

phòng thủ bổ sung:

Cũng Thực thi: Least Privilege
Cũng Thực hiện: White List Input Validation

Như bạn có thể biết, yêu cầu một bài viết nên được hỗ trợ bởi một đối số hợp lệ, ít nhất là bởi một tham chiếu! Mặt khác, nó được coi là một cuộc tấn công và một yêu sách tồi!

Cập nhật 2:

Từ hướng dẫn PHP, PHP: Báo cáo đã chuẩn bị - Hướng dẫn :

Thoát và SQL tiêm

Các biến giới hạn sẽ được thoát tự động bởi máy chủ. Máy chủ chèn các giá trị đã thoát của chúng tại các vị trí thích hợp vào mẫu câu lệnh trước khi thực hiện. Một gợi ý phải được cung cấp cho máy chủ về loại biến bị ràng buộc, để tạo ra một chuyển đổi phù hợp. Xem hàm mysqli_stmt_bind_param () để biết thêm thông tin.

Việc tự động thoát các giá trị trong máy chủ đôi khi được coi là một tính năng bảo mật để ngăn chặn việc tiêm SQL. Mức độ bảo mật tương tự có thể đạt được với các câu lệnh không được chuẩn bị nếu các giá trị đầu vào được thoát chính xác.

Cập nhật 3:

Tôi đã tạo các trường hợp thử nghiệm để biết cách PDO và MySQLi gửi truy vấn đến máy chủ MySQL khi sử dụng câu lệnh đã chuẩn bị:

PDO:

$user = "''1''"; // Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

Nhật ký truy vấn:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

Nhật ký truy vấn:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

Rõ ràng là một tuyên bố đã chuẩn bị cũng đang thoát khỏi dữ liệu, không có gì khác.

Như đã đề cập trong tuyên bố trên,

Việc tự động thoát các giá trị trong máy chủ đôi khi được coi là một tính năng bảo mật để ngăn chặn việc tiêm SQL. Mức độ bảo mật tương tự có thể đạt được với các câu lệnh không được chuẩn bị, nếu các giá trị đầu vào được thoát chính xác

Do đó, điều này chứng tỏ rằng xác thực dữ liệu như intval()là một ý tưởng tốt cho các giá trị số nguyên trước khi gửi bất kỳ truy vấn nào. Ngoài ra, ngăn chặn dữ liệu người dùng độc hại trước khi gửi truy vấn là một cách tiếp cận chính xác và hợp lệ .

Vui lòng xem câu hỏi này để biết thêm chi tiết: PDO gửi truy vấn thô tới MySQL trong khi Mysqli gửi truy vấn đã chuẩn bị, cả hai đều tạo ra kết quả giống nhau

Người giới thiệu:

  1. Bảng cheat SQL
  2. Tiêm SQL
  3. Bảo mật thông tin
  4. Nguyên tắc bảo mật
  5. Xác nhận dữ liệu

175

Cảnh báo bảo mật : 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 SQL tiêm , thay vào đó hãy sử dụng các câu lệnh được chuẩn bị . Sử dụng chiến lược được nêu dưới đây có nguy cơ của riêng bạn. (Ngoài ra, mysql_real_escape_string()đã bị xóa trong PHP 7.)

Cảnh báo không dùng nữa: Phần mở rộng mysql không được dùng nữa tại thời điểm này. chúng tôi khuyên bạn nên sử dụng tiện ích mở rộng PDO

Tôi sử dụng ba cách khác nhau để ngăn ứng dụng web của mình không bị tổn thương khi tiêm SQL.

  1. Việc sử dụng mysql_real_escape_string(), đó là một chức năng được xác định trước trong PHP và mã add này backslashes để các ký tự sau: \x00, \n, \r, \, ', "\x1a. Truyền các giá trị đầu vào dưới dạng tham số để giảm thiểu cơ hội tiêm SQL.
  2. Cách tiên tiến nhất là sử dụng PDO.

Tôi hy vọng điều này sẽ giúp bạn.

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_opes () sẽ không bảo vệ ở đây. Nếu 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 thì điều đó bảo vệ bạn trước điều này. Đây là một giải pháp dưới đây cho việc này:

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

Câu hỏi này có một số câu trả lời tốt về điều này.

Tôi đề nghị, sử dụng PDO là lựa chọn tốt nhất.

Biên tập:

mysql_real_escape_string()không được dùng nữa kể từ PHP 5.5.0. Sử dụng mysqli hoặc PDO.

Một thay thế cho mysql_real_escape_opes () là

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

Thí dụ:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

169

Một cách đơn giản là sử dụng một khung công tác PHP như CodeIgniter hoặc Laravel có các tính năng sẵn có như lọc và ghi lại hoạt động để bạn không phải lo lắng về các sắc thái này.


7
Tôi nghĩ rằng toàn bộ vấn đề của câu hỏi là để thực hiện điều này mà không cần sử dụng khuôn khổ như vậy.
Sanke

147

Cảnh báo: cách tiếp cận được mô tả trong câu trả lời này chỉ áp dụng cho các tình huống rất cụ thể và không an toàn vì các cuộc tấn công tiêm SQL không chỉ dựa vào khả năng tiêm X=Y.

Nếu những kẻ tấn công đang cố gắng xâm nhập vào biểu mẫu thông qua $_GETbiến của PHP hoặc bằng chuỗi truy vấn của URL, bạn sẽ có thể bắt chúng nếu chúng không an toàn.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

Bởi vì 1=1, 2=2, 1=2, 2=1, 1+1=2, vv ... là những câu hỏi phổ biến đến một cơ sở dữ liệu SQL của kẻ tấn công. Có lẽ nó cũng được sử dụng bởi nhiều ứng dụng hack.

Nhưng bạn phải cẩn thận, rằng bạn không được viết lại một truy vấn an toàn từ trang web của bạn. Đoạn mã trên cung cấp cho bạn một mẹo, để viết lại hoặc chuyển hướng (tùy thuộc vào bạn) rằng chuỗi truy vấn động cụ thể hack vào một trang sẽ lưu trữ địa chỉ IP của kẻ tấn công hoặc NGAY LẬP TỨC, lịch sử, trình duyệt hoặc bất kỳ sự nhạy cảm nào khác thông tin, vì vậy bạn có thể đối phó với họ sau đó bằng cách cấm tài khoản của họ hoặc liên hệ với chính quyền.


Có chuyện 1-1=0gì thế? :)
Rápli András

@ RápliAndrás Một số loại ([0-9\-]+)=([0-9]+).
5ervant

127

Có rất nhiều câu trả lời cho PHP và MySQL , nhưng đây là mã cho PHP và Oracle để ngăn chặn việc tiêm SQL cũng như sử dụng trình điều khiển oci8 thường xuyên:

$conn = oci_connect($username, $password, $connection_string);
$stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
oci_bind_by_name($stmt, ':xx', $fieldval);
oci_execute($stmt);

Vui lòng giải thích các tham số oci_bind_by_name.
Jahanzeb Awan

127

Một ý tưởng tốt là sử dụng một trình ánh xạ quan hệ đối tượng như Idiorm :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

Nó không chỉ giúp bạn tiết kiệm từ SQL tiêm mà còn cả lỗi cú pháp nữa! Nó cũng hỗ trợ các bộ sưu tập các mô hình với chuỗi phương thức để lọc hoặc áp dụng các hành động cho nhiều kết quả cùng một lúc và nhiều kết nối.


124

Cảnh báo không dùng nữa: Mã mẫu của câu trả lời này (như mã mẫu của câu hỏi) sử dụng MySQLphần mở rộng của PHP , không được dùng trong PHP 5.5.0 và bị xóa hoàn toàn trong PHP 7.0.0.

Cảnh báo bảo mật : 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 SQL tiêm , thay vào đó hãy sử dụng các câu lệnh được chuẩn bị . Sử dụng chiến lược được nêu dưới đây có nguy cơ của riêng bạn. (Ngoài ra, mysql_real_escape_string()đã bị xóa trong PHP 7.)

Sử dụng PDOMYSQLi là một cách thực hành tốt để ngăn chặn việc tiêm SQL, nhưng nếu bạn thực sự muốn làm việc với các hàm và truy vấn của MySQL, tốt hơn là sử dụng

mysql_real_escape_ chuỗi

$unsafe_variable = mysql_real_escape_string($_POST['user_input']);

Có nhiều khả năng hơn để ngăn chặn điều này: như xác định - nếu đầu vào là một chuỗi, số, char hoặc mảng, có rất nhiều hàm sẵn có để phát hiện điều này. Ngoài ra, sẽ tốt hơn nếu sử dụng các chức năng này để kiểm tra dữ liệu đầu vào.

is_ chuỗi

$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');

is_numeric

$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');

Và tốt hơn hết là sử dụng các chức năng đó để kiểm tra dữ liệu đầu vào mysql_real_escape_string.


10
Ngoài ra, hoàn toàn không có điểm nào trong việc kiểm tra các thành viên mảng $ _POST với is_opes ()
Ý thức chung của bạn vào

21
CẢNH BÁO! mysql_real_escape_string() không phải là không thể sai lầm .
eggyal

10
mysql_real_escape_stringbây giờ không được dùng nữa, vì vậy nó không còn là một lựa chọn khả thi. Nó sẽ bị xóa khỏi PHP trong tương lai. Tốt nhất là chuyển sang những gì mà mọi người PHP hoặc MySQL khuyên dùng.
jww

2
Chủ đề: Không tin tưởng dữ liệu đã gửi của người dùng. Bất cứ điều gì bạn mong đợi là một dữ liệu rác với các ký tự đặc biệt hoặc logic boolean, chính nó sẽ trở thành một phần của truy vấn SQL mà bạn có thể đang thực hiện. Giữ các giá trị $ _POST dưới dạng dữ liệu, không phải phần SQL.
Boudel tối đa

88

Tôi đã viết chức năng nhỏ này vài năm trước:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

Điều này cho phép chạy các câu lệnh trong một chuỗi C # -ish String.Format như:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

Nó thoát khỏi việc xem xét các loại biến. Nếu bạn cố gắng tham số hóa tên bảng, tên cột, nó sẽ thất bại vì nó đặt mọi chuỗi trong dấu ngoặc kép là một cú pháp không hợp lệ.

CẬP NHẬT BẢO MẬT: str_replacePhiên bản trước cho phép tiêm bằng cách thêm {#} mã thông báo vào dữ liệu người dùng. Đây preg_replace_callbackphiên bản không gây ra vấn đề nếu thay thế chứa các thẻ.

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.