post-image

Tìm hiểu về kỹ thuật tấn công CSRF và cách xử lý trong Spring Boot

Spring Boot Starters & Common Configurations Tổng quan

Thế nào là kỹ thuật tấn công CSRF?

Nói tóm gọn, đây là kỹ thuật thông qua việc người dùng đồng thời tương tác với nhiều website, một trang web xấu nào đấy sẽ lợi dụng việc bạn đang trong 1 session mà chưa đăng xuất để send một request ẩn với mục đích xấu. Ví dụ sau đây sẽ giúp bạn dễ hình dung…

Một ngân hàng nọ có một form để chuyển tiền từ tài khoản đang đăng nhập tới một tài khoản ngân hàng khác:

POST /transfer HTTP/1.1 Host: bank.example.com Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876

Trên website có ý đồ xấu, sẽ tồn tại một form ẩn có nội dung đại khái như sau:

<form action="https://bank.example.com/transfer" method="post">
<input type="hidden"
	name="amount"
	value="100.00"/>
<input type="hidden"
	name="routingNumber"
	value="evilsRoutingNumber"/>
<input type="hidden"
	name="account"
	value="evilsAccountNumber"/>
<input type="submit"
	value="Win Money!"/>
</form>Code language: HTML, XML (xml)

Khi người dùng bấm vào một button bất kỳ(vô ý hay chủ ý), hoặc đơn giản thông qua một event js nào đấy, lệnh submit này sẽ vô tình được kích hoạt. Và do đó, 100$ cứ thế theo gió vô tình biến mất khỏi tài khoản…

Cách xử lý

Một cách để giải quyết vấn đề trên, đó là sử dụng một loại token để xác thực. Khi một request được submit, server phải kiểm tra token này, không đúng => fail.

Bằng cách tránh việc lồng random token vào HTTP GET, ta có thể ngăn việc token bị lộ ra ngoài. Request đại khái sẽ thay đổi như sau:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=<secure-random>Code language: HTTP (http)

Config với spring

Điều đầu tiên là bạn phải sử dụng giao thức HTTP, và như đã đề cập ở trên, ta phải hạn chế sử dụng HTTP GET vì nó có thể làm leak thông tin. Ở Spring Security 4.0, có thể được disable thông qua XML:

<http>
	<!-- ... -->
	<csrf disabled="true"/>
</http>Code language: HTML, XML (xml)

hoặc thông qua class configuration:

@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.csrf().disable();
	}
}Code language: PHP (php)

Gán các biến param của Csrf trong form(Spring Security đã cung cấp một class sẵn, bạn chỉ cần apply thôi). Và thường thì 1 form sẽ có cấu trúc như này:

<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
	method="post">
<input type="submit"
	value="Log out" />
<input type="hidden"
	name="${_csrf.parameterName}"
	value="${_csrf.token}"/>
</form>Code language: HTML, XML (xml)
package org.springframework.security.web.csrf;

import java.io.Serializable;

/**
 * Provides the information about an expected CSRF token.
 *
 * @see DefaultCsrfToken
 *
 * @author Rob Winch
 * @since 3.2
 *
 */
public interface CsrfToken extends Serializable {Code language: PHP (php)

Một cách khác là khi ta đã sử dụng annotation @EnableWebSecurity và xài tag form:form do Spring MVC cung cấp(hoặc các tag do Thymleaf cung cấp, các thẻ input trong form sẽ được auto render nên sẽ hạn chế được việc tấn công Csrf) => CsrfToken sẽ tự động được lồng vào form.

Về Ajax và JSON Requests.

Không thể submit Csrf token thông qua HTTP parameter với việc sử dụng JSON, do đó bạn có thể submit token bên trong HTTP header:

<html>
<head>
	<meta name="_csrf" content="${_csrf.token}"/>
	<!-- default header name is X-CSRF-TOKEN -->
	<meta name="_csrf_header" content="${_csrf.headerName}"/>
	<!-- ... -->
</head>
<!-- ... -->Code language: HTML, XML (xml)

Và khi submit request bên JS, ta xử lý như sau:

$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
	xhr.setRequestHeader(header, token);
});
});Code language: JavaScript (javascript)

Set Csrf token dưới Cookie

Sẽ có trường hợp ta muốn set token dưới cookie. Mặc định CookieCsrfTokenRepository sẽ set một cookie với name XSRF-TOKEN và get nó lên từ header có name X-XSRF-TOKEN hoặc parameter có name _csrf.

Trường hợp config bằng XML:

<http>
	<!-- ... -->
	<csrf token-repository-ref="tokenRepository"/>
</http>
<b:bean id="tokenRepository"
	class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
	p:cookieHttpOnly="false"/>Code language: HTML, XML (xml)

Hoặc config configuration bean:

@EnableWebSecurity
public class WebSecurityConfig extends
		WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.csrf()
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
	}
}Code language: CSS (css)

Một số vấn đề liên quan.

Timeouts

Csrf token sẽ được lưu trữ trong HttpSession, vậy nên nếu session hiện tại của bạn đã expired thì dĩ nhiên token đó khi submit lên sẽ được tính là invalid. Đồng thời, nếu sử dụng default filter AccessDeniedHandler thì đối với invalid token/request gửi đến thì browser sẽ nhận về trang 403.

Việc set token dưới cookie không được commend bởi vì các website có ý đồ xấu sẽ có thể lấy được dữ liệu từ cookie.

Về lý thuyết, ta đã hiểu được định nghĩa, cách thức hoạt động, cách config cơ bản về vấn đề Csrf. Nhưng vì vấn đề document chỉ hướng cho ta đến bề nổi, đơn giản nhất về Csrf, đến đây mình vẫn chưa xác định được cách bên Server side sẽ valid được Csrf token như thế nào. Hoặc Spring Boot sẽ đảm nhiệm luôn valid từng request, generate random token luôn để tối giản luôn việc config dùm developer.

Bài viết được tham khảo từ nguồn: 

https://viblo.asia/p/tim-hieu-ve-ky-thuat-tan-cong-csrf-va-cach-xu-ly-trong-spring-boot-gDVK2jPwKLj

https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html

Leave a Reply

Your email address will not be published. Required fields are marked *