Mysql
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Mysql

Chèn SQL xung quanh mysql_real_escape_string ()

Câu trả lời ngắn gọn là vâng, có, có một cách để giải quyết mysql_real_escape_string() . # Đối với các TRƯỜNG HỢP CẠNH TRANH RẤT ĐÁNG YÊU !!!

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

Cuộc tấn công

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

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

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

  1. Chọn bộ ký tự

    mysql_query('SET NAMES gbk');
    

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

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

  2. Tải trọng

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

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

  3. mysql_real_escape_string ()

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

    Do đó, lệnh gọi đến mysql_real_escape_string() chèn dấu gạch chéo ngược và chúng tôi có ' bị treo miễn phí nhân vật trong nội dung "đã trốn thoát" của chúng tôi! Trên thực tế, nếu chúng ta xem xét $var trong gbk bộ ký tự, chúng ta sẽ thấy:

    縗' OR 1=1 /*

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

  4. Truy vấn

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

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

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

Điều tồi tệ

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

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

Bây giờ, cần lưu ý rằng bạn có thể ngăn chặn điều này bằng cách tắt các câu lệnh đã chuẩn bị mô phỏng:

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

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

Xấu xí

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

Nếu bạn đang sử dụng bản phát hành MySQL cũ hơn, thì một lỗi trong mysql_real_escape_string() có nghĩa là các ký tự nhiềubyte không hợp lệ chẳng hạn như các ký tự trong trọng tải của chúng tôi được coi là các byte đơn lẻ cho mục đích thoát ngay cả khi khách hàng đã được thông báo chính xác về mã hóa kết nối và vì vậy cuộc tấn công này sẽ vẫn thành công. Lỗi đã được sửa trong MySQL 4.1.20 , 5.0.22 5.1.11 .

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

Ơn cứu rỗi

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

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

Ví dụ về An toàn

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

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Bởi vì máy chủ đang mong đợi utf8 ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

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

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

Bởi vì chúng tôi đã tắt các câu lệnh chuẩn bị giả lập.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Bởi vì chúng tôi đã đặt bộ ký tự đúng cách.

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

Bởi vì MySQLi luôn thực hiện các câu lệnh chuẩn bị đúng.

Kết thúc

Nếu bạn:

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

HOẶC

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

Bạn an toàn 100%.

Nếu không, bạn sẽ dễ bị tấn công ngay cả khi bạn đang sử dụng mysql_real_escape_string() ...



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Ứng dụng Android có thể kết nối trực tiếp với cơ sở dữ liệu mysql trực tuyến không

  2. Cảnh báo:mysql_fetch_array () yêu cầu tham số 1 là tài nguyên, boolean được đưa vào

  3. Trong Apache Spark 2.0.0, có thể tìm nạp một truy vấn từ cơ sở dữ liệu bên ngoài (thay vì lấy toàn bộ bảng) không?

  4. Liên kết dự án không hoạt động trên Máy chủ Wamp

  5. mysql_fetch_array chỉ trả về một hàng