+1

SQL injection vulnerabilities - Lỗ hổng SQL injection (Phần 11)

VI. Demo ngăn chặn lỗ hổng SQL injection

1. Ngôn ngữ PHP

Xem xét đoạn code PHP với cơ sở dữ liệu MySQL xử lý input chứa lỗ hổng SQL injection sau:

$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";

mysql_query($query);

Dễ dàng nhận thấy đoạn code trên đang ẩn chứa nguy cơ bị tấn công SQL injection do không có cơ chế ngăn chặn nào với các giá trị người dùng nhập hoặc câu truy vấn.

1.1. Cơ bản

  • Sử dụng hàm mysql_real_escape_string()

Hàm mysql_real_escape_string() sẽ chuyển các ký tự đặc biệt trong chuỗi thành các ký tự hợp lệ để truy vấn cơ sở dữ liệu. Ví dụ với input This is a string with 'single quotes' and "double quotes" sau khi sử dụng hàm trên sẽ trở thành This is a string with \'single quotes\' and \"double quotes\". Cài đặt như sau:

$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
  • Sử dụng hàm htmlspecialchars()

Hàm htmlspecialchars() sẽ chuyển các ký tự đặc biệt như &, <, >, ", ', và / thành các ký tự HTML. Cài đặt như sau:

$username = htmlspecialchars($_POST['username']);
$password = htmlspecialchars($_POST['password']);

1.2. Trung bình

  • Sử dụng hàm addslashes()

Hàm addslashes() thêm dấu gạch chéo \ trước các ký tự đặc biệt như dấu nháy đơn ', dấu nháy kép ", dấu chấm phẩy ; và dấu gạch chéo \ trong chuỗi. Ví dụ khi người dùng nhập giá trị cho biến $username' OR 1=1 -- sau khi sử dụng hàm trên sẽ trở thành \' OR 1=1 --. Câu truy vấn khi đó: SELECT * FROM users WHERE username = '\' OR 1=1 --' trở nên an toàn. Cài đặt như sau:

$username = addslashes($_POST['username']);
$password = addslashes($_POST['password']);
  • Sử dụng

Hàm mysqli_real_escape_string() là một hàm trong PHP dùng để ngăn chặn lỗ hổng SQL injection. Nó sẽ chuyển các ký tự đặc biệt trong chuỗi như ', ", ;, ... thành các ký tự hợp lệ để truy vấn cơ sở dữ liệu. Cài đặt như sau:

$username = mysqli_real_escape_string($conn, $_POST['username']);
$password = mysqli_real_escape_string($conn, $_POST['password']);

1.3. Nâng cao

  • Sử dụng PDO prepared statements

PDO prepared statements sử dụng các câu lệnh SQL được thực thi trước đó để tránh các lỗ hổng bảo mật như SQL injection. Đồng thời nó cũng giúp tối ưu hóa các truy vấn và tăng tốc độ truy vấn.

Cách sử dụng PDO prepared statements:

// Tạo một đối tượng PDO:
$db = new PDO('mysql:host=localhost;dbname=myDatabase', $user, $pass);

// Tạo câu lệnh SQL cần thực thi:
$sql = "SELECT * FROM users WHERE username = :username";

//Tạo một đối tượng PDOStatement:
$stmt = $db->prepare($sql);

// Gán giá trị cho các tham số trong câu lệnh SQL:
$stmt->bindParam(':username', $username);

// Thực thi câu lệnh:
$stmt->execute();

// Lấy kết quả trả về:
$result = $stmt->fetchAll();

  • Sử dụng các hàm mysqli_stmt_bind_param(), mysqli_stmt_execute(), mysqli_stmt_fetch()

mysqli_stmt_bind_param() dùng để liên kết các tham số đầu vào với câu lệnh SQL đã được biên dịch. Nó được sử dụng để tránh các lỗi bảo mật và để tránh việc phải thực hiện các chuỗi để truy vấn cơ sở dữ liệu.

mysqli_stmt_execute() dùng để thực thi một câu lệnh SQL đã được biên dịch trước bởi hàm mysqli_stmt_prepare(). Nó trả về TRUE nếu câu lệnh được thực thi thành công và FALSE nếu thất bại.

mysqli_stmt_fetch() dùng để lấy dữ liệu từ một kết quả truy vấn đã được thực thi bằng câu lệnh truy vấn đã được biên dịch. Hàm này trả về một mảng các giá trị của các cột trong kết quả truy vấn.

Cách cài đặt như sau:

$stmt = $mysqli->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->bind_param("ss", $name, $email);

/* Set our params */
$name = "Viblo";
$email = "viblo@example.com";

/* Execute the prepared Statement */
$stmt->execute();

/* Fetch the result */
$result = $stmt->get_result();
$row = $result->fetch_assoc();

/* Close the statement */
$stmt->close();

2. Framework Rail

  • Sử dụng các Rails built-in sanitization methods.

Framework Rails tích hợp các built-in sanitization methods giúp ngăn chặn các cuộc tấn công SQL Injection. Những method này có sẵn thông qua class ActiveRecord::Base và có thể được sử dụng để "làm sạch" dữ liệu người dùng trước khi được sử dụng trong câu truy vấn. Bao gồm: ActiveRecord::Base.sanitize_sql, ActiveRecord::Base.sanitize_sql_array, ActiveRecord::Base.sanitize_sql_for_assignment, ActiveRecord::Base.sanitize_sql_for_conditions, ActiveRecord::Base.sanitize_sql_hash. Cài đặt ví dụ:

sql = "SELECT * FROM users WHERE name = ?"
params = ["Viblo"]

sanitized_sql = ActiveRecord::Base.sanitize_sql([sql, params])

# sanitized_sql = "SELECT * FROM users WHERE name = 'Viblo'"
  • Sử dụng các Rails built-in escaping methods

Rails built-in escaping methods ngăn chặn tấn công SQL injection bao gồm:

1. Active Record's attribute methods:
- ActiveRecord::Base#attribute
- ActiveRecord::Base#attributes
- ActiveRecord::Base#attribute_names
- ActiveRecord::Base#attribute_present

2. Active Record's sanitize methods:
- ActiveRecord::Base#sanitize_sql
- ActiveRecord::Base#sanitize_sql_array
- ActiveRecord::Base#sanitize_sql_for_assignment

3. Active Record's quoting methods:
- ActiveRecord::Base#quote
- ActiveRecord::Base#quote_value
- ActiveRecord::Base#quoted_table_name
- ActiveRecord::Base#quoted_primary_key

4. Active Record's type casting methods:
- ActiveRecord::Base#type_cast_attribute_for_write
- ActiveRecord::Base#type_cast_attribute_for_read
- ActiveRecord::Base#type_cast_attribute

5. Active Record's quoting methods:
- ActiveRecord::Base#quote_column_name
- ActiveRecord::Base#quote_table_name
- ActiveRecord::Base#quote_default_expression

6. Active Record's escaping methods:
- ActiveRecord::Base#connection.quote
- ActiveRecord::Base#connection.quote_string
- ActiveRecord::Base#connection.quote_column_name
- ActiveRecord::Base#connection.quote_table_name

Một ví dụ sử dụng method sanitize_sql_array() chỉnh sửa một mảng các chuỗi và lọc bỏ bất kỳ ký tự đặc biệt nào có thể được sử dụng để chèn mã SQL độc hại:

sanitize_sql_array(["SELECT * FROM users WHERE name = ?", params[:name]])

Dòng code trên sẽ escape bất kỳ ký tự đặc biệt nào trong tham số params[:name] trước khi nó được sử dụng trong truy vấn SQL.

3. Sử dụng tường lửa

Chúng ta cũng có thể sử dụng tường lửa (firewall) thực hiện ngăn chặn tấn công SQL injection. Ở đây tôi sẽ lấy ví dụ với firewall mod_security. Một ví dụ đơn giản cho rule ngăn chặn tấn công như sau:

SecRule REQUEST_URI "^/index\.php" \
    "chain,phase:2,t:none,block,msg:'SQL injection attack detected'" \
    "id:'981234',severity:'2',tag:'application-multi'" \
    "skipAfter:END_SQL_INJECTION_CHECK"

SecRule REQUEST_FILENAME "index\.php" \
    "chain"

SecRule ARGS "SELECT" \
    "chain"

SecRule ARGS "INSERT" \
    "chain"

SecRule ARGS "DELETE" \
    "chain"

SecRule ARGS "UNION" \
    "chain"

SecRule ARGS "CONCAT" \
    "chain"

SecRule ARGS "--" \
    "chain"

SecRule ARGS "\|" \
    "chain"

SecRule ARGS "\%" \
    "chain"

SecRule ARGS "\'" \
    "chain"

SecRule ARGS "\;" \
    "chain"

SecRule ARGS "\(" \
    "chain"

SecRule ARGS "\)" \
    "chain"

SecMarker END_SQL_INJECTION_CHECK

Rule trên sẽ kiểm tra các request tới index.php, thực hiện block các request khả nghi chứa các keywords hoặc patterns thường được sử dụng trong các kịch bản tấn công SQL injection như SELECT, INSERT, UNION, DELETE, CONCAT. Đồng thời cũng block các request chứa các ký tự đặc biệt như --, |, ... Tuy nhiên, việc config tường lửa cần thận trọng do có thể xảy ra tình trạng false positive (block "nhầm" các request bình thường từ user). Chẳng hạn với rule như trên cũng có thể block một số request từ người dùng khi họ sử dụng các dấu ngoặc (, ).

Để tránh xảy ra tình trạng false positive, chúng ta có thể thêm một số rule kiểm tra các yếu tố có thể giúp chúng ta xác định request đó là một request an toàn.

Chẳng hạn có thể kiểm tra độ dài request do các request có kích thước lớn thường là dấu hiệu cho thấy ẩn chứa nguy cơ khai thác lỗ hổng SQL injection. Ví dụ kiểm tra request và chỉ cho phép các request có độ dài nhỏ hơn hoặc bằng 100100:

SecRule REQUEST_BODY "^.{101,}$" \
    "chain"

Hoặc có thể thêm rule kiểm tra header HTTP Referer và chỉ block các request có header Referer bất thường. Ví dụ:

SecRule REQUEST_HEADERS:Referer "^https?://" \
    "chain"

Các tài liệu tham khảo


©️ Tác giả: Lê Ngọc Hoa từ Viblo


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí