CSRF detailed: attack, defense, Spring Security application, etc.

Introduction to CSRF

CSRF (Cross Site Request Forgery, cross-site domain request forgery) is a network attack method, which was listed as one of the top 20 Internet security risks in 2007. Other security risks, such as SQL script injection, cross-site scripting attacks, etc., have gradually become known to everyone in recent years, and many websites have also defended against them. However, for most people, CSRF is still a strange concept. Even the well-known Gmail has a CSRF vulnerability at the end of 2007, which was hacked and caused huge losses to Gmail users.

How CSRF attacks

Look at the picture first :

As can be seen from the above figure, website A uses cookies to identify users (C). When the user successfully authenticates, the browser will get a cookie that identifies his identity. As long as you do not close the browser or log out, you can visit website A later Will always carry this cookie. If the browser is controlled by someone to initiate a request to the A website to perform some functions that the user does not want to do (such as adding an account), this is session hijacking. Because this is not the request that the user really wants to make, this is the so-called "request forgery". In addition, since the request can be submitted from a third-party website, the prefix “cross-site” means that it is initiated from website B.

Take bank transfer as an example (this is an example on the Internet, a big pile...) :

A CSRF attack can forge a request and send it to the attacked site in the name of the victim without the victim's knowledge, so as to perform operations under the protection of permissions without authorization. For example, the victim Bob has a deposit in the bank. By sending a request to the bank's website,  http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 Bob can transfer the deposit of 1,000,000 to Bob2's account. Normally, after the request is sent to the website, the server will first verify whether the request comes from a legal session, and the user Bob of the session has successfully logged in. The hacker Mallory himself has an account in the bank, and he knows that the URL above can transfer money. Mallory himself may send a request to the bank: http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory. But this request comes from Mallory instead of Bob, he cannot pass the security authentication, so the request will not work. At this time, Mallory thought of using the CSRF attack method. He first made a website by himself, put the following code in the website:,  src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”and used advertisements to induce Bob to visit his website. When Bob visits the website, the above url will be sent from Bob's browser to the bank, and this request will be sent to the bank server with the cookie in Bob's browser. In most cases, the request will fail because he requires Bob's authentication information. However, if Bob happened to visit his bank shortly afterwards, and the session between his browser and the bank website had not expired, the browser’s cookie contained Bob’s authentication information. At this time, the tragedy happened, the url request would be responded, and the money would be transferred from Bob's account to Mallory's account, and Bob had no knowledge at the time. Afterwards, Bob discovered that the account had less money. Even if he went to the bank to check the log, he could only find that there was indeed a legitimate request from him to transfer the funds, without any trace of being attacked. Mallory can get away with the money.

Points to note for CSRF understanding

To understand CSRF, I think you need to understand the following questions: @pdai

Can hackers get cookies?

A CSRF attack is a hacker who uses the victim's cookie to defraud the server's trust, but the hacker cannot get the cookie or see the content of the cookie.

For the results returned by the server, hackers cannot parse it due to the browser's same-origin policy. Therefore, the hacker can't get anything from the returned results. All he can do is send a request to the server to execute the command described in the request, and directly change the value of the data on the server side instead of stealing the data from the server.

What kind of request is CSRF protected?

Why some frameworks (such as Spring Security) limit the method of CSRF protection filter to POST/PUT/DELETE, etc., but not GET Method?

The objects we want to protect are those services that can directly produce data changes, while services that read data do not need to be protected by CSRF. Generally speaking, GET should be used as request data, not as modification data, so these frameworks do not intercept Get and other methods of request. For example, a transfer request in the banking system will directly change the amount of the account, which will be attacked by CSRF and needs to be protected. While querying the balance is a reading operation of the amount, it does not change the data, the CSRF attack cannot parse the result returned by the server, and no protection is needed.

Why did CSRF intercept the request, but still report the CRSF vulnerability?

Why I have used POST+CSRF Token request on the front-end, and the back-end has also implemented CSRF Filter for POST requests, but there are still CSRF vulnerabilities in the penetration test?

Look directly at the code below.

// 这里没有限制POST Method,导致用户可以不通过POST请求提交数据。
@RequestMapping("/url")
public ReponseData saveSomething(XXParam param){
    // 数据保存操作...
}

PS: This point is easy to be overlooked. It has appeared many times in the penetration tests of several projects that the author has experienced. @pdai

CSRF defense conventional ideas

Be sure to note that the following is just to provide you with general ideas (the following text is excerpted from the response to CSRF attacks , please see the next chapter for specific implementation. @pdai

Verify the HTTP Referer field

According to the HTTP protocol, there is a field called Referer in the HTTP header, which records the source address of the HTTP request. Under normal circumstances, a request to access a secure restricted page comes from the same website. For example, to access  http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory, the user must first log in to  bank.example and then pass Click the button on the page to trigger the transfer event. At this time, the Referer value of the transfer request will be the URL of the page where the transfer button is located, usually an address beginning with the bank.example domain name. If a hacker wants to implement a CSRF attack on a bank website, he can only construct a request on his own website. When a user sends a request to the bank through the hacker's website, the Referer of the request points to the hacker's own website. Therefore, to defend against CSRF attacks, the bank website only needs to verify the Referer value for each transfer request. If it is a domain name starting with bank.example, it means that the request comes from the bank website itself and is legitimate. If Referer is another website, it may be a CSRF attack by a hacker and reject the request.

Add token to the request address and verify

The CSRF attack is successful because the hacker can completely forge the user’s request. All user authentication information in the request is in the cookie, so the hacker can directly use the user’s own cookie without knowing the authentication information. To pass the security verification. To resist CSRF, the key is to put information that hackers cannot forge in the request, and that information does not exist in the cookie. You can add a randomly generated token as a parameter to the HTTP request, and establish an interceptor on the server to verify the token. If there is no token in the request or the content of the token is incorrect, the request may be rejected because of a CSRF attack. .

This method is safer than checking Referer. The token can be generated and placed in the session after the user logs in, and then the token is taken out of the session every time a request is made, and compared with the token in the request, but this The difficulty of this method is how to add the token to the request as a parameter. For GET requests, the token will be appended to the request address, so the URL becomes  http://url?csrftoken=tokenvalue. For POST requests, add <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>it at the end of the form , so that the token is added to the request as a parameter. However, in a website, there are many places that can accept requests. It is very troublesome to add a token to each request, and it is easy to miss. The usual method is to use javascript to traverse every time the page is loaded. In the entire dom tree, add token after all a and form tags in dom. This can solve most of the requests, but for the html code dynamically generated after the page is loaded, this method has no effect, and the programmer needs to manually add the token when coding.

Another disadvantage of this method is that it is difficult to guarantee the security of the token itself. Especially in some forums and other websites that support users to post content by themselves, hackers can post the address of their personal website on it. Since the system will also add a token to this address, hackers can get this token on their website and launch a CSRF attack immediately. In order to avoid this, the system can add a judgment when adding the token. If the link is linked to its own site, add the token at the back, if it is connected to the Internet, it will not be added. However, even if the csrftoken is not attached to the request as a parameter, the hacker's website can also obtain the token value through Referer to launch a CSRF attack. This is why some users like to manually turn off the browser Referer function.

Customize attributes in HTTP headers and verify

This method also uses the token and performs verification. The difference from the previous method is that instead of putting the token in the HTTP request as a parameter, it is placed in a custom attribute in the HTTP header. Through the XMLHttpRequest class, you can add the HTTP header attribute csrftoken to all requests of this type at one time, and put the token value into it. This solves the inconvenience of adding the token to the request in the previous method. At the same time, the address requested through XMLHttpRequest will not be recorded in the browser's address bar, and there is no need to worry about the token being leaked to other websites through Referer.

However, the limitations of this method are very large. XMLHttpRequest requests are usually used for partial asynchronous refresh of the page in the Ajax method. Not all requests are suitable for this class to initiate, and the page obtained through this type of request cannot be recorded by the browser, so that it can be forwarded, backed, and refreshed. , Collection and other operations, which bring inconvenience to users. In addition, for legacy systems without CSRF protection, this method must be used for protection, and all requests must be changed to XMLHttpRequest requests, which will almost rewrite the entire website, which is undoubtedly unacceptable.

CSRF defense combat

The mainstream framework generally includes CSRF interception.

Non-frame type-custom XXXCsrfFilter

It can be implemented by customizing xxxCsrfFilter to intercept. It is recommended that you refer to Spring Security-org.springframework.security.web.csrf.CsrfFilter.java.

Spring Security-when to disable CSRF

When will you consider disabling CSRF for the application you develop? At this time, you need to consider that CSRF is essentially the use of cookies, and you can disable it without a cookie.

  • If you are just creating a service for non-browser clients, you may want to disable CSRF protection

Disable CSRF in Spring Security

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();// 默认是启用的,需要禁用CSRF保护
    }
}

Spring Security - CookieCsrfTokenRepository.withHttpOnlyFalse()

Store cookies, such as front-end and back-end separation scheme: Spring Security CookieCsrfTokenRepository + unified front-end routing settings

Spring Security dependency package

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Spring Security - CookieCsrfTokenRepository.withHttpOnlyFalse()

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 本例子给个范例而已,对于xxx的部分,自己根据业务定义
    http
        .authorizeRequests()
            /* allow */
            .antMatchers("/plugins/**", "/api-docs/**") .permitAll()
            .antMatchers("/login", "/logout").permitAll()
            
            /* auth control */
            .antMatchers("/xxx/user", "/xxx/user/**").access("hasAuthority('xxx:user')")
            .antMatchers("/xxx/role", "/xxx/role/**").access("hasAuthority('xxx:role')")

            /* others */
            .anyRequest().authenticated()
           
        /* other Filters */
        .and()
            .addFilterBefore(xxxFilter(), UsernamePasswordAuthenticationFilter.class)
        
        /* iframe */
        .headers()
            .frameOptions()
            .sameOrigin()
        
        /* form login & logout */
        .and().formLogin()
            .loginPage("/login")
            .usernameParameter("username")
            .passwordParameter("password")
            .defaultSuccessUrl("/admin/", true)
        .and().rememberMe()
            .rememberMeParameter("remember")
            .rememberMeCookieName("remember")
        .and().logout()
            .deleteCookies("JSESSIONID")
            .invalidateHttpSession(true)
            .logoutSuccessHandler(new XXXLogoutSuccessHandler(localeResolver()))
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .permitAll()
        
        /* csrf */
        .and().csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
//		.and().cors()
    
}

The back-end thymeleaf login page "/login" :

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<form id="form" method="post">
    <label>用户名:</label><input name="username" type="text" value="" />
    <label>密码:</label><input name="password" type="text" value="" />
    <!--csrf验证需要-->
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
    <br/>
    <input type="submit" value="登录">
</form>
</body>
</html>

The front-end calls the back-end API: Method one (the front and back ends are separated) :

//  将Cookie转换为JS Object
function initCookies() {
    var cookie = document.cookie,
        items = cookie.split(";"),
        keys = {};
    items.forEach(function(item) {
        var kv = item.split('=');
        keys[$.trim(kv[0])] = $.trim(kv[1]);
    });
    return keys;
}
//  提交数据
$.post(url, {
    userId : code,
    _csrf : initCookies()['X-XSRF-TOKEN'];
}, function(datas) {
    //  TODO something
})

**Front-end calls back-end API: Method two (back-end writing front-end, back-end template used) **:

<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
 
<script>
 
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $.ajaxSetup({
        beforeSend: function (xhr) {
            if(header && token ){
                xhr.setRequestHeader(header, token);
            }
        }}
    );
</script>

Spring Security - new CookieCsrfTokenRepository()

You can new CookieCsrfTokenRepository()customize the interception logic, roughly meaning:

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository())
                .requireCsrfProtectionMatcher(
                        /**
                         * 拦截“/login”开头的访问路径,不让访问
                         * 拦截所有“POST”请求,不让访问
                         */
//                        httpServletRequest -> httpServletRequest.getRequestURI().startsWith("/login")
//                                && httpServletRequest.getMethod().equals("POST")
                        httpServletRequest -> httpServletRequest.getMethod().equals("POST")
                );
    }
}

Of course, you can also write like this, you can see the follow-up to the default DefaultRequiresCsrfMatchersource code

public class CsrfSecurityRequestMatcher implements RequestMatcher {
    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
    private RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("^/rest/.*", null);
 
    @Override
    public boolean matches(HttpServletRequest request) {
        if(allowedMethods.matcher(request.getMethod()).matches()){
            return false;
        }
 
        return !unprotectedMatcher.matches(request);
    }
}

Spring Security-How does CookieCsrfTokenRepository work?

CookieCsrfTokenRepository.withHttpOnlyFalse() The essence isnew CookieCsrfTokenRepository()

public static CookieCsrfTokenRepository withHttpOnlyFalse() {
    CookieCsrfTokenRepository result = new CookieCsrfTokenRepository();
    result.setCookieHttpOnly(false);
    return result;
}

Why the default cookie for storing CSRFToken is httpOnly?

If the HttpOnly attribute is set in the cookie, then the cookie information cannot be read through the js script, which can effectively prevent XSS attacks and steal the cookie content, which increases the security of the cookie. Even if it is so, don’t take important information. Deposit cookies. The full name of XSS is Cross SiteScript, cross-site scripting attack, which is a common vulnerability in Web programs. XSS is a passive attack method used on the client side, so its harmfulness is easy to be ignored. The principle is that an attacker enters (passes in) malicious HTML code into a website with XSS vulnerabilities. When other users browse the website, this HTML code will be automatically executed, thereby achieving the purpose of the attack. For example, stealing user cookies, destroying page structure, redirecting to other websites, etc.

// 比如,设置https的cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");

Cookie CsrfToken default package

static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN";

static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";

private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;

private String headerName = DEFAULT_CSRF_HEADER_NAME;

private String cookieName = DEFAULT_CSRF_COOKIE_NAME;

@Override
public CsrfToken generateToken(HttpServletRequest request) {
    return new DefaultCsrfToken(this.headerName, this.parameterName,
            createNewToken());
}

CsrfToken preservation

@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
        HttpServletResponse response) {
    String tokenValue = token == null ? "" : token.getToken();
    Cookie cookie = new Cookie(this.cookieName, tokenValue);
    cookie.setSecure(request.isSecure());
    if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
            cookie.setPath(this.cookiePath);
    } else {
            cookie.setPath(this.getRequestContext(request));
    }
    if (token == null) {
        cookie.setMaxAge(0);
    }
    else {
        cookie.setMaxAge(-1);
    }
    if (cookieHttpOnly && setHttpOnlyMethod != null) {
        ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
    }

    response.addCookie(cookie);
}

Loading of CsrfToken

@Override
public CsrfToken loadToken(HttpServletRequest request) {
    Cookie cookie = WebUtils.getCookie(request, this.cookieName);
    if (cookie == null) {
        return null;
    }
    String token = cookie.getValue();
    if (!StringUtils.hasLength(token)) {
        return null;
    }
    return new DefaultCsrfToken(this.headerName, this.parameterName, token);
}

Spring Security-How does CsrfFilter complete interception and verification?

public final class CsrfFilter extends OncePerRequestFilter {
    // 负责CsrfToken生成,加载等
    private final CsrfTokenRepository tokenRepository;
    
    // 负责拦截Csrf的匹配
    private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;
    
    // 被拦截后的拒绝策略
	private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

    // CsrfFilter的过滤逻辑
	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);

        // 加载token,没有的自动生成一个
		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		final boolean missingToken = csrfToken == null;
		if (missingToken) {
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);

        // 拦截请求
		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

        // 校验token
		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		if (!csrfToken.getToken().equals(actualToken)) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
			if (missingToken) {
				this.accessDeniedHandler.handle(request, response,
						new MissingCsrfTokenException(actualToken));
			}
			else {
				this.accessDeniedHandler.handle(request, response,
						new InvalidCsrfTokenException(csrfToken, actualToken));
			}
			return;
		}

		filterChain.doFilter(request, response);
    }
}

Spring Security-Which methods are blocked by default?

"GET", "HEAD", "TRACE", "OPTIONS" 不会拦截:

private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
    private final HashSet<String> allowedMethods = new HashSet<String>(
            Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

    /*
        * (non-Javadoc)
        *
        * @see
        * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
        * servlet.http.HttpServletRequest)
        */
    @Override
    public boolean matches(HttpServletRequest request) {
        return !this.allowedMethods.contains(request.getMethod());
    }
}

Spring Security - HttpSessionCsrfTokenRepository

After the above analysis, if you look at the Session again, is it very simple? I posted a code here and you can just scan it. @pdai

public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
	private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

	private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";

	private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class
			.getName().concat(".CSRF_TOKEN");

	private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;

	private String headerName = DEFAULT_CSRF_HEADER_NAME;

	private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.web.csrf.CsrfTokenRepository#saveToken(org.
	 * springframework .security.web.csrf.CsrfToken,
	 * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public void saveToken(CsrfToken token, HttpServletRequest request,
			HttpServletResponse response) {
		if (token == null) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				session.removeAttribute(this.sessionAttributeName);
			}
		}
		else {
			HttpSession session = request.getSession();
			session.setAttribute(this.sessionAttributeName, token);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.security.web.csrf.CsrfTokenRepository#loadToken(javax.servlet
	 * .http.HttpServletRequest)
	 */
	public CsrfToken loadToken(HttpServletRequest request) {
		HttpSession session = request.getSession(false);
		if (session == null) {
			return null;
		}
		return (CsrfToken) session.getAttribute(this.sessionAttributeName);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.springframework.security.web.csrf.CsrfTokenRepository#generateToken(javax.
	 * servlet .http.HttpServletRequest)
	 */
	public CsrfToken generateToken(HttpServletRequest request) {
		return new DefaultCsrfToken(this.headerName, this.parameterName,
				createNewToken());
	}
}

Spring Security-What errors can be caused by incorrectly setting Csrf?

  • 403-Use CSRF as a control authority, causing authority issues
There was an unexpected error (type=Forbidden, status=403).
Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.
  • 405-Front parameter binding problem
POST method not supported。// 本质上还是参数绑定时,Csrf没有设置或者不正确。

Summary and outlook

It can be seen that CSRF is a very harmful attack, and it is difficult to prevent. Although several current defense strategies can resist CSRF attacks to a large extent, there is no perfect solution. Some new schemes are under study, such as using different dynamic passwords for each request, combining the Referer and token schemes, and even trying to modify the HTTP specification, but these new schemes are not yet mature and will be formally put into use and be used. It will take time for the industry to be widely accepted. Before that, we had to pay full attention to CSRF and choose the most suitable strategy according to the actual situation of the system, so as to minimize the harm of CSRF.

Reference article

Guess you like

Origin blog.csdn.net/z69183787/article/details/113105542