III. Phân tích, khai thác và ngăn chặn lỗ hổng CSRF với anti-CSRF token (tiếp)
4. Tấn công CSRF khi token không liên kết duy nhất với tài khoản
Về nguyên tắc, giá trị tham số CSRF token cần mang tính duy nhất và liên kết duy nhất với tài khoản. Một số chức năng thực tế có thể vi phạm nguyên tắc này, chẳng hạn, server lưu trữ session toàn bộ người dùng trong database để dễ quản lý (hoặc mục đích nào đó), trong đó đồng thời lưu trữ CSRF token, quá trình kiểm tra CSRF token sẽ tìm kiếm trong database sự tồn tại của nó.
Cách hoạt động này dẫn đến kẻ tấn công có thể tạo một tải khoản phụ và lưu trữ CSRF token của nó (token chưa được sử dụng), dùng chúng trong mục đích tấn công CSRF. Các CSRF token này đã được lưu trữ trong database của server (do chưa sử dụng nên chưa bị loại bỏ) nên sẽ là các token hợp lệ.
Về phương thức tấn công CSRF này bạn đọc có thể luyện tập trong bài lab CSRF where token is not tied to user session.
5. Một số hướng tấn công CSRF token khác
Một số trang web thực hiện kiểm tra CSRF token đính kèm trong token với giá trị token lấy từ một giá trị khác trong Cookie mà không phải Session. Chẳng hạn các ứng dụng web sử dụng nhiều framework khác nhau có thể xảy ra tình trạng này. Một framework dành cho quá trình xử lý Session, một framework khác dành cho cơ chế bảo vệ tấn công CSRF. Ví dụ đoạn code Python sau sử dụng thư viện Flask để xử lý session người dùng, và thư viện Django cho cơ chế bảo vệ tấn công CSRF.
# Flask for session handling
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'secret_key' @app.route('/')
def index(): if 'username' in session: return 'Logged in as %s' % session['username'] return 'You are not logged in' @app.route('/login', methods=['GET', 'POST'])
def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form action="" method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' # Django CSRF for protection
from django.views.decorators.csrf import csrf_protect @csrf_protect
def some_view(request): # handle request here # ...
Ví dụ một request thực hiện chức năng thay đổi email có dạng như sau:
POST /change_email HTTP/1.1
Host: vulnerable-CSRF-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=b0JYSScWMpmC605pFOab9ixuFuYg4XWF; csrfKey=B7HC1nSzEDp8tzagGoSY6yqJnqTz5mdv csrf=RhVyQDLOxcq9jL6EaD2fWVb4muFqOqtY&email=hacked@gmail.com
Kẻ tấn công có thể sử dụng tài khoản cá nhân của họ để sinh các giá trị csrf
và csrfKey
. Sử dụng cặp token này thực hiện tấn công CSRF tới nạn nhân. Bạn đọc có thể luyện tập thêm trong bài lab CSRF where token is tied to non-session cookie.
Một biến thể khác của các cơ chế ngăn chặn tấn công CSRF không lưu trữ token trong Session mà trực tiếp đặt trong Cookie, sau đó chỉ cần kiểm tra giá trị CSRF token có trùng với giá trị token trong Cookie hay không. Cơ chế hoạt động đơn giản này còn được gọi là "double submit". Ví dụ đoạn code Python như sau:
from flask import Flask, request app = Flask(__name__) @app.route("/")
def index(): token = generate_token() response = make_response(render_template("index.html", token=token)) response.set_cookie("token", token) return response @app.route("/submit", methods=["POST"])
def submit(): cookie_token = request.cookies.get("token") form_token = request.form.get("token") if cookie_token != form_token: return "Invalid Token", 400 # process form data return "Form processed successfully" def generate_token(): # generate a unique token return "abcdefghijklmnopqrstuvwxyz123456"
Như vậy, để tạo kịch bản khai thác lỗ hổng CSRF thành công trong trường hợp này kẻ tấn công cần tìm cách thay đổi giá trị Cookie của nạn nhân. Chẳng hạn lợi dụng một dạng lỗ hổng khác để thiết lập Cookie nạn nhân. Khi đó request có dạng như sau:
POST /email/change HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Cookie: session=1DQGdzYbOJQzLP7460tfyiv3do7MjyPw; csrf=J64Gbf8hGld7G4374GlfajhgVD7F0hjdg csrf=J64Gbf8hGld7G4374GlfajhgVD7F0hjdg&email=hacked@gmail.com
Chúng ta cùng phân tích bài lab CSRF where token is duplicated in cookie để hiểu rõ phương thức tấn công này.
Quan sát request chức năng thay đổi email trong Burp Suite:
Nhận thấy rằng hệ thống không tạo và lưu CSRF token trong Session mà đóng vai trò một tham số riêng trong Cookie. Ngoài ra giá trị csrf
trong Cookie trùng với tham số csrf
truyền bằng phương thức POST. Chứng tỏ hệ thống sử dụng cơ chế kiểm tra sự trùng lặp của hai giá trị này. Dễ dàng kiếm tra điều đó:
Ngoài ra, cơ chế chỉ kiểm tra sự trùng lặp của hai giá trị token, mà không kiểm tra tính hợp lệ của token, chúng ta có thể thay đổi tùy ý, chỉ cần hai token như nhau:
Điều khó khăn ở đây là làm sao chúng ta có thể thay đổi giá trị csrf
trong Cookie tùy ý. Chú ý chức năng tìm kiếm, quan sát request nhận thấy trang web thêm chuỗi tìm kiếm cuối cùng của người dùng vào tham số LastSearchTerm
trong Cookie:
Một cách tự nhiên, đây là vị trí chúng ta có thể lợi dụng nhằm thay đổi các tham số trong Cookie, cụ thể là sử dụng phương thức tấn công CRLF. Payload như sau:
abc%0D%0ASet-Cookie:LastSearchTerm=any-value
Hiện thị response trong browser, chúng ta đã set cookie thành công:
Với ý tưởng như trên, sử dụng tính năng Generate CSRF PoC, tạo payload với tùy chonja auto submit:
Cần tìm cách để Set cookie cho nạn nhân. Sử dụng thẻ <img>
với thuộc tính src
gọi chức năng Search để set cookie, thuộc tính onerror
kích hoạt auto submit. Lưu ý cần thêm giá trị SameSite=None
cho Cookie để bypass cơ chế kiểm tra samesite của trang web:
<img src="https://LAB-ID.web-security-academy.net/?search=abc%0D%0ASet-Cookie:csrf=123%3b%20SameSite=None" onerror="document.forms[0].submit();"/>
Cuối cùng chỉ cần gửi payload tới nạn nhân để hoàn thành bài lab.
IV. Thêm token vào thuộc tính tự định nghĩa trong header HTTP
Biện pháp phòng ngừa này cũng sử dụng cách xác thực token. Khác với phương pháp phía trên, token được đặt trong các thuộc tính tự định nghĩa trong header HTTP. Sử dụng phương thức XMLHttpRequest, có thể thêm token vào header X-CSRF-Token với các request. Ví dụ đoạn code như sau:
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://example.com/api/submit", true); // Set the custom header with the CSRF token value
xhr.setRequestHeader("X-CSRF-Token", csrfTokenValue); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // Handle the response here }
}; xhr.send();
Phương pháp này tránh được các bất tiện trong việc thêm token vào request dưới dạng tham số, đồng thời các địa chỉ truy cập không bị tiết lộ được qua header Referer đến các website khác.
Tuy nhiên, phương pháp này có một số hạn chế lớn cần giải quyết. Request XMLHttpRequest thường được sử dụng trong phương thức Ajax để tạo mới một phần của website một cách không đồng bộ. Không phải tất cả các request đều phù hợp để sử dụng phương thức này. Đối với các trang web đã được hoàn thiện, việc chỉnh sửa các request nhằm sử dụng phương thức XMLHttpRequest hầu như cần xây dựng lại toàn bộ chương trình trang web, hao tốn rất lớn kinh phí, thời gian, sức lực.
Các tài liệu tham khảo
- https://portswigger.net/web-security/csrf
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CSRF%20Injection
- https://book.hacktricks.xyz/pentesting-web/csrf-cross-site-request-forgery
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CRLF%20Injection
- https://owasp.org/www-community/vulnerabilities/CRLF_Injection
- https://www.w3schools.com/xml/xml_http.asp