苹果或直接下架微信, 要微信还是要苹果?

CSRF(Cross-site request forgery跨站请求伪造,也被称成为“one click attack”或者session riding{ A j i , 9 ( L #,通常缩写为CSRF或者! e Y ~ R zXSRF,是一种对网站的恶意利用。 @pdai

CSRF 简介

CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种d ` G O 1网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一。其他安全隐患,比如 SQL 脚本注入,跨站I T ; h 1域脚本攻击等在近年来已经逐渐为众人熟知,很多网站也都针对他们进行了防御。然而,对于大多数人来说,CSRF 却依然是一个陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在着 CSRF 漏洞,从而被黑客攻击而使 Gmail 的用户造: Y | 成巨大的损失。

CSRF 如何攻击

先看图:

CSRF 详解:攻击,防御,Spring Security应用等

从上图可以看出,A网站通过cookie来识别用户(C),当用户成功进行身份验证之后浏览器就会得到一个标识其身份的cookie,只要不关闭浏览器或者退出登录,以后访问A网站会一直带上这个cookie。如果这期间浏览器被人控制着向A网站发起请求去执行一些用户不想; 5 Z f ] q *做的功能(比如添加账号),这就是会话劫持了。因为这个不是用户真正想发出的请求,这就是所谓的“请求伪造”。此外,由于请求可以从第三方网站提交,所以前缀跨站二字,即从B网站发起。

具体到银行转账为例(这是网上的一个例子,一= a x c大坨...):

CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未F c ` X 8 T =授权的情@ K o d J况下执行在权限保护之Q x , 7下的操作。比如说,受害者 Bob 在银行有一笔存R f N D款,通过对银行的网站发送请求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款转z 7 t B j 2 x #到 bob2 的账号下。v E 0 -通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的P g - E Q ( t b ] session,并且该 session 的用户 Bob 已经成功登陆。黑客 Mallory 自己在该银行也有账户,他知道上文中的 URL 可以把钱进行转帐操作。Mallory 可以自己发送一个请求给银行: http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory 。但是这个请求来自 Mallory 而非 Bob,他不能通过安全认证,因此该请求不- 9 I会起作用。这时,Mallory 想到使用 CSR& ` N 6 s ^ ,F 的攻击方式,他先自己做一个网站,在网站中放入如下代码: sr1 { C p hc=”http://bank.example/withdraw?account=bob&amou? d k ` O : l Knt=1000000&for=Mallory ” ,并且r , S H S通过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bon 0 X 3 hb 浏览器中的 cookie 一1 G ! o h D E起发向银行服务器。大多数情况下,该请求会失败,因为他要求 Bob 的认证g G 7信息。但是,如果 Bob# b m 当时恰巧刚访问他的银行% x $ N ^后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的 { u _ Q cookie 之中含有 Bob 的认证信息。这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 Bob 的账号转移到 Mallory 的账号,而 Bob 当时毫不知情。等以后 Bob 发现账户钱少了,即] Z )使他去银行查询日志,他也只能发现确实有一Q q = } 8 * u j g个来自于他本人的x B k X _ ? 2合法请求转移了资金,没有任何被攻击的痕迹。而 Mallory 则可以拿O E O b X n e v到钱后逍遥法外。

CSRF 理解的注意点

要理解CSRF,我认为你需要理F } Y D + K解如下几@ c e K m t / .个问题:@pdai

黑客能拿到Cookie吗?

CSRF 攻击是黑客借助受害者的 cookie 骗取服务器的信任,但是黑客并不能拿到 cookie,也看不到 cookie 的内容。

对于服务器返j { Q回的结果,由于浏览器同源策略A T x U c m 7的限制,黑客也无法进行解析。因此,黑客无法从返回的结果中得到任何东西,他所能做的就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,L H l v J a而非窃取服务器中的数据。

什么样的请求是要CSRF保护的Q f 9 * C a A R?

为什么有些框架(比如Spring Security)里防护CSRF的filter限定` K A的Mk I l ? o 2 4 f pethod是POST/PUT/DELETE等,而没有限定GET Meth( H C 4 g K X Rod?

我们要保护的对象是那些可以直接产生数据改变的服务,而对于读取数据的服务,则不需要进行 CSRF 的保护K Y , ! # 2。通常而言GET请作为请求数据,不作为修改数据,所以这些框架没有拦y H L截Get等方式请求。比如q _ U ; [ J +银行系统中转账的请求会直接改变账户的金额,会遭到 CSRF 攻击,需要保护。而查询余额是对金额的读取操作,不会改变数据,CSRF 攻击无法解析服务器返回的结果,无需保护。

为什么对请求做了CSRF拦截,但还是会报CRSF漏洞?

为什么我在前端已经采用P_ N d 8 + # & gOST+CSRF Token请求,后端也对POST请求做了CSRF Filter,但是渗透测试中还有CSRF漏洞?

直接看下面代码。~ ? v z

// 这里没有限制POSTC k V : s a P Method,导致用户可以不通过POST请求提交数据。
@RequestMapping(\"/url\")
public ReponseData 5 Aa saveSomething(XXParam param){
//g I V 数据保存操作...
}

PS:这一点是很容易6 R v e Z C k D ;被忽视的,在9 [ d U u # S笔者经历过的几个项目的渗透测试中,多次出现。@pdai

CSRF 防御常规思路

一定要注意,下面只是给你提供常R w t ; Q b @ o 规思路而已(以下文字摘自 CSRF 攻击的应对之道 ,具体实现请看下一个章节。@pdai

验证 HTTP Referer 字段

根据 HTTP 协议,在 Hp N X 8 Z z FTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问 http://bank.example/withdraw?account=b* d ^ D h #ob&amount=1000000&for=Mallory,用户必须先登陆 bank.examplep K / P,然后通过点击页面上的按钮来触发转账事件。这时,该转帐请求的 Referer 值就会是转账按o } 2钮所在的页面的 URL,通常是以 bann j M r ; # tk.example 域名开头的地址。而如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客自己的网站。因此,要防御 C} ] | c !SRF 攻击7 S !,银行网站只需要对于每一个转账请求验证其 RefererV 8 p 9 k 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。

在请求地址中添加 token 并验证

CSRF 攻击之所以能够成功,是@ c =因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户L a = z自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信. ] & v * 2 # E 息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生A | n L & t的 token,并] A ~ Z $ y在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

这种方法要比检查 Referer 要安全一些7 E e ) N e ( `,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从F 1 9 ^ ) M ^ ~ session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue 。 而对于 POST 请求来说v 5 | Y r K D (,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/> ,这样就把 token 以参数的形式加入V . H g C O请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且8 V ~ O很容易漏掉,W u y c 3 C q z通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于h ? u k O dom 中所有的 a 和 form 标签后加入$ C - H token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方% a k G Z法就没有作用,还需要程序员在编码时手动添加 token。

该方法还有一个缺点是难以保证 token 本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上 token,黑客可以在K : | ,自己的网站上得到这个 token,并马上就可以发动 CSRF% } 5 + A v z q ? 攻击。为了避免这一点,系统可以在添加 tokeg p / 9 o ! 4 - dn 的时候增加一个判断,如果这个链接, & .是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个 csrftoken 不以参数的形式附加H n a R Y } h B( i P请求之中,黑客的网站也同样可以通过 Referer 来得到这个 tokO k 4 ; M n L 9 Jen 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。

在 HTTP 头中自定义属性并验证i t , N a K X

这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 tokenO ^ 9 j 值放入其中。这样解决了上种方法在请求中加入 tok- 5 7 r N : E Aen 的不便,同时,通过 XMLHttpRI = vequest 请a e [ #求的V ~ .地址不q 6 3 )会被记录到浏览器的地址栏,也不用担心 to 8 $ H V (ken 会透过 Referer 泄露到其他网站中去。

然而这种方法的局限性非常大。XMLHttp? L C M ?Request 请求通常用于 Aja[ ) & ! e dx 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏y g n @ w N C p O览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对R U & I n k S s于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接D F R W K受的。

CSRF 防御实战

主流的框架一般都包含了CSRF的拦截。

非框架型 - 自定义XXXCsrfFilter

可以通过自定义xxxCsrfFilter去拦截实现, 这里建议你参考 Spring Security - org.springframeworg Q d P Zk.security.web.csrf.CsrfFilter.java。

Spring Sev r ^curity - 什么时候禁用CSRF

你开发3 0 ] ! G ,的应用在何时,会考虑禁用CSRF呢? 这时候需要考虑CSRF本质是盗用cookie,r N - n 无cookie方E ^ Q W - E 案就可以禁用。

  • 如果你只是创建一个非浏览器客户端使用的服务,你可能会想要禁9 E O 2 o D ] ,用CSRu u $ l D )F保护

Spring Security中禁用CSRF

@EnableWebSecurity
public class WeF N |bSecurityConfig extends WebSecurityConfigurerAdapter {

@Overj ; M r x { { h 7ride
protecteb N r : + md void configure(HtT J N f : wtpSt 0 5 1 p R K Recurity http) throws Exception {
http.csrR g -f().disable();// 默认是启用的,需要禁用CSRF保护
}
}

Spring Security - CookieCsrfTokenRepository.withHttpOnlyFalse()] | e = ~ |

存Cookie,比如前后端分离方案:Spring Security Con R o f dokieCsrfTokenRepository + 前端路由统一设置

Spring Security依赖包

<( A 0 d & / x (;dependency>
&l` c 0 p h - { : Xt;groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-startQ X ? z l Fer-security</artifactId>
</dependency>

Spring Security - CookieCsrfTokenRepos^ l # A 0itory.withHttpOnlyFalse()

@Override
protected void configure(HttpSecurity http& ( C )) th[ 6 L Jrl { d h e 0 l ^ {ows Exception {
// 本例子给个范例而已i 3 p i q G J ~,对于xxx的部分,自己根据业务定义
http
.a0 P T 6uthorizeRequests()
/* allow */
.antMatchers(\"/plugins8 N n/**\F : m z S ` -", \6 & `"/api-docs/**\") .permO d 7itAll()
.antG _ z # 7 } VMatchers(\"/login\", \"/logout\").permitAll()

/* auth control */
.antMatchers(\"/xxx/user\", \"/xxx/user/**\").access(\"hasAuthority(\'xxx:user\')\")
.antG e g x p eMatchers(\"/xxx/role\", \"/xxx/role/**\").access(\"hasAuthority(\'xxx:role\')\")

/* others */
.anyRequest().au] D 0 $ j u ^ G gthenticated()

/*L 9 | 6 $ d other Filters */
.and()
.addFilterBefore(xxxFilter(), UsernamePassA e z R ` n * _ jwordAuthenticationFilter.class)

/* ifram7 G y f ; f d j Je */
.headersG ! q { F k [ 5()` C W
.frameOptions()
.sameOrigin()

/* form login & logout */
.and().formLogin()
.loginPagF x fe(\"/login\")
.usu 1 ^ wernameParameD : o w % I U -ter(\"username\")
.passwordParameter(\"passa / c } 6 j Z ; $word\")
.defaultSuccessUrl(\"/admin/\",s 5 @ 9 { f true)
.and().rememberMe()
.rememberMeParameter(\"remember\")
.rememberMeCookieName(\"remember\")
.and().logout()
.deleteCookies(\"JSESSIONID\")
.invalidateHttpSession(true)
.logoutSuccessHandler(new XXXLogoutSuccessHandler(localeResolver()))
.logoutRequestMatcher(new AntPathRequestMQ 6 Aatcher(\"/logout\x R . a ~ # ="))
.permitAll()

/* csrf */
.and().csrf()
.csrfTt 6 k @ ] y CokenRepou k 3 )sitory(CookieCsrfTokenRepository.withHttpOnlyFalse());
// .and().cors()

}

后端thymeleaf登录页面\"/login\"r P b Y e l ] n

<!DOCTYPE html>
<html lang=\"en\" xmlns:th=\"http://www.thymelE y :eaf.org\">
<head>
<meta charset=\"UTF-8\">
<title>登录页面</title>
</head>
<body>
<form id=\"form\" method=\"post\">
<label>用户名:</label>&l` [ ? = 6 Ot;input name=\"usernJ 5 oame\" type=\"text\" value=\"\" />
<label>密码:</label><input name=\"password\" type=\"text\" value=\"\y G 8" />
<!--csrf验证需要-->
<input type=\"hidden\" th:name=\"${_csrf.parameterName}\" th:value=\"${_csrf.token}2 m @ h } a\"/>
<br/>
<input type=\"submit\" value=\"登录\">
</form>
</body>
</html>R u X [ # 8

前端调用后端A) ^ k .PI: 方式一 (前后端分离的):

//  将Cookie转换为JS Object
function initCookies() {
var cookie = document.cookie,
items = cookie.split(\";\"),
keys = {}f T Q `;
items.forEach(function(item) {
var kv = itU S K O 7 2 s P Wem.splitq C q 1 a(\'=\');
keys[$.trim(h + 7 ) & ( +kv[0])] = $.trim(kv[1]);
});
return keys;
}
// 提交数据
$.post(url, {
userId : code,
_csrf : initCookies()[\'X-XSRF-TOKEN\'];
}k c b 5, function(datas) {
// TODO something
})

前端调用后端API: 方式二 (后端写前端,用的后端模板) :

<meta name=\"_csrf\k _ 6 t 1 N ." content=\"${_csrf.token}\"/>
<meta name=\"_csrf_header\" content=\"${_csrf.hX ~ J ~ ?eaderName}\"/>

<scri9 Q _ S - ; . Qpt>

var token = $(\"meta[name=\'_csrf\']\p A @ ;").attr(\"content\");
var header = $(\"meta[name=\'_csrf_he: - @ 0ader\']\").attr(\"content\")` Q f q ) E;
$.ajaxSetup({
beforeSend: function (xhr) {
if(header &aO 4 ` U r t 5mp;& token ){
xhr.setRequestHeader(header, token)l 6 3;
}
}}
);
</script>

Spring Security - new CookieCsrfTokenRepository()

可以通过 new CookieCsrfTokenRepository() 自定义拦截的逻辑,大概意思:

@Configurak } K 5 h /tion
public class WebSecuritS H C XyConfiguration extends WebSecurityConfigurerAda1 ; -pter {
@Override
protected void configure(HttpS % W X W uecurity http) throws Exception {
http.csrf().csrfToke9 o F NnRepository(new CookieCsrfTokenRepository())
.requireCsrfProtectionMa: j } . & 8 M Stcher(
/**
* 拦截“/login”开头的访} 6 Y ~ E A j v问路径,不让访问
* 拦截所有“POST”请求,不让访问
*/
// httpServlD L c 8 2 w wetRequest -> httpServletRequest.gQ p A C , } eetRequestURI().startsWith(\"/login\")
// && httpServlS e S 3 C n SetReq2 & ; x V 5 4 E ]uest.getMethod().equals(\". 8 k ) K KPOST\")
httpServl, V V m _ ] ` y etRequest -> httpServletRequest.getMethod().equals(\"POST\")
);
}
}

当然也可以这么写,可以看后续对默认的 DefaultRG % P n L , qequiresCsrfMatcher 的源码

public class CsrfSecurityRequestMatcher implements RequestMatchm & 4er {
pr# k l K W L Jivs l 4ate Pattern allowedMethods = Pattern.h ! V R 6 n w =compile(\"^(GET|HEAD|TRACE|OPTIONS)$\"j k } N);
privath P ! 6 l &e RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher! U W L 0 | y T(\"^/rest/.*\", nul- J f u { s @l);

@Override
public boolean matches(HttpServletRequest request) {
if(l O AallowedMethods.matcher(request.getMethod()).matches()){
reQ 0 t [ k Uturn false;
}

return !unprotectedMatcher.matches(requesK M u 5 % Bt);
}
}

Spr9 s ` 6ing Security - CookieCsrfTokenRepository如何工作的呢?

CookieCsrfTokenRepository.withHttpOnlyFalse() 本质就是 new CookieCsrfTokenRepository()

public static CookieCsrfTokenRepository withHttpOnlyFalsH K v v & 9 G x le(6 6 , x ] T u } )) {
Cookb z F = h DieCsrfTokenRepository result = new CookieCsrfTokenRepository();
result.setCookieHttpOnn f g ) ^ - Oly(false);
return result;
}

为何默认的存放CSRFToken的cookie是httpOnly呢?

如果cookie中设z f @置了HttpOnly属性,那么通过js脚d g } c k v - M :本将无法读取到cI M & W .ookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了c8 n { H Z z 4 bookie的安全性,即便{ E h #是这样,也不要将重要信息# + ~ {存入cookR Q V B sie。XSS全称Cross SiteScript,跨站脚本攻击,是Web程序中常见的漏洞,XSS属于被动式且用/ b P g a / v &于客户端的攻击, j 8 # m 2 $ G方式,所以容易被忽略其危害性。其原理是攻击者向有XSS漏u ; 3 e ) B e洞的网站中输入(传入)恶意的HTML代码,当其它用户浏览该网站时r z s z 1 v _,这段HTML代码会自动执行,从而达到攻击的目的。如,盗i J V U 5 p取用户Cookie、破坏页面结构、重定向到- & [ 4 Q其它网站等。

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

Cookie CsrfToken 默认的封装

static final String DEFAE p 8 o [ULT_CSRF_COOKIEr W M ! ~ )_NAME = \"XSRF-TOKEN\B x i p";

static fS { @ 1inal String DEF3 m 0 JAULT_CSRi . * i t { ) fF_PARAMETER_NAME = \"_csrf\";

static final` I C P x K String DEFAULT_CSRF_HEADER_NAMu i p j @ Q g ME = \"X-XSRF-TOKEN\"x Y J k ! @ L c;

private Stri% h ^ 8ng parameterNamn A N : q } W `e = DEFAULT_CSRF_PARAMETER_NAME;

private String heade0 ( f 2rName = DEFAULT_CSRF_HEADER_NAME;

private String cookieName = DT e V E )EFAULZ ! 0 C a ~ 5 iT_CSRF_COOKIE_NAME;

@Override
public CsrfToken generateToken(HttpServlx u L ! k Z [ & oetRequest request) {
return new DefaultCsrT } P , $ + G X YfToken(this.headerName, this.parameterName,
createNewToken());
}

CsrfToken的保存

@e b _ g | P _ [ |Override
public6 v 9 j 0 void sa[ = Y a , . Dve8 & z 9 n fToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
String tokenC s Y | c [ ` K YValue = token == null ? \"\" : token.getToken();
Cookie& I W cookie = new Cookie(this.cookieName, tokenValue);
cookie.setSecure(reques1 8 d B v z 8 ,t.F c ] J x 1 w isSecure());
if (this.cookiePath != null && !this.cookiePaN A * yth.isEmpty()) {
cookie.setPath(this.cookiePath);
} else {
cookie.setPath(this.ge/ D 8 t 7tRequestContext(request));
}
if (tA . moken == null) {
cookie.setMaxAge(0);
}R - 9 # 2 i ( - T
el% } vse {
cookie.setMaxAge(-1);
}I K * u q B j
if (cookieHttpOnly && setHttpOnlyMethod != null) {
ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
}

respo+ # = jnse.addCookie(cookie);
}

CsrfToken的加载

@Override
p. r S H PubK & Ilic CsrfToken loadd 0 #Token(HttpSe% M TrvletRequest request) {
Cookie cookie = WebUn ? 3 . G ytils.getCookie(requO Q + d pesk t $ ^ . qt, this.cookieName);
if (cookie == null) {
return null;
}
String token = cookie.getValue();^ ) Q
if@ 2 l L (!StringUtils.hasLength(token)) {
reV e +turn null;
}
return new DefaultCsrfToken(this.headerName, this.p1 | & G / 1 d karameterName, token);
}

Spring Securiti 4 : w cy - CsrfFilter是如何完成拦截和校验的呢?

public final class CsrfFilter extends OncePerRequestFilter {
// 负责CsrfToken生成,加载等
private final CsrfTokenRepository tokenRepository;

// 负责拦截Csrf的匹配
private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER;

// 被拦截后的拒绝策略
private AccessDeniedHandlef X ? _r accessDeniedH+ U - m C candle? 2 5 v h V )r = new AccessDeniedHandlerImpl();

// CsrfFilter的过滤逻辑
@Override
protected voG Y o w /id doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException,y n h H 9 = a ` IOException {H N | 7 a * W
request.setAttribute(HttpServletResponse.class.getName(), re? t @ u r W y Usponse);

// 加载token,没有的自动生成一个
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missinE R % 5gToken = csrfToken == null;
if (missingToker J ~ V o Q T 9n) {
csrfToken = t{ . z Fhis.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
requesg ~ [ c q Nt.setA7 U n g = i ttribute(csrfTo* ] v .ken.getParamV a o ; D q PeterName(), csrfToke+ * 7 % l ^n);

// 拦截请求
if (!this.t # 7 E - .requireCsrfPR 0 ! s =roteh 1 x , 5 1 ] 8ctionMatcher.matches(reqK h f v f @ n z %uest)) {
filterChain.doFilter(request, response);
return;
}

// 校9 W Q [ , b E验token
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (acz @ L T 5 Y Y ptualToken == null) {
actualToken = r/ f v ; H Pequest.getParam/ ? & e G (eter(csrfToken.getParameterName());
}
if (!csrfToken.getToken- z 5 s q x 4().equals(actualToken)) {
if (this.logJ y 5 lger.is9 C a d z M 1 rDebue n 2 F 3 VgEnabled()) {
tj I } 5 Whis.logger.debug(\"Invalid CSRF token found for \"
+ UrlUtils.buildFullRe^ P Q S # - 0questUrl(request));
}
if (missingToken) {
t+ M N 4 6 { 4 w Mhis.accessDeniedHandler.handle(request, response,
new Missing7 r [ %CsrfTokenExB b / E a u 9 Yception(aY W ; ~ctualToken));
}
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfTokt 4 X 9 gen, actualToken));
}
return;
}

filterChain.doFilter(rE Z F C K ? iequest, response);
}
}

Spring Security - 默认对哪些Method拦截呢?

\"GET\", \^ 4 a { ~ e"HEAD\", \"TRACE\P } q", \"OPTIONS\"t V Z 不会拦截:

private static final class DefaultS f qRequiresCsrfMatcher implements RequestMatcher {
private final HashSet<String&V D C _ &gt; allowedMethods = new HashSet<String>(
ArraA Z |ys.asList(\"GET\", \"HEAD\", \"TRACE\", \"OPTIONS\"));( 2 / ?

/*
* (5 q s cnon-Javadoc)
*
* @see
* org.sprinj 8 & & m 4gframework.security.web.util.matcher.RequestMatcher#matches(javax.
* servlet.httM ` + | D Bp.HttpSeA U N y ervletR] ) H U cequest)
*/
@Override
public boolean; 2 N a [ R u matches(HttpServletRequest requl * @ rev : 0 s y ] : Mst) {
return !this.allowedMethods.contains(ro i H qequest.getMethod());
}
}

Spring Security - HttpSessionCsrfTokenRepository

经过上面的分析,你再看Session的,是不是很简单? 我h q t , ; . , T 4这边贴个代码,你眼睛扫一下即可。@pdai

public final class HttpSessionCsrfT2 1 z N okenRepository implements CsrfTokenRepository {
private static final Str{ 1 = _ing DEFAULT# T m B a & O_CSRF_PARAMETE q m iER_NAME = \"_csrf\";

private static final Str6 ` t B ing DEFAULT_CSRF_HEADER_NAME = \"X-CSRF-TOKEN\7 q |";

private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME$ | ] D F = HS o y 1 1ttpSessionCsrfTo: x _ f m N d Y .kenRepository.class
.getName(M L J _ Q k }).concat(\".CSRF_TOKEN\");

private String: & ( parameterName = DEg Z n nFAULT_CSRF_PARAME} + j i jTER_NAME;

pri6 ! C 3 l uvate String headerName = DEFAULT_CSRF_HEADER_NAME;

private String sz - / I KessionAttributeName = DEFAU~ _ DLTu [ ) w 4_CSRF_TOKEN_ATTR_NAME;

/*
* (non-Javadoc)
*
* @see org.springframework.F h V Lsecurity.web.csrf.CsrfTokenRepository#saveTokr 9 L Sen[ T 7 Y P I } = n(org.
* springframewh h Q ) p ) p Work .securi$ 0 Z 3 n 1 xtA 9 V j z s _ 9 Iy.w$ _ C i Neb.csrf.CsrfToken,
* javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
public void saveToken(CsrfToken token, HttpServletRequest reqh e l o w Y J Q @uej ; Z 1 y & ( g Zst,
HttpServletResponse response) {
if (token == null) {
HttpSession sessionn _ ; g 7 ) = request.getSession(false);
if (se[ { 5ssion != null) {
session.removeAttribute(this.sessionAttributeName);
}
}
else {
HttpSession session = request.getSession();
sessioe F ~ 9 mn.setAttribU 8 ] y d @ N ? oute(thij q ~ 7s.sessionA, d h M l XttributeName, token);
}
}

/k z 7 =*
* (non-Javadoc)
*
* @see
* org.springframework) ) ; L w -.security.web.csrf.CsrfTokenRepository#loadToken(javax.Q ) A J w D {servlm = j c ~et
* .http.HttpServletRequest)
*/
public CsrfToken loadToken(HttpServletRequeZ M c a Q H W ~ @st request) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return (CsrfToken) session.getAttribute(B i gthis.sessionAttributeName);
}

/*
* (non-Javaf f h 1 t . ` F }doc)
*
* @see orgV c f ! ! [.springframework.R ` @ o $ * |security.web.csrf.CsrfTokenRepository#generateToken(javax.
* servlet .http.HttpServletRequest)
*/
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(this.headerName, this.parameterName,
creq - + 3 S KateNewToken());
}
}

Spring= ] x Security - 设置Csrf不对会造成哪些u F U G d $ ]错误呢?

  • 403 - 用CSRF作为控制权限,引发权限问题
There was an unexpected error (type=Forbidden,o O y B ] / status=403).
Invalid CSRF Token \'null\' was found on the request parameter \'9 N j O - ! ~_csV H , [ v F Jrf\~ F ; G 6 5' or header : F u a C e\'X-XSRF-T0 k H r _ 9OKEN\'.
  • 405 - 前置的参数绑定问题
POST method not supported。// 本质上还是参数绑定时,Csrf没有设置或者不正确。

总结与展望

可见,CSRF 是一种危害非常大的攻击,又很难以防范。目前几种防御策略虽然可以很大程度上抵御 CSRF 的攻击,但并没有一种完美| N 6 X Q ( _ 3的解决方案。一些新的方案正在研究之中,比如对于每次请求都使用_ ; l 1不同的动态口令,把 Referer 和 token 方案结合起来,甚至尝试修改 HTTR X 3 5 r ^ d mP 规范,但是这些新的方案尚不成熟,要正式投入使用并被业界广为接受还需时日。在这之前,我们只有充分重视 CSRF,根据系统的实际情7 : 0 1 (况选择最合适的策略,这样才能把 CSRF 的危害降到最低。

上一篇

抗日时期, 为何六个日本兵就能守住一座城? 城中百姓为何不反抗

下一篇

充电3分钟,续航800公里?!

你也可能喜欢

  • 暂无相关文章!

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片
返回顶部