Lập trình an toàn: học được gì từ cách Hacker vượt qua xác thực CSRF token
1. Lỗ hổng CSRF
Dễ tìm ra, dễ khai thác, có thể tạo ra hậu quả nặng nề tới người dùng. Tuỳ vào nơi tồn tại lỗ hổng mà người dùng nhẹ thì có thể bị chỉnh sửa một số dữ liệu không mấy quan trọng; nặng thì có thể bị mất tiền, mất tài khoản,...
Để thực hiện tấn công CSRF, đầu tiên hacker sẽ tạo một trang web A giả mạo trang web B (trang web B tồn tại lỗ hổng CSRF), trên trang web A này có một đoạn mã HTML. Đoạn mã này sẽ gửi yêu cầu đến máy chủ của trang web B, yêu cầu thực hiện một thao tác nào đó, ví dụ như chuyển tiền đến số tài khoản của hacker chẳng hạn. Tiếp theo hacker sẽ dùng nhiều con đường để gửi liên kết tới trang web A cho người dùng. Nếu người dùng mở đường liên kết này thì đoạn mã HTML sẽ được kích hoạt để gửi yêu cầu tới máy chủ của trang web B. Trong trường hợp người dùng chưa đăng nhập, thậm chí chưa có tài khoản ở trang web B thì sẽ không sao cả. Nhưng nếu người dùng đã đăng nhập vào trang web B rồi thì trình duyệt sẽ gửi kèm thông tin định danh phiên của người dùng tới máy chủ của trang web B, và yêu cầu sẽ được xử lý. Lúc này thì tiền trong tài khoản của người dùng liền không cánh mà bay.
Tuy để tấn công người dùng trong thực tế không mấy đơn giản, nhưng với những hậu quả lớn có thể xảy đến nếu lỗ hổng này được sử dụng thành công, các nhà cung cấp dịch vụ trên nền tảng số sẵn sàng trả những khoản tiền thưởng thích hợp cho những người phát hiện ra lỗ hổng này và báo cáo lại. Vì thế mà CSRF là một lỗ hổng "dễ chơi, dễ trúng thưởng" đối với các hacker và thợ săn tiền thưởng.
2. Cách phòng chống CSRF
Các framework hiện nay cũng đã hỗ trợ việc triển khai biện pháp chống tấn công CSRF một cách hiệu quả. Kể cả khi lập trình viên không hiểu gì về lỗ hổng CSRF thì họ vẫn được hướng dẫn triển khai biện pháp phòng ngừa qua tài liệu của framework. Đơn giản chỉ cần thêm một đoạn mã với tên gọi CSRF Token vào ứng dụng, và tiến hành kiểm tra nó trước khi thực hiện yêu cầu của người dùng là đã có thể ngăn ngừa tấn công CSRF rồi.
Đoạn mã CSRF thường được thêm vào các Form HTML dưới dạng một giá trị ẩn như sau:
<form name="change-email-form" action="/my-account/change-email" method="POST">
<label>Email</label>
<input required type="email" name="email" value="example@normal-website.com">
<input required type="hidden" name="csrf" value="50FaWgdOhi9M9wyna8taR1k3ODOR8d6u">
<button class='button' type='submit'> Update email </button>
</form>
Yêu cầu gửi đi từ trình duyệt sẽ trông như sau:
POST /my-account/change-email HTTP/1.1
Host: normal-website.com
Content-Length: 70
Content-Type: application/x-www-form-urlencoded
csrf=50FaWgdOhi9M9wyna8taR1k3ODOR8d6u&email=example@normal-website.com
Nhưng có thể do chưa hiểu rõ cách hoạt động của CSRF Token, cũng chưa hiểu về tấn công CSRF nên việc triển khai phòng chống CSRF trong một số trường hợp chưa thực sự hiệu quả. Lúc này hacker vẫn có thể vượt qua quá trình xác thực CSRF Token để khai thác được lỗ hổng này.
Tiếp theo hãy tìm hiểu một số trường hợp hacker có thể vượt qua xác thực CSRF Token, trong những trường hợp dưới đây có trường hợp nào như bạn không, cùng sửa nhé.
3. Một số trường hợp vượt qua xác thực CSRF Token
3.1. Việc xác thực CSRF Token được thực hiện dựa trên phương thức HTTP
Trong một số trường hợp, việc xác thực CSRF Token đã được thực hiện rất tốt trên một số phương thức HTTP, nhưng một số phương thức khác lại không thực hiện xác thực hoặc không có CSRF Token luôn. Ví dụ như toàn bộ các yêu cầu được gửi qua phương thức POST đều đính kèm CSRF Token, khi gói tin đến máy chủ thì cũng phải trải qua việc xác thực CSRF Token trước khi thực hiện yêu cầu. Nhưng với phương thức GET thì lại không được như vậy.
Có thể do ban đầu, dự án được thiết kế chỉ gửi các yêu cầu có thể thay đổi dữ liệu qua phương thức POST, còn phương thức GET sẽ chỉ được sử dụng để yêu cầu đọc dữ liệu từ máy chủ. Trong trường hợp toàn bộ mã nguồn được thực hiện theo thiết kế này thì sẽ không có lỗ hổng CSRF, vì toàn bộ yêu cầu cần sử dụng phương thức POST đều được xác thực CSRF Token một cách chính xác rồi.
Nhưng có thể xảy ra sai sót trong quá trình phát triển ứng dụng, lập trình viên đã sơ ý thực hiện thay đổi dữ liệu đối với một yêu cầu bằng phương thức GET nào đó. Hoặc dự án đã làm rất tốt, nhưng sau này khi phát triển thêm chức năng, đội ngũ lập trình viên đã có thêm người mới chưa nắm được yêu cầu thống nhất từ đầu. Dẫn đến họ chủ động tạo ra các yêu cầu chỉnh sửa dữ liệu qua thương thức GET, và cũng quên đi việc xác thực CSRF Token với phương thức GET luôn. Lúc này đây sẽ tạo ra lỗ hổng CSRF.
Vì vậy, để giảm thiểu tối đa khả năng này, chúng ta nên kiểm tra lại toàn bộ các yêu cầu thay đổi dữ liệu từ phía người dùng. Đảm bảo mọi yêu cầu thay đổi dữ liệu từ phía người dùng, dù là với phương thức gì đi chăng nữa, cũng cần phải xác thực CSRF Token đầy đủ. Đồng thời cũng có thể tuân thủ chặt chẽ các quy định thiết kế ứng dụng, ví dụ như khi sử dụng RESTful API thì cần xác thực trên mọi yêu cầu qua phương thức POST, PUT, DELETE; đồng thời không thực hiện thay đổi dữ liệu khi yêu cầu được gửi qua phương thức GET.
3.2. CSRF Token không ràng buộc với phiên của người dùng
Đây là trường hợp có thể xảy ra khi lập trình viên đã tuân thủ đúng việc xác thực CSRF Token, nhưng lại không để ý đến sự liên kết giữa CSRF Token và phiên của người dùng. Lí do mà chỉ một cái CSRF Token có thể phòng ngừa được cuộc tấn công CSRF là vì hacker không biết token đó, nên không thể đính kèm token vào trong đoạn mã HTML gửi cho người dùng. Như vậy nếu hacker có thể biết nội dung của CSRF Token là như nào mới có thể vượt qua xác thực, thì hacker có thể thực hiện tấn công CSRF.
Nếu ứng dụng chỉ sinh ra 1 loạt các CSRF Token và không quan tâm chiếc token này là của tài khoản nào, miễn là CSRF Token đính kèm trong yêu cầu từ người dùng đúng với CSRF Token do ứng dụng sinh ra thì hacker có thể vượt qua xác thực một cách dễ dàng. Chỉ cần tạo tài khoản trên trang web đó, lấy CSRF Token từ tài khoản của mình và đính kèm với mã HTML để thực hiện tấn công. Khi người dùng mở đường dẫn lên, mã HTML được thực thi sẽ gửi yêu cầu tới máy chủ dưới định danh phiên của họ và CSRF Token của hacker. Máy chủ kiểm tra thấy phiên làm việc hợp lệ, CSRF Token hợp lệ thì sẽ thực hiện thay đổi dữ liệu mà không để ý xem token có đúng là của tài khoản hay không.
Do đó, cần kiểm tra xem phiên làm việc của người dùng và CSRF Token có ràng buộc với nhau hay không? Khi kiểm tra được một CSRF Token thuộc về tài khoản nào, thì hacker không thể vượt qua xác thực bằng cách sử dụng CSRF Token hợp lệ của tài khoản khác.
3.3. CSRF Token ràng buộc với cookie, nhưng không phải mã định danh phiên của người dùng
Trường hợp này có điểm gần giống với trường hợp 3.2. Trong trường hợp này thì CSRF Token đã được ràng buộc với một cookie nhưng cookie này lại không phải mã định danh phiên của người dùng. Việc này có thể xảy ra khi ứng dụng sử dụng hai framework/thư viện khác nhau: một cho việc quản lý mã định danh phiên và một cho việc quản lý CSRF Token. Do hai thành phần này chẳng làm việc với nhau nên CSRF Token cũng không thể ràng buộc được với mã định danh phiên một cách dễ dàng. Nếu muốn thì phải tự code thôi.
Ở trường hợp này thì CSRF Token sẽ được ràng buộc với một cookie nào đấy (tạm gọi là cookie CSRF_Key). Cookie CSRF_Key sẽ được cấp và lưu tại trình duyệt của người dùng sau khi đăng nhập thành công. Và cookie CSRF_Key của các tài khoản khác nhau sẽ khác nhau luôn. Do đó không thể đem CSRF Token của tài khoản A dùng cho tài khoản B được. Khi máy chủ kiểm tra thấy cookie CSRF_Key là của tài khoản A nhưng CSRF Token lại của tài khoản B thì cũng sẽ không thực hiện yêu cầu.
Nói là an toàn thì cũng an toàn, nhưng không phải tuyệt đối. Nếu ứng dụng tồn tại một lỗ hổng cho phép hacker có thể đặt giá trị cho cookie của người dùng, thì lúc này cookie CSRF_Key của người dùng có thể bị hacker sửa đổi thành cookie CSRF_Key của hacker. Sau đó hacker chỉ cần thực hiện tấn công CSRF với CSRF Token của hacker là được.
Cuối cùng thì ràng buộc CSRF Token với mã định danh phiên vẫn an toàn hơn. Vì kể cả khi hacker có kết hợp thêm lỗ hổng khác để thay đổi được mã định danh phiên lưu trong cookie thì đâu có ý nghĩa gì? Mã định danh phiên thay đổi thì đâu còn là tài khoản đó nữa đâu. Không tìm được CSRF Token ràng buộc cùng với mã định danh phiên của người dùng thì việc tấn công CSRF là không khả thi.
3.4. CSRF Token cố định
Bạn đã từng nghe về trường hợp mã định danh phiên cố định với mọi lần đăng nhập chưa? Đúng, có trường hợp này xảy ra trong thực tế đó. Và cũng có trường hợp CSRF Token là cố định luôn. Thường thì trường hợp này xảy ra khi tính năng sinh và xác thực CSRF Token được tự phát triển. Nếu để do sánh độ nguy hiểm và khả năng bị khai thác thì trường hợp này an toàn hơn 3 trường hợp trước đó. Nhưng nếu người dùng, qua con đường nào đó bị lộ CSRF Token thì lúc này sự an toàn không còn nữa. Có thể nghĩ đến 2 kịch bản đơn giản sau đây dẫn đến việc lộ CSRF Token:
- CSRF Token được lưu trong cookie, và trang web tồn tại lỗ hổng XSS. Mã định danh phiên lưu trong cookie đã được gắn cờ HttpOnly và Secure để bảo vệ, nhưng CSRF Token thì không. Lúc này hacker có thể khai thác lỗ hổng XSS để lấy được CSRF Token của người dùng.
- CSRF Token cố định và được sinh bởi các yếu tố có thể đoán được. Ví dụ như được sinh ra nhờ encode Base64 các yếu tố dễ đoán như tên đăng nhập,...
Nhìn chung thì việc cố định CSRF Token là không đủ an toàn. CSRF Token nên được sinh mới liên tục, và mỗi CSRF Token sẽ có thời gian hiệu lực nhất định. Khi quá thời hạn thì sẽ từ chối CSRF Token và từ chối xử lý yêu cầu.
Trên đây là một số trường hợp triển khai CSRF Token chưa đủ an toàn, dẫn đến hacker có thể vượt qua quá trình xác thực để thực hiện cuộc tấn công CSRF. Các bạn có thắc mắc gì về CSRF Token hay không? Hãy để lại dưới phần bình luận nếu có nhé.
Bài viết được tham khảo từ các nguồn:
All Rights Reserved