+2

Cross-site request forgery vulnerabilities (CSRF) - Các lỗ hổng CSRF (phần 2)

III. Phân tích, khai thác và ngăn chặn lỗ hổng CSRF với anti-CSRF token

1. Tạo payload tấn công CSRF

Chúng ta có thể tạo thủ công payload tấn công CSRF là một HTML form, kết hợp javascript tự động submit. Tuy nhiên, với các bạn sử dụng công cụ Burp Suite Professional có thể tận dụng trình tạo CSRF PoC tự động được tích hợp sẵn. Chúng ta cùng phân tích bài lab CSRF vulnerability with no defenses để hiểu rõ quá trình tạo payload thực hiện một cuộc tấn công CSRF.

Sau khi đăng nhập với tài khoản wiener:peter, trang web chứa chức năng thay đổi email người dùng. Quan sát request trong Burp Suite:

image.png

Chức năng thay đổi email sử dụng phương thức POST và không có bất kỳ cơ chế nào ngăn chặn tấn công CSRF. Quan sát request này nhận thấy quá trình thay đổi email của chức năng xác định duy nhất qua tham số email truyền bằng phương thức POST (giá trị session trong cookie có sẵn trong browser nạn nhân). Chúng ta cần tìm cách khiến nạn nhân cũng thực hiện request POST này với giá trị tham số email được đặt trước.

Sử dụng chức năng Generate CSRF PoC của công cụ Burp Suite:

image.png

Thêm tính năng auto-submit trong tùy chọn Options rồi click Regenerate. Giá trị email có thể thay đổi tùy ý trong trường value (Với các bạn không sử dụng Burp Suite bản Professional có thể code lại form này). Nên thử nghiệm trước payload bằng cách chọn Test in browser, khi dán link vào browser thì chúng ta đang đóng vai trò là nạn nhân. Email đã được thay đổi thành công:

image.png

Cuối cùng chỉ cần gửi payload cho nạn nhân.

2. Anti-CSRF token

Lý do quá trình tấn công CSRF có thể thành công là do kẻ tấn công có thể giả mạo hoàn toàn yêu cầu của người dùng, trong đó tất cả thông tin xác thực người dùng đều được chứa trong cookie, vì vậy kẻ tấn có thể trực tiếp sử dụng cookie của chính nạn nhân mà không cần biết các giá trị đó nhằm vượt qua cơ chế xác minh bảo mật của ứng dụng.

Như vậy, một ý tưởng tự nhiên nhằm chống lại tấn công CSRF là tạo ra một yếu tố xác thực đi kèm trong yêu cầu mà kẻ tấn công không thể giả mạo, đồng thời yếu tố xác thực này không được phép lưu trong cookie - thường được gọi là CSRF token.

Chúng ta có thể tạo ra một cơ chế sinh ngẫu nhiên CSRF token này và lưu trữ trong Session sau khi người dùng đăng nhập. CSRF token thường là một đoạn mã ký tự được sinh ngẫu nhiên, ví dụ:

$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

Trong mỗi request gửi tới server sẽ đính kèm giá trị token này, ở phía server chứa một cơ chế xác thực. Trong mỗi request sẽ lấy giá trị token từ Session, so sánh với tham số CSRF token gửi kèm trong request. Nếu tham số token không tồn tại hoặc giá trị sai sẽ loại bỏ request và đưa ra cảnh báo tới người dùng. Tất nhiên, sau khi xác thực token cần loại bỏ chúng luôn nhằm đảm bảo tính duy nhất.

// Check that the CSRF token in the request matches the one in the session
if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
    // Do something ...

    // Unset the CSRF token so it can't be used again
    unset($_SESSION['csrf_token']);
}

Vậy thì, vấn đề chúng ta đang cần giải quyết là làm sao để "đính kèm" token này trong request? Các bạn có thể dừng lại suy nghĩ một chút trước khi đọc tiếp nhé.

Đối với các request sử dụng phương thức GET, chúng ta sẽ gán token dưới dạng một tham số theo sau URL:

https://some-website.com?csrftoken=tokenvalue

Đối với các request sử dụng phương thức POST sẽ khó khăn hơn một chút. Thông thường chúng ta sẽ thêm một thẻ <input> vào cuối các form (lưu ý token sẽ ở dạng hidden). Như vậy CSRF token sẽ được thêm vào request dưới hình thức một tham số.

<input type="hidden" name="csrftoken" value="csrf-token-value"

image.png

Tuy nhiên, trong các website thường có rất nhiều chức năng, việc thêm thủ công các thẻ <input> để đính kèm CSRF token sẽ mang lại rắc rối do rất dễ bỏ sót! Hiện tại các bạn lập trình viên thường sử dụng Javascript duyệt qua toàn bộ cây DOM, thêm token vào sau tất cả các thẻ chứa nguy cơ lỗ hổng CSRF như <a>, <form>, ... Ví dụ chương trình như sau:

// Lấy tất cả các thẻ a và form trong cây DOM
var aTags = document.getElementsByTagName("a");
var formTags = document.getElementsByTagName("form");

// Tạo token mới
var csrfToken = "Token_" + Math.random().toString(36).substring(2);

// Thêm token vào sau các thẻ a và form
for (var i = 0; i < aTags.length; i++) {
    aTags[i].href = aTags[i].href + "?csrf_token=" + csrfToken;
}
for (var i = 0; i < formTags.length; i++) {
    var input = document.createElement("input");
    input.type = "hidden";
    input.name = "csrf_token";
    input.value = csrfToken;
    formTags[i].appendChild(input);
}

Cách làm này có thể giải quyết ngăn chặn tấn công ở hầu hết các request, nhưng đối với các đoạn code html được sinh dynamic sau khi trang được load sẽ không mang lại tác dụng, vẫn cần các bạn lập trình viên phải thêm thủ công.

Ngoài ra, một nhược điểm khác của phương pháp này là việc đảm bảo "sự an toàn của bản thân các CSRF token". Ví dụ đối với các trang web cho phép người dùng đăng tải các đường link, website bên ngoài. Do hệ thống cũng sẽ tự động thêm giá trị CSRF token sau các đường link này, nên kẻ tấn công có thể thu được các CSRF token trên website của họ, sử dụng chúng thực hiện tấn công CSRF. Để giải quyết vấn đề này, chúng ta nên thêm một bước kiểm tra: Nếu các đường link có đích đến trong phạm vi an toàn sẽ thêm token, ngược lại không gán thêm token. Có vẻ đã an toàn rồi nhỉ ... Nhưng bạn có thử nghĩ rằng kẻ tấn công có thể dùng các kỹ thuật khác nhằm vượt quá bước kiểm tra trên để đánh cắp giá trị token, chẳng hạn như header Referer! (Xin dành cho bạn đọc tìm hiểu)

Việc sử dụng token có thể ngăn chặn hầu hết các cuộc tấn công CSRF. Tuy nhiên quá trình cài đặt có thể có nhiều thiếu sót, dẫn đến kẻ tấn công vẫn có thể lợi dụng các sơ hở nhằm thực hiện các cuộc tấn công đặc biệt trong từng trường hợp. Chúng ta sẽ phân tích kỹ hơn các dạng tấn công trong các mục tiếp theo.

3. Bỏ qua tham số csrf-token trong request tấn công

Lỗi xảy ra khi đoạn chương trình thực hiện chức năng không hoàn toàn yêu cầu tham số CSRF token cần có mặt trong request. Phần lớn trách nhiệm thuộc về phía lập trình viên. Tham số CSRF token được cài đặt hidden trong browser và sẽ được đính kèm trong request thực hiện chức năng. Nên có thể lập trình viên đã tin tưởng và mắc một số lỗi trong quá trình xây dựng chức năng:

  • Trường hợp 11: Không kiểm tra sự tồn tại và tính hợp lệ của CSRF token.
<?php
    if (isset($_POST['new_email'])) {
        $new_email = $_POST['new_email'];
        // Change the email address in the database
        // (database connection and query omitted for simplicity)
        // do something ...
    } else {
        // ...
    }
  • Trường hợp 22: Chỉ kiểm tra sự tồn tại nhưng không kiểm tra tính hợp lệ của CSRF token.
<?php
    if (isset($_POST['new_email']) && isset($_POST['csrf_token'])) {
        $new_email = $_POST['new_email'];
        // Change the email address in the database
        // (database connection and query omitted for simplicity)
        // do something ...
    } else {
        // ...
    }

Mặc dù các trường hợp này hiếm khi xảy ra, tuy nhiên chúng ta vẫn cần lưu ý trong quá trình xây dựng các cơ chế ngăn chặn tấn công CSRF. Về phương thức bypass cơ chế ngăn chặn CSRF này bạn đọc có thể luyện tập thêm qua bài lab CSRF where token validation depends on token being present.

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


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


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.