Htmlspecialchars và mysql_real_escape_string có giữ mã PHP của tôi an toàn khỏi bị tiêm không?


116

Đầu ngày hôm nay, một câu hỏi đã được đặt ra về các chiến lược xác thực đầu vào trong các ứng dụng web .

Câu trả lời hàng đầu, tại thời điểm viết bài, gợi ý PHPchỉ bằng cách sử dụng htmlspecialcharsmysql_real_escape_string.

Câu hỏi của tôi là: Điều này luôn luôn đủ? Có nhiều hơn nữa chúng ta nên biết? Các chức năng này bị hỏng do đâu?

Câu trả lời:


241

Khi nói đến các truy vấn cơ sở dữ liệu, hãy luôn thử và sử dụng các truy vấn được tham số sẵn. Các mysqliPDOcác thư viện hỗ trợ này. Điều này vô cùng an toàn hơn so với việc sử dụng các chức năng thoát như mysql_real_escape_string.

Vâng, mysql_real_escape_stringthực sự chỉ là một hàm thoát chuỗi. Nó không phải là một viên đạn ma thuật. Tất cả những gì nó sẽ làm là thoát các ký tự nguy hiểm để chúng có thể an toàn khi sử dụng trong một chuỗi truy vấn duy nhất. Tuy nhiên, nếu bạn không làm sạch đầu vào của mình trước thì bạn sẽ dễ bị tấn công bởi một số vectơ tấn công.

Hãy tưởng tượng câu lệnh SQL sau:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

Bạn có thể thấy rằng điều này rất dễ bị khai thác.
Hãy tưởng tượng idtham số chứa vector tấn công phổ biến:

1 OR 1=1

Không có ký tự rủi ro nào trong đó để mã hóa, vì vậy nó sẽ đi thẳng qua bộ lọc thoát. Để lại cho chúng tôi:

SELECT fields FROM table WHERE id= 1 OR 1=1

Đây là một vectơ SQL injection đáng yêu và sẽ cho phép kẻ tấn công trả lại tất cả các hàng. Hoặc là

1 or is_admin=1 order by id limit 1

sản xuất

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

Điều này cho phép kẻ tấn công trả lại thông tin chi tiết của quản trị viên đầu tiên trong ví dụ hoàn toàn hư cấu này.

Mặc dù các chức năng này hữu ích nhưng chúng phải được sử dụng cẩn thận. Bạn cần đảm bảo rằng tất cả các đầu vào web đều được xác thực ở một mức độ nào đó. Trong trường hợp này, chúng tôi thấy rằng chúng tôi có thể bị khai thác vì chúng tôi đã không kiểm tra xem biến chúng tôi đang sử dụng dưới dạng số có thực sự là số hay không. Trong PHP, bạn nên sử dụng rộng rãi một tập hợp các hàm để kiểm tra xem đầu vào có phải là số nguyên, float, chữ và số, v.v. Nhưng khi nói đến SQL, hầu hết đều chú ý đến giá trị của câu lệnh đã chuẩn bị. Đoạn mã trên sẽ được bảo mật nếu nó là một câu lệnh được chuẩn bị sẵn vì các hàm cơ sở dữ liệu sẽ biết rằng đó 1 OR 1=1không phải là một chữ hợp lệ.

Đối với htmlspecialchars(). Đó là một bãi mìn của riêng nó.

Có một vấn đề thực sự trong PHP là nó có toàn bộ lựa chọn các hàm thoát khác nhau liên quan đến html và không có hướng dẫn rõ ràng về chính xác hàm nào làm những gì.

Thứ nhất, nếu bạn đang ở bên trong thẻ HTML, bạn đang gặp rắc rối thực sự. Nhìn vào

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Chúng ta đã ở trong thẻ HTML, vì vậy chúng ta không cần <hoặc> làm bất cứ điều gì nguy hiểm. Véc tơ tấn công của chúng tôi có thể làjavascript:alert(document.cookie)

Bây giờ HTML kết quả trông giống như

<img src= "javascript:alert(document.cookie)" />

Cuộc tấn công được thông qua.

Nó trở nên tồi tệ hơn. Tại sao? bởi vì htmlspecialchars(khi được gọi theo cách này) chỉ mã hóa dấu ngoặc kép chứ không mã hóa đơn. Vì vậy, nếu chúng ta có

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Kẻ tấn công xấu xa của chúng ta giờ có thể đưa các thông số hoàn toàn mới vào

pic.png' onclick='location.href=xxx' onmouseover='...

cho chúng tôi

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

Trong những trường hợp này, không có viên đạn ma thuật nào cả, bạn chỉ cần tự đánh đầu vào. Nếu bạn cố gắng và lọc ra những ký tự xấu chắc chắn bạn sẽ thất bại. Thực hiện cách tiếp cận danh sách trắng và chỉ thông qua các ký tự tốt. Xem bảng gian lận XSS để biết các ví dụ về mức độ đa dạng của các vectơ

Ngay cả khi bạn sử dụng htmlspecialchars($string)bên ngoài các thẻ HTML, bạn vẫn dễ bị tấn công bởi các vectơ tấn công bộ ký tự nhiều byte.

Hiệu quả nhất mà bạn có thể đạt được là sử dụng kết hợp mb_convert_encoding và htmlentities như sau.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Thậm chí, điều này còn khiến IE6 dễ bị tấn công, do cách nó xử lý UTF. Tuy nhiên, bạn có thể quay trở lại mã hóa hạn chế hơn, chẳng hạn như ISO-8859-1, cho đến khi việc sử dụng IE6 giảm xuống.

Để có nghiên cứu chuyên sâu hơn về các vấn đề multibyte, hãy xem https://stackoverflow.com/a/12118602/1820


24
Điều duy nhất bị bỏ lỡ ở đây, là ví dụ đầu tiên cho truy vấn DB ... một intval () đơn giản sẽ giải quyết việc tiêm. Luôn sử dụng intval () thay cho mysqlescape ... () khi cần một số chứ không phải một chuỗi.
Robert K

11
và hãy nhớ rằng sử dụng truy vấn được tham số hóa sẽ cho phép bạn luôn có dữ liệu được coi là dữ liệu chứ không phải mã. Sử dụng thư viện như PDO và sử dụng các truy vấn được tham số hóa bất cứ khi nào có thể.
Cheekysoft

9
Hai nhận xét: 1. Trong ví dụ đầu tiên, bạn sẽ an toàn nếu bạn cũng đặt dấu ngoặc kép xung quanh tham số, như $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. Trong trường hợp thứ hai (thuộc tính chứa URL), không có tác dụng htmlspecialcharsgì cả; trong những trường hợp này, bạn nên mã hóa đầu vào bằng lược đồ mã hóa URL, ví dụ: sử dụng rawurlencode. Bằng cách đó, người dùng không thể chèn javascript:et al.
Marcel Korpel

7
“Htmlspecialchars chỉ mã hóa dấu ngoặc kép chứ không phải đơn”: điều đó không đúng, nó phụ thuộc vào các cờ được đặt, hãy xem các tham số của nó .
Marcel Korpel,

2
Điều này nên được in đậm: Take a whitelist approach and only let through the chars which are good.Một danh sách đen sẽ luôn luôn bỏ sót một cái gì đó. +1
Jo Smo

10

Ngoài câu trả lời xuất sắc của Cheekysoft:

  • Có, chúng sẽ giúp bạn an toàn, nhưng chỉ khi chúng được sử dụng hoàn toàn đúng cách. Sử dụng chúng không đúng cách và bạn sẽ vẫn dễ bị tổn thương và có thể gặp các vấn đề khác (ví dụ: hỏng dữ liệu)
  • Vui lòng sử dụng các truy vấn được tham số hóa thay thế (như đã nêu ở trên). Bạn có thể sử dụng chúng thông qua ví dụ như PDO hoặc thông qua trình bao bọc như PEAR DB
  • Đảm bảo rằng magic_quotes_gpc và magic_quotes_runtime luôn tắt và không bao giờ vô tình được bật, thậm chí không phải trong thời gian ngắn. Đây là một nỗ lực sớm và sai lầm sâu sắc của các nhà phát triển PHP để ngăn chặn các vấn đề bảo mật (phá hủy dữ liệu)

Không thực sự là một viên đạn bạc để ngăn chặn việc đưa HTML vào (ví dụ: viết mã trang web chéo), nhưng bạn có thể đạt được điều đó dễ dàng hơn nếu bạn đang sử dụng thư viện hoặc hệ thống tạo khuôn mẫu để xuất HTML. Đọc tài liệu về điều đó để biết cách thoát khỏi mọi thứ một cách thích hợp.

Trong HTML, mọi thứ cần được thoát khác nhau tùy thuộc vào ngữ cảnh. Điều này đặc biệt đúng với các chuỗi được đặt vào Javascript.


3

Tôi chắc chắn sẽ đồng ý với các bài viết trên, nhưng tôi có một điều nhỏ cần bổ sung để trả lời câu trả lời của Cheekysoft, cụ thể:

Khi nói đến các truy vấn cơ sở dữ liệu, hãy luôn thử và sử dụng các truy vấn được tham số sẵn. Thư viện mysqli và PDO hỗ trợ điều này. Điều này vô cùng an toàn hơn so với việc sử dụng các hàm thoát như mysql_real_escape_string.

Có, mysql_real_escape_string thực chất chỉ là một hàm thoát chuỗi. Nó không phải là một viên đạn ma thuật. Tất cả những gì nó sẽ làm là thoát các ký tự nguy hiểm để chúng có thể an toàn khi sử dụng trong một chuỗi truy vấn duy nhất. Tuy nhiên, nếu bạn không làm sạch đầu vào của mình trước thì bạn sẽ dễ bị tấn công bởi một số vectơ tấn công.

Hãy tưởng tượng câu lệnh SQL sau:

$ result = "CHỌN các trường TỪ bảng WHERE id =" .mysql_real_escape_string ($ _ POST ['id']);

Bạn sẽ có thể thấy rằng điều này rất dễ bị khai thác. Hãy tưởng tượng tham số id chứa vector tấn công phổ biến:

1 HOẶC 1 = 1

Không có ký tự rủi ro nào trong đó để mã hóa, vì vậy nó sẽ đi thẳng qua bộ lọc thoát. Để lại cho chúng tôi:

CHỌN trường TỪ bảng WHERE id = 1 HOẶC 1 = 1

Tôi đã mã hóa một hàm nhỏ nhanh chóng mà tôi đặt trong lớp cơ sở dữ liệu của mình để loại bỏ bất cứ thứ gì không phải là số. Nó sử dụng preg_replace, do đó, có một chức năng tối ưu hơn một chút, nhưng nó hoạt động không tốt ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Vì vậy, thay vì sử dụng

$ result = "CHỌN các trường TỪ bảng WHERE id =" .mysqlrealescapestring ("1 HOẶC 1 = 1");

tôi sẽ dùng

$ result = "CHỌN các trường TỪ bảng WHERE id =" .Numbers ("1 HOẶC 1 = 1");

và nó sẽ chạy truy vấn một cách an toàn

CHỌN trường TỪ bảng WHERE id = 111

Chắc chắn, điều đó chỉ ngăn nó hiển thị đúng hàng, nhưng tôi không nghĩ rằng đó là một vấn đề lớn đối với những ai đang cố gắng đưa sql vào trang web của bạn;)


1
Hoàn hảo! Đây là chính xác loại sanitisation bạn cần. Mã ban đầu không thành công vì nó không xác thực rằng một số là số. Mã của bạn thực hiện điều này. bạn nên gọi Numbers () trên tất cả các vars sử dụng số nguyên có giá trị bắt nguồn từ bên ngoài cơ sở mã.
Cheekysoft 22/09/08

1
Điều đáng nói là intval () sẽ hoạt động hoàn toàn tốt cho việc này, vì PHP tự động ép các số nguyên thành chuỗi cho bạn.
Adam Ernst

11
Tôi thích intval hơn. Nó biến 1abc2 thành 1, không phải 12.
jmucchiello

1
intval tốt hơn, đặc biệt là trên ID. Hầu hết thời gian, nếu nó bị hỏng, nó giống như ở trên, 1 hoặc 1 = 1. Bạn thực sự không nên làm lộ ID của người khác. Vì vậy, intval sẽ trả về đúng ID. Sau đó, bạn nên kiểm tra xem giá trị ban đầu và đã được làm sạch có giống nhau không. Đó là một cách tuyệt vời để không chỉ ngăn chặn các cuộc tấn công mà còn tìm ra những kẻ tấn công.
triunenature

2
Hàng không chính xác sẽ thật tai hại nếu bạn đang hiển thị dữ liệu cá nhân, bạn sẽ thấy thông tin của người dùng khác! thay vào đó nó sẽ được tốt hơn để kiểm trareturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte

2

Một phần quan trọng của câu đố này là bối cảnh. Ai đó gửi "1 OR 1 = 1" dưới dạng ID không phải là vấn đề nếu bạn trích dẫn mọi đối số trong truy vấn của mình:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

Kết quả là:

SELECT fields FROM table WHERE id='1 OR 1=1'

mà là không hiệu quả. Vì bạn đang thoát chuỗi, đầu vào không thể thoát ra khỏi ngữ cảnh chuỗi. Tôi đã thử nghiệm điều này với phiên bản 5.0.45 của MySQL và việc sử dụng ngữ cảnh chuỗi cho cột số nguyên không gây ra bất kỳ sự cố nào.


15
và sau đó tôi sẽ bắt đầu vectơ tấn công của mình với ký tự nhiều byte 0xbf27 trong cơ sở dữ liệu latin1 của bạn sẽ được chuyển đổi bởi bộ lọc fuction thành 0xbf5c27 - là một ký tự nhiều byte đơn theo sau là một dấu ngoặc kép.
Cheekysoft 22/09/08

8
Cố gắng không bảo vệ trước một vectơ tấn công đã biết. Bạn sẽ phải theo đuổi cái đuôi của mình cho đến khi hết thời gian áp dụng bản vá này đến bản vá khác cho mã của bạn. Đứng lại và xem xét các trường hợp chung sẽ chuyển sang mã an toàn hơn và tư duy tập trung vào bảo mật tốt hơn.
Cheekysoft 22/09/08

Tôi đồng ý; lý tưởng là OP sẽ sử dụng các câu lệnh đã chuẩn bị sẵn.
Lucas Oman

1
Mặc dù trích dẫn các lập luận được đề xuất bởi bài đăng này không phải là hoàn hảo, nhưng nó sẽ giảm thiểu nhiều cuộc tấn công kiểu 1 OR 1 = 1 phổ biến nên nó đáng được đề cập.
Night Owl

2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Hoạt động tốt, thậm chí tốt hơn trên hệ thống 64 bit. Tuy nhiên, hãy cẩn thận với những hạn chế hệ thống của bạn trong việc giải quyết số lượng lớn, nhưng đối với id cơ sở dữ liệu, điều này hoạt động hiệu quả 99%.

Bạn cũng nên sử dụng một chức năng / phương pháp duy nhất để làm sạch các giá trị của mình. Ngay cả khi hàm này chỉ là một trình bao bọc cho mysql_real_escape_string (). Tại sao? Bởi vì một ngày nào đó khi một phương pháp khai thác dữ liệu ưa thích của bạn được tìm thấy, bạn chỉ phải cập nhật nó ở một nơi, thay vì tìm và thay thế trên toàn hệ thống.


-3

tại sao, oh TẠI SAO, bạn sẽ không bao gồm dấu ngoặc kép xung quanh đầu vào của người dùng trong câu lệnh sql của bạn? có vẻ khá ngớ ngẩn không! bao gồm dấu ngoặc kép trong câu lệnh sql của bạn sẽ làm cho "1 hoặc 1 = 1" một nỗ lực không có kết quả, phải không?

vì vậy bây giờ, bạn sẽ nói, "điều gì sẽ xảy ra nếu người dùng bao gồm một dấu ngoặc kép (hoặc dấu ngoặc kép) trong đầu vào?"

tốt, sửa chữa dễ dàng cho điều đó: chỉ cần xóa dấu ngoặc kép do người dùng nhập. ví dụ: input =~ s/'//g;. bây giờ, dường như đối với tôi, đầu vào của người dùng sẽ được bảo mật ...


"tại sao, ồ TẠI SAO, bạn sẽ không bao gồm dấu ngoặc kép xung quanh thông tin người dùng nhập vào câu lệnh sql của bạn?" - Câu hỏi không nói gì về việc không trích dẫn đầu vào của người dùng.
Quentin

1
"well, easy fix for that" - Sửa chữa kinh khủng cho điều đó. Điều đó ném đi dữ liệu. Bản thân giải pháp được đề cập trong câu hỏi là một cách tiếp cận tốt hơn.
Quentin

trong khi tôi đồng ý câu hỏi không giải quyết việc trích dẫn đầu vào của người dùng, có vẻ như vẫn chưa trích dẫn đầu vào. và, tôi muốn tung dữ liệu hơn là nhập dữ liệu xấu. nói chung, trong một cuộc tấn công tiêm, dù sao bạn cũng KHÔNG muốn dữ liệu đó .... phải không?
Jarett L

"trong khi tôi đồng ý rằng câu hỏi không giải quyết việc trích dẫn đầu vào của người dùng, có vẻ như vẫn không trích dẫn đầu vào." - Không, không. Câu hỏi không thể hiện nó theo cách này hay cách khác.
Quentin

1
@JarettL Hãy quen với việc sử dụng các câu lệnh chuẩn bị sẵn hoặc quen với việc Bobby Table phá hủy dữ liệu của bạn vào mỗi Thứ Ba . SQL được tham số hóa là cách tốt nhất duy nhất để bảo vệ bạn chống lại SQL injection. Bạn không cần thực hiện "kiểm tra SQL injection" nếu bạn đang sử dụng một câu lệnh đã chuẩn bị. Chúng cực kỳ dễ thực hiện (và theo ý kiến ​​của tôi, làm cho mã dễ đọc hơn RẤT NHIỀU), bảo vệ khỏi các đặc điểm riêng khác nhau của nối chuỗi và chèn sql, và tốt nhất, bạn không phải phát minh lại bánh xe để triển khai nó .
Siyual
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.