I. Đặt vấn đề
1. Khái niệm lỗ hổng Cross-site request forgery (CSRF)
Lỗ hổng CSRF (Cross-site request forgery) còn được biết tới với tên One Click Attack hoặc Session Riding. Lỗ hổng cho phép kẻ tấn công đánh lừa browser của nạn nhân truy cập tới một endpoint (tại đây trang web đã xác thực danh tính nạn nhân) thực hiện các thao tác với mục đích xấu như: thay đổi địa chỉ email, mua bán sản phẩm, chuyển khoản online, ... Server sẽ cho rằng đây là các yêu cầu từ chủ nhân của tài khoản nạn nhân do đã xác thực danh tính từ trước.
2. Nguyên lý hoạt động và phương thức tấn công của CSRF
Trước hết chúng ta cùng xem xét ví dụ quá trình một cuộc tấn công CSRF xảy ra:
- Kẻ tấn công submit nội dung chứa mã độc bằng javascript vào ứng dụng web chứa lỗ hổng CSRF.
- Nạn nhân đăng nhập vào ứng dụng web. Trang web sinh ra một SessionID, trả về và được lưu trữ trong browser nạn nhân.
- Khi nạn nhân truy cập tới endpoint chứa mã độc, mã độc tự động thực thi trên browser nạn nhân, sử dụng giá trị SessionID trong browser đó.
- Ứng dụng web kiểm tra giá trị SessionID này và trả về hợp lệ, dẫn đến tài khoản nạn nhân thực hiện các thao tác không mong muốn như gửi session cho kẻ tấn công, chuyển khoản, thay đổi thông tin cá nhân, ...
Xét về phía nạn nhân, từ quá trình trên, chúng ta nhận thấy có hai điều kiện cần thỏa mãn để kẻ tấn công có thể thực hiện một cuộc tấn công CSRF:
- Nạn nhân đăng nhập trên ứng dụng chứa lỗ hổng CSRF và cookie, session lưu trữ trên browser client.
- Trong tình trạng tài khoản chưa đăng xuất (session còn hoạt động), nạn nhân truy cập endpoint chứa mã độc.
Do kẻ tấn công cần tạo trước một kịch bản thực hiện các hành vi sau khi "giả mạo" được nạn nhân. Nên quá trình tấn công CSRF còn chứa một điều kiện mấu chốt:
- Các request kẻ tấn công mong muốn thực hiện với vai trò nạn nhân gửi đến server không được phép chứa các tham số, giá trị không thể đoán trước. (Sẽ giải thích rõ hơn trong phần sau)
II. Làm quen với lỗ hổng CSRF
1. Lỗ hổng CSRF với method GET
Dạng tấn công CSRF này khá đơn giản do chỉ cần một request với method GET cùng các tham số đã biết trước. Xem xét trang web có chức năng chuyển tiền sau:
<!DOCTYPE html>
<html>
<head> <title>Transfer Money</title>
</head>
<body> <h1>Transfer Money</h1> <form action="http://www.mybank.com/Transfer.php"> <label>To Bank ID:</label> <input type="text" name="toBankId"> <br><br> <label>Amount:</label> <input type="text" name="money"> <br><br> <input type="submit" value="Transfer"> </form>
</body>
</html>
Khi người dùng nhập Id ngân hàng đích, số tiền cần chuyển và chọn Transfer, trang web tạo request với method GET tới URL https://vul-bank.com/transfer.php?toBankId=123456&amount=1000
.
Quá trình thực hiện giao dịch đơn giản này chứa lỗ hổng CSRF. Thật vậy, do yêu cầu chuyển tiền sử dụng method GET cùng với hai tham số toBankId
và amount
mang tính chất "có thể đoán trước được" - kẻ tấn công tự thực hiện chuyển tiền và dễ dàng phát hiện hai tham số mấu chốt này. Bởi vậy, khi nạn nhân đã đăng nhập vào ứng dụng chuyển tiền, kẻ tấn công cần tìm cách khiến nạn nhân truy cập vào đường dẫn URL http://vul-bank.com/transfer.php?toBankId=<span style="color:red">id-bank-attacker</span>&amount=<span style="color:red">stolen-money-amount</span> với giá trị các tham số toBankId
và amount
được tạo trước. Khi đó ứng dụng web xác nhận session nạn nhân hợp lệ và tiếp tục thực hiện giao dịch chuyển tiền.
Với hướng tấn công yêu cầu tương tác từ nạn nhân, kẻ tấn công có thể sử dụng kỹ thuật phishing, chẳng hạn sử dụng thẻ <a>
cùng thuộc tính href
để chuyển hướng nạn nhân tới đường link tấn công CSRF:
<a href="https://vul-bank.com/transfer.php?toBankId=123456&amount=1000">Click Me</a>
Ngoài ra, nếu trang web chứa lỗ hổng Stored XSS (Bạn đọc có thể xem thêm tại chuỗi bài viết về lỗ hổng XSS), kẻ tấn công có thể lưu trữ mã độc javascript trong để tất cả người dùng truy cập tới trang web chứa script sẽ chuyển hướng tới chức năng chứa lỗ hổng CSRF, chẳng hạn:
<img src="https://vul-bank.com/transfer.php?toBankId=123456&amount=1000">
Do nguy cơ bị tấn công CSRF thường rất cao khi các chức năng sử dụng method GET thực hiện nên chúng ta không nên cài đặt như ví dụ trên với các chức năng quan trọng của ứng dụng web.
2. Chức năng cho phép GET và POST dẫn tới lỗ hổng CSRF
Một cách dễ dàng tạo ra một cuộc tấn công CSRF là sử dụng các thẻ HTML như <img>
, <script>
, <iframe>
với thuộc tính src
. Chúng khiến browser nạn nhân chuyển hướng tới các endpoint chứa lỗ hổng và kẻ tấn công đạt được mục đích. Một đặc điểm chung của các thẻ này là request tới server bằng phương thức GET. Bởi vậy, ý tưởng đầu tiên thường sử dụng để ngăn chặn tấn công CSRF là yêu cầu các chức năng sử dụng phương thức POST.
Xét chức năng cho phép người dùng thay đổi email như sau:
Để ngăn chặn tấn công CSRF nên chức năng yêu cầu sử dụng phương thức POST khi người dùng submit email mới:
<!DOCTYPE html>
<html>
<head> <title>Change Email</title>
</head>
<body> <h1>Change Email</h1> <form action="change_email.php" method="post"> <label for="new_email">New Email:</label> <input type="email" id="new_email" name="new_email" required> <br><br> <input type="submit" value="Change Email"> </form>
</body>
</html>
Tuy nhiên, tại change_email.php
, đoạn code sử dụng $_REQUEST
để lấy giá trị email mới.
<?php $new_email = $_REQUEST['new_email']; // Change the email address in the database // (database connection and query omitted for simplicity) echo "<script> alert('Email address changed successfully to $new_email'); window.location.href = 'index.php'; </script>";
?>
Chú ý rằng $_REQUEST
chấp nhận cả hai phương thức GET và POST, thật vậy, chúng ta chỉ cần thay đổi method, tham số new_email
vẫn nhận giá trị email mới:
Dẫn đến kẻ tấn công vẫn có thể dễ dàng sử dụng các request với method GET nhằm tấn công CSRF. Bạn đọc có thể thực hành cách tấn công này trong bài lab CSRF where token validation depends on request method
Để ngăn chặn phương thức tấn công như trên, chúng ta cần quy định chặt chẽ về các phương thức hợp lệ của từng chức năng. Trong trường hợp trên chỉ cho phép method POST.
- Cách sửa : Thêm một phương kiểm tra method của chức năng có phải POST hay không.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') { $new_email = $_REQUEST['new_email']; // Change the email address in the database // (database connection and query omitted for simplicity) // do something ...
} else { echo "<script> alert('Invalid method!'); </script>";
}
?>
- Cách sửa : Không sử dụng
$_REQUEST
, sử dụng$_POST
.
<?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 { // ... }
Hiện tại, chức năng thay đổi email chỉ cho phép sử dụng method POST, tuy nhiên vẫn ẩn chức nguy cơ tấn công CSRF. Chúng ta sẽ tìm hiểu cách khai thác cụ thể trong phần sau.
Các tài liệu tham khảo
- https://portswigger.net/web-security/csrf
- https://cystack.net/vi/blog/phishing-la-gi
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CSRF%20Injection#html-get---requiring-user-interaction
- https://book.hacktricks.xyz/pentesting-web/csrf-cross-site-request-forgery
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer
- https://portswigger.net/web-security/csrf/xss-vs-csrf