Phân tích nguyên lý hoạt động và mã nguồn lỗ hổng XSS (Cross-Site Scripting)

Cross-Site Scripting (XSS) là một lỗ hổng bảo mật phổ biến trong các ứng dụng web, cho phép kẻ tấn công chèn các đoạn mã độc hại (thường là JavaScript) vào các trang web được người dùng tin tưởng. Khi nạn nhân truy cập vào trang bị nhiễm mã độc, trình duyệt sẽ thực thi đoạn mã này dưới quyền hạn của người dùng đó. Lỗ hổng này thường được chia thành ba loại chính dựa trên cách thức xử lý dữ liệu: Phản chiếu (Reflected), Lưu trữ (Stored), và DOM-based. Chúng ta sẽ đi sâu vào phân tích kỹ thuật của từng loại thông qua các ví dụ mã nguồn bị lỗi.

XSS Phản chiếu (Reflected XSS)

XSS phản chiếu, hay còn gọi là XSS không lưu trữ, xảy ra khi dữ liệu do người dùng cung cấp được máy chủ nhận về và ngay lập tức phản hồi lại trong trang web mà không qua bất kỳ quá trình làm sạch hay mã hóa nào.

Xét đoạn mã PHP sau mô phỏng một chức năng tìm kiếm đơn giản:

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <title>Tìm kiếm sản phẩm</title>
</head>
<body>
    <form method="GET" action="">
        <input type="text" name="tu_khoa" placeholder="Nhập từ khóa...">
        <button type="submit">Tìm kiếm</button>
    </form>
    <hr>
    <?php
    if (isset($_GET['tu_khoa'])) {
        $keyword = $_GET['tu_khoa'];
        // Lỗi: In trực tiếp dữ liệu người dùng vào HTML mà không mã hóa
        echo "<div>Kết quả tìm kiếm cho: <b>" . $keyword . "</b></div>";
    }
    ?>
</body>
</html>

Trong ví dụ trên, tham số `tu_khoa` được lấy từ URL và chèn thẳng vào thẻ ``. Kẻ tấn công có thể lợi dụng điều này để đóng thẻ `` và chèn mã độc. Ví dụ, nếu nhập payload:

<img src=x onerror=alert('Tấn công XSS')>

Đoạn mã HTML được tạo ra sẽ là:

<div>Kết quả tìm kiếm cho: <b><img src=x onerror=alert('Tấn công XSS')></b></div>

Trình duyệt sẽ cố gắng tải hình ảnh tại nguồn `x` (không tồn tại), sự kiện `onerror` được kích hoạt và thực thi hàm `alert()`, cho thấy mã JavaScript đã được thực thi thành công.

XSS Lưu trữ (Stored XSS)

XSS lưu trữ nguy hiểm hơn nhiều vì đoạn mã độc được lưu vĩnh viễn vào cơ sở dữ liệu của máy chủ (thường trong các trường bình luận, diễn đàn, hồ sơ người dùng). Mỗi khi trang web tải dữ liệu này ra hiển thị cho người dùng khác, mã độc sẽ được thực thi.

Dưới đây là ví dụ về một hệ thống bình luận thiếu cơ chế bảo vệ:

<?php
// Giả định kết nối cơ sở dữ liệu $conn đã được thiết lập
$conn = new mysqli("localhost", "db_user", "db_pass", "my_app");

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $user = $_POST['username'];
    $content = $_POST['comment'];

    // Dữ liệu được đưa vào CSDL mà không kiểm tra nội dung độc hại
    // (Chỉ chú trọng tránh lỗi SQL Injection)
    $stmt = $conn->prepare("INSERT INTO binh_luan (nguoi_dung, noi_dung) VALUES (?, ?)");
    $stmt->bind_param("ss", $user, $content);
    $stmt->execute();
}

// Hiển thị danh sách bình luận
$result = $conn->query("SELECT nguoi_dung, noi_dung FROM binh_luan ORDER BY id DESC");
?>

<div class="comment-section">
    <?php while ($row = $result->fetch_assoc()) { ?>
        <div class="comment-item">
            <strong><?php echo $row['nguoi_dung']; ?></strong>:
            <p><?php echo $row['noi_dung']; ?></p>
        </div>
    <?php } ?>
</div>

Khi dữ liệu được truy vấn từ cơ sở dữ liệu và in ra màn hình thông qua `echo`, nếu nội dung chứa thẻ HTML hay JavaScript, trình duyệt sẽ phân tích và thực thi chúng như một phần của trang web. Bất kỳ người dùng nào xem trang bình luận này đều trở thành nạn nhân, ngay cả khi họ không tương tác với form nhập liệu.

XSS dựa trên DOM (DOM-based XSS)

Loại tấn công này khác biệt ở chỗ lỗ hổng nằm hoàn toàn ở phía client (trình duyệt) dựa trên việc thao tác DOM (Document Object Model), chứ không phụ thuộc vào việc phản hồi từ máy chủ. Dữ liệu độc hại đi từ nguồn không an toàn (như URL, hash fragment) vào một sink nguy hiểm (như `innerHTML`, `eval`).

Ví dụ sau mô phỏng một trang web cập nhật thông báo trạng thái:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Trạng thái người dùng</title>
</head>
<body>
    <div id="status-display">Trạng thái: Đang rảnh</div>
    <hr>
    <input type="text" id="status-input" placeholder="Nhập trạng thái mới">
    <button onclick="capNhatTrangThai()">Cập nhật</button>

    <script>
        function capNhatTrangThai() {
            var inputVal = document.getElementById('status-input').value;
            // Lỗ hổng: Gán trực tiếp dữ liệu không tin cậy vào innerHTML
            document.getElementById('status-display').innerHTML = "Trạng thái: " + inputVal;
        }
    </script>
</body>
</html>

Trong hàm `capNhatTrangThai`, giá trị lấy từ ô input được gán trực tiếp vào thuộc tính `innerHTML`. Nếu người dùng nhập:

<img src=1 onerror=alert('DOM XSS')>

Thẻ `` sẽ được chèn vào DOM và kích hoạt alert. Điểm đặc biệt của DOM XSS là khi bạn xem "View Source" (Xem nguồn trang) của trang web, bạn sẽ không thấy đoạn mã độc này, vì nó chỉ tồn tại trong bộ nhớ trình duyệt sau khi DOM được cập nhật bởi JavaScript.

Biện pháp khắc phục và phòng chống

Để bảo vệ ứng dụng trước các tấn công XSS, các nguyên tắc sau cần được tuân thủ nghiêm ngặt:

  • Mã hóa đầu ra (Output Encoding): Đây là biện pháp quan trọng nhất. Khi hiển thị dữ liệu do người dùng cung cấp ra trình duyệt, hãy luôn chuyển đổi các ký tự đặc biệt sang dạng HTML Entity (ví dụ: chuyển `<` thành `<`, `>` thành `>`, `"` thành `"`). Trong PHP, có thể sử dụng hàm `htmlspecialchars()`.
  • Sử dụng các API an toàn trong JavaScript: Tránh sử dụng `innerHTML` hoặc `eval()` với dữ liệu chưa kiểm tra. Thay vào đó, hãy dùng `innerText`, `textContent` hoặc các phương thức DOM an toàn khác.
  • Làm sạch đầu vào (Input Validation): Hạn chế loại dữ liệu mà người dùng được phép nhập (ví dụ: chỉ cho phép chữ cái, số trong trường tên người dùng). Tuy nhiên, việc lọc đầu vào không nên là biện pháp duy nhất vì mã độc có nhiều hình thức biến thể khó lọc hết.
  • Sử dụng Content Security Policy (CSP): Cấu hình header CSP để giới hạn nguồn gốc mà trình duyệt được phép tải tài nguyên (script, image, style), từ đó ngăn chặn việc thực thi script từ nguồn không rõ ràng.

Thẻ: web-security XSS php JavaScript DOM

Đăng vào ngày 22 tháng 6 lúc 20:48