SQL injection là gì và cách ngăn chặn trong các ứng dụng PHP?

Spread the love

Vì vậy, bạn nghĩ rằng cơ sở dữ liệu SQL của bạn hoạt động hiệu quả và an toàn không bị phá hủy ngay lập tức? Chà, SQL Injection không đồng ý!

Vâng, đó là sự hủy diệt ngay lập tức mà chúng ta đang nói đến, bởi vì tôi không muốn mở đầu bài viết này với thuật ngữ khập khiễng thông thường là “thắt chặt an ninh” và “ngăn chặn truy cập độc hại”. SQL Injection là một thủ thuật cũ trong sách mà mọi người, mọi nhà phát triển đều biết rất rõ về nó và biết rõ cách ngăn chặn nó. Ngoại trừ một lần kỳ lạ khi họ trượt chân, và kết quả có thể là thảm họa.

Nếu bạn đã biết SQL Injection là gì, vui lòng chuyển sang nửa sau của bài viết. Nhưng đối với những người mới bắt đầu trong lĩnh vực phát triển web và đang mơ ước được đảm nhận các vai trò cấp cao hơn, thì một số lời giới thiệu là phù hợp.

SQL injection là gì?

Chìa khóa để hiểu SQL Injection nằm ở cái tên của nó: SQL + Injection. Từ “tiêm” ở đây không có bất kỳ ý nghĩa y tế nào, mà là cách sử dụng của động từ “tiêm”. Cùng với nhau, hai từ này truyền đạt ý tưởng đưa SQL vào một ứng dụng web.

Đưa SQL vào một ứng dụng web. . . hừm . . . Đó không phải là những gì chúng ta đang làm sao? Có, nhưng chúng tôi không muốn kẻ tấn công điều khiển cơ sở dữ liệu của chúng tôi. Hãy hiểu điều đó với sự giúp đỡ của một ví dụ.

Giả sử bạn đang xây dựng một trang web PHP điển hình cho một cửa hàng thương mại điện tử địa phương, vì vậy bạn quyết định thêm một biểu mẫu liên hệ như sau:

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

Và giả sử tệp send_message.php lưu trữ mọi thứ trong cơ sở dữ liệu để chủ cửa hàng có thể đọc tin nhắn của người dùng sau này. Nó có thể có một số mã như thế này:

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

Vì vậy, trước tiên bạn đang cố gắng xem liệu người dùng này đã có tin nhắn chưa đọc chưa. Truy vấn SELECT * from messages where name = $name có vẻ khá đơn giản phải không?

SAI LẦM!

Trong sự ngây thơ của mình, chúng tôi đã mở ra cánh cửa dẫn đến việc cơ sở dữ liệu của chúng tôi bị phá hủy ngay lập tức. Để điều này xảy ra, kẻ tấn công phải đáp ứng các điều kiện sau:

  • Ứng dụng đang chạy trên cơ sở dữ liệu SQL (ngày nay, hầu hết mọi ứng dụng đều như vậy)
  • Kết nối cơ sở dữ liệu hiện tại có quyền “chỉnh sửa” và “xóa” trên cơ sở dữ liệu
  • Tên của các bảng quan trọng có thể được đoán

Điểm thứ ba có nghĩa là bây giờ kẻ tấn công biết bạn đang điều hành một cửa hàng thương mại điện tử, rất có thể bạn đang lưu trữ dữ liệu đơn hàng trong bảng đơn hàng. Được trang bị tất cả những thứ này, tất cả những gì kẻ tấn công cần làm là cung cấp tên này làm tên của chúng:

  Chia sẻ tệp, thư mục và ảnh chụp màn hình chỉ trong một cú nhấp chuột

Joe; cắt bớt lệnh;? Vâng thưa ngài! Hãy xem truy vấn sẽ trở thành gì khi nó được thực thi bởi tập lệnh PHP:

CHỌN * TỪ tin nhắn WHERE name = Joe; cắt ngắn đơn đặt hàng;

Được rồi, phần đầu tiên của truy vấn có lỗi cú pháp (không có dấu ngoặc kép xung quanh “Joe”), nhưng dấu chấm phẩy buộc công cụ MySQL bắt đầu diễn giải một câu hỏi mới: cắt bớt các lệnh. Cứ như vậy, chỉ trong một cú nhấp chuột, toàn bộ lịch sử đơn hàng đã biến mất!

Bây giờ bạn đã biết cách thức hoạt động của SQL Injection, đã đến lúc xem cách ngăn chặn nó. Hai điều kiện cần phải đáp ứng để tiêm SQL thành công là:

  • Tập lệnh PHP phải có đặc quyền sửa đổi/xóa trên cơ sở dữ liệu. Tôi nghĩ điều này đúng với tất cả các ứng dụng và bạn sẽ không thể đặt ứng dụng của mình ở chế độ chỉ đọc. 🙂 Và hãy đoán xem, ngay cả khi chúng tôi xóa tất cả các đặc quyền sửa đổi, SQL injection vẫn có thể cho phép ai đó chạy các truy vấn CHỌN và xem tất cả cơ sở dữ liệu, bao gồm cả dữ liệu nhạy cảm. Nói cách khác, việc giảm cấp độ truy cập cơ sở dữ liệu không hoạt động và ứng dụng của bạn vẫn cần nó.
  • Đầu vào của người dùng đang được xử lý. Cách duy nhất SQL injection có thể hoạt động là khi bạn đang chấp nhận dữ liệu từ người dùng. Một lần nữa, việc dừng tất cả đầu vào cho ứng dụng của bạn chỉ vì bạn lo lắng về SQL injection là không thực tế.
  • Ngăn chặn SQL injection trong PHP

    Bây giờ, do các kết nối cơ sở dữ liệu, truy vấn và đầu vào của người dùng là một phần của cuộc sống, làm cách nào để ngăn chặn việc tiêm SQL? Rất may, nó khá đơn giản và có hai cách để thực hiện: 1) làm sạch đầu vào của người dùng và 2) sử dụng các câu lệnh đã chuẩn bị sẵn.

    Vệ sinh đầu vào của người dùng

    Nếu bạn đang sử dụng phiên bản PHP cũ hơn (5.5 trở xuống và điều này xảy ra rất nhiều trên dịch vụ lưu trữ được chia sẻ), bạn nên chạy tất cả dữ liệu nhập của người dùng thông qua một hàm gọi là mysql_real_escape_string(). Về cơ bản, những gì nó làm là loại bỏ tất cả các ký tự đặc biệt trong một chuỗi để chúng mất đi ý nghĩa khi được cơ sở dữ liệu sử dụng.

    Chẳng hạn, nếu bạn có một chuỗi như tôi là một chuỗi, thì kẻ tấn công có thể sử dụng ký tự trích dẫn đơn (‘) để thao tác với truy vấn cơ sở dữ liệu đang được tạo và gây ra một lệnh tiêm SQL. Chạy nó thông qua mysql_real_escape_string() tạo ra tôi là một chuỗi, thêm dấu gạch chéo ngược vào trích dẫn đơn, thoát khỏi nó. Kết quả là, toàn bộ chuỗi hiện được chuyển dưới dạng một chuỗi vô hại vào cơ sở dữ liệu, thay vì có thể tham gia vào thao tác truy vấn.

      Cách tắt hoàn toàn mọi rung động trên iPhone của bạn

    Có một nhược điểm với cách tiếp cận này: đó là một kỹ thuật thực sự rất cũ đi cùng với các hình thức truy cập cơ sở dữ liệu cũ hơn trong PHP. Kể từ PHP 7, chức năng này thậm chí không còn tồn tại nữa, điều này đưa chúng ta đến giải pháp tiếp theo.

    Sử dụng báo cáo chuẩn bị

    Các câu lệnh được chuẩn bị sẵn là một cách để thực hiện các truy vấn cơ sở dữ liệu một cách an toàn và đáng tin cậy hơn. Ý tưởng là thay vì gửi truy vấn thô tới cơ sở dữ liệu, trước tiên chúng tôi cho cơ sở dữ liệu biết cấu trúc của truy vấn mà chúng tôi sẽ gửi. Đây là những gì chúng tôi muốn nói bằng cách “chuẩn bị” một tuyên bố. Khi một câu lệnh được chuẩn bị, chúng tôi chuyển thông tin dưới dạng đầu vào được tham số hóa để cơ sở dữ liệu có thể “lấp đầy khoảng trống” bằng cách cắm đầu vào vào cấu trúc truy vấn mà chúng tôi đã gửi trước đó. Điều này lấy đi bất kỳ sức mạnh đặc biệt nào mà các đầu vào có thể có, khiến chúng chỉ được coi là các biến (hoặc tải trọng, nếu bạn muốn) trong toàn bộ quá trình. Đây là những tuyên bố đã chuẩn bị trông như thế nào:

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $firstname = "John";
    $lastname = "Doe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Mary";
    $lastname = "Moe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Julie";
    $lastname = "Dooley";
    $email = "[email protected]";
    $stmt->execute();
    
    echo "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    Tôi biết quy trình này nghe có vẻ phức tạp không cần thiết nếu bạn chưa quen với các câu lệnh đã chuẩn bị sẵn, nhưng khái niệm này rất đáng để bạn nỗ lực. Đây là một giới thiệu tốt đẹp về nó.

    Đối với những người đã quen thuộc với phần mở rộng PDO của PHP và sử dụng nó để tạo các câu lệnh chuẩn bị sẵn, tôi có một lời khuyên nhỏ.

    Cảnh báo: Hãy cẩn thận khi thiết lập PDO

    Khi sử dụng PDO để truy cập cơ sở dữ liệu, chúng ta có thể bị cuốn vào cảm giác an toàn sai lầm. “À, tôi đang sử dụng PDO. Bây giờ tôi không cần phải nghĩ về bất cứ điều gì khác nữa” – đây là cách suy nghĩ của chúng ta thường diễn ra. Đúng là PDO (hoặc các câu lệnh chuẩn bị sẵn của MySQLi) đủ để ngăn chặn tất cả các loại tấn công SQL injection, nhưng bạn phải cẩn thận khi thiết lập nó. Thông thường, bạn chỉ cần sao chép-dán mã từ các hướng dẫn hoặc từ các dự án trước đây của mình và tiếp tục, nhưng cài đặt này có thể hoàn tác mọi thứ:

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    Những gì cài đặt này thực hiện là yêu cầu PDO mô phỏng các câu lệnh đã chuẩn bị thay vì thực sự sử dụng tính năng câu lệnh đã chuẩn bị của cơ sở dữ liệu. Do đó, PHP gửi các chuỗi truy vấn đơn giản tới cơ sở dữ liệu ngay cả khi mã của bạn trông giống như đang tạo các câu lệnh đã chuẩn bị sẵn và cài đặt tham số, v.v. Nói cách khác, bạn vẫn dễ bị tấn công bởi SQL injection như trước đây. 🙂

      Cách tạo số ngẫu nhiên trong Google Trang tính

    Giải pháp rất đơn giản: đảm bảo mô phỏng này được đặt thành false.

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

    Giờ đây, tập lệnh PHP buộc phải sử dụng các câu lệnh đã chuẩn bị ở cấp độ cơ sở dữ liệu, ngăn chặn tất cả các loại SQL injection.

    Ngăn chặn sử dụng WAF

    Bạn có biết bạn cũng có thể bảo vệ các ứng dụng web khỏi SQL injection bằng cách sử dụng WAF (tường lửa ứng dụng web) không?

    Chà, không chỉ SQL injection mà còn nhiều lỗ hổng lớp 7 khác như cross-site scripting, hỏng xác thực, giả mạo cross-site, lộ dữ liệu, v.v. Bạn có thể sử dụng tự lưu trữ như Mod Security hoặc dựa trên đám mây như sau.

    SQL injection và các framework PHP hiện đại

    Việc tiêm SQL quá phổ biến, quá dễ dàng, quá khó chịu và nguy hiểm đến mức tất cả các khung web PHP hiện đại đều được tích hợp sẵn các biện pháp đối phó. Ví dụ: trong WordPress, chúng tôi có hàm $wpdb->prepare(), trong khi nếu bạn đang sử dụng khung MVC, nó sẽ thực hiện tất cả công việc bẩn thỉu cho bạn và bạn thậm chí không phải nghĩ đến việc ngăn chặn SQL injection. Có một chút khó chịu khi trong WordPress bạn phải chuẩn bị các câu lệnh một cách rõ ràng, nhưng này, chúng ta đang nói về WordPress. 🙂

    Dù sao, quan điểm của tôi là, các nhà phát triển web hiện đại không cần phải nghĩ đến việc tiêm SQL và kết quả là họ thậm chí không nhận thức được khả năng đó. Do đó, ngay cả khi họ để mở một cửa hậu trong ứng dụng của mình (có thể đó là tham số truy vấn $_GET và thói quen cũ kích hoạt truy vấn bẩn), kết quả có thể rất thảm khốc. Vì vậy, tốt hơn hết là dành thời gian để tìm hiểu sâu hơn về nền tảng.

    Sự kết luận

    SQL Injection là một cuộc tấn công rất khó chịu vào một ứng dụng web nhưng dễ dàng tránh được. Như chúng ta đã thấy trong bài viết này, hãy cẩn thận khi xử lý đầu vào của người dùng (nhân tiện, SQL Injection không phải là mối đe dọa duy nhất mà việc xử lý đầu vào của người dùng mang lại) và truy vấn cơ sở dữ liệu là tất cả những gì cần làm. Điều đó nói rằng, chúng tôi không phải lúc nào cũng làm việc trong lĩnh vực bảo mật của khung web, vì vậy tốt hơn hết là bạn nên biết về kiểu tấn công này và không mắc phải nó.

    x