CSRF 原理以及 Struts2 令牌校验防御攻略解析

struts2 token 不仅能够有效防止表单重复提交,而且还可以进行CSRF 验证
CSRF 攻击原理如下图:


CSRF 攻击原理图

事实上,B 可能也是一个良性网站,只是被黑客 XSS 劫持了而已。用户实在冤枉啊:我没有上乱七八糟的网站,怎么还是中招了呢?
struts2 token 校验原理如图所示:


Struts2 token 验证原理图

对照 CSRF 攻击原理图,可以看到,虽然 a.jsp 将这个令牌值返回给浏览器,但是 B 是无法拿到这个令牌的具体值的,或者说 B 只能够通过去请求 A 才能拿到一个令牌,但这样就失去了伪造用户请求的目的,因为 session 不一致了。所以 b.action 也就不再处理这个伪造的请求,截断了 CSRF 的流程如下图所示:


Struts2 token截断了CSRF攻击原理图

了解了原理,那就干活吧。结果发现很多非 form 表单提交的请求都被成功拦截了,比如原有的 ajax 请求、页面的 href 请求、dwr 请求都无法正常工作了——把这些请求的 action 的 token 验证去掉就能正常工作,但却这给黑客带来了可趁之机。
那么怎么样才可以让 form 表单之外的其他请求传递 struts2 token 呢?
我们继续来深入了解一下 struts2 token 验证的原理。

以最新版的 2.3.20 为例。我们在 struts.xml 对某 action 加 <interceptor-ref name="token"></interceptor-ref> 标签,事实上是配置了 org.apache.struts2.interceptor.TokenInterceptor 拦截器。查看其 doIntercept 源码:

	protected String doIntercept(ActionInvocation invocation) throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Intercepting invocation to check for valid transaction token.");
		}

		//see WW-2902: we need to use the real HttpSession here, as opposed to the map
		//that wraps the session, because a new wrap is created on every request
		HttpSession session = ServletActionContext.getRequest().getSession(true);

		synchronized (session) {
			if (!TokenHelper.validToken()) {
				return handleInvalidToken(invocation);
			}
		}
		return handleValidToken(invocation);
	}

它调用了 org.apache.struts2.util.TokenHelper 的 validToken 方法,其源码:

	public static boolean validToken() {
		String tokenName = getTokenName();

		if (tokenName == null) {
			if (LOG.isDebugEnabled()) {
				LOG.debug("no token name found -> Invalid token ");
			}
			return false;
		}

		String token = getToken(tokenName);

		if (token == null) {
			if (LOG.isDebugEnabled()) {
				LOG.debug("no token found for token name "+tokenName+" -> Invalid token ");
			}
			return false;
		}

		Map session = ActionContext.getContext().getSession();
		String sessionToken = (String) session.get(tokenName);

		if (!token.equals(sessionToken)) {
			if (LOG.isWarnEnabled()) {
				LOG.warn(LocalizedTextUtil.findText(TokenHelper.class, "struts.internal.invalid.token", ActionContext.getContext().getLocale(), "Form token {0} does not match the session token {1}.", new Object[]{token, sessionToken}));
			}

			return false;
		}

		// remove the token so it won‘t be used again
		session.remove(tokenName);

		return true;
	}

它先调用自己的 getTokenName() 拿到 tokenName 值,然后根据这个值使用自己的 getToken(tokenName) 拿到客户端请求过来的 token 参数的值,最后跟 session 中保存的值进行比较。
查看 getTokenName() 源码:

	public static String getTokenName() {
		Map params = ActionContext.getContext().getParameters();

		if (!params.containsKey(TOKEN_NAME_FIELD)) {
			if (LOG.isWarnEnabled()) {
				LOG.warn("Could not find token name in params.");
			}

			return null;
		}

		String[] tokenNames = (String[]) params.get(TOKEN_NAME_FIELD);
		String tokenName;

		if ((tokenNames == null) || (tokenNames.length < 1)) {
			if (LOG.isWarnEnabled()) {
				LOG.warn("Got a null or empty token name.");
			}

			return null;
		}

		tokenName = tokenNames[0];

		return tokenName;
	}

TOKEN_NAME_FIELD 是 org.apache.struts2.util.TokenHelper 的静态变量,值为 "struts.token.name",getTokenName 方法的作用就是拿到客户端传过来的 struts.token.name 参数的值。validToken 拿到这个值以后,根据这个值继续调用 getToken,getToken 源码:

	public static String getToken(String tokenName) {
		if (tokenName == null ) {
			return null;
		}
		Map params = ActionContext.getContext().getParameters();
		String[] tokens = (String[]) params.get(tokenName);
		String token;

		if ((tokens == null) || (tokens.length < 1)) {
			if (LOG.isWarnEnabled()) {
				LOG.warn("Could not find token mapped to token name " + tokenName);
			}
			return null;
		}

		token = tokens[0];

		return token;
	}

它根据 struts.token.name 参数的值,比如为 "token"(这个值可以在 jsp 的 token 标签中进行配置,比如 <s:token name="token"/>,默认值是 token),拿到客户端传来的 token 参数的值,最后返回给 validToken 方法。
使用 chrome 的开发者工具截取到的带有 token 验证的 struts action 印证了上面的说法:


截取到的struts.token.name的值

原理都了解了,接下来的事情似乎就顺理成章了。
$.ajax 调用支持 struts2 token:

<script>
var strutsToken = "<s:property value="#session[‘struts.tokens.token‘]" />";
var token = {
  "struts.token.name": "token",
  "token": strutsToken
};

$.ajax({
  url: ‘/endpoint‘,
  data: token,
  dataType: ‘jsonp‘,
  cache: true,
  success: function() { console.log(‘success‘); },
  error: function() { console.log(‘failure‘); }
});
</script>

页面 href 请求支持 struts2 token:

<td>
	<a href="findSigleDetail.action?TransId=${value.transid}&struts.token.name=token&token=
		<s:property value="#session[‘struts.tokens.token‘]" />" title="Edit Meta"><img src="frame/image/pencil.png"
		alt="Edit Meta" />查看详细</a>
</td>

参考资料

时间: 2024-12-19 16:53:16

CSRF 原理以及 Struts2 令牌校验防御攻略解析的相关文章

电子商务网站互联网安全防御攻略

电子商务网站,互联网的安全防御相当重要,尤其是牵扯到支付这一块的.本文总结了一些比较通用的 web 安全防御常识,供大家参考一下,也希望可以和关心这一块的同行一起讨论一下这方面的话题. 1. 信息传输加密 https 使用对称加密还是非对称加密? 对称加密使用 DES 还是 AES? 非对称加密使用 RSA 还是 DSA? 使用什么加密算法,对于 java web 来讲是由 keytool 工具在生成秘钥库的时候指定,生成秘钥库之后,再将购买的证书进行导入.一般使用RSA加密算法. SSL 证书

【codecombat】 试玩全攻略

一直都想找一个边玩游戏边学习编程的途径,上周仍不死心,在百度搜编程游戏,偶然发现了codecombat这款可以边学习编程语言边玩游戏的过关类小游戏, 一玩就停不下来. 从刚开始的简单,到思索半天仍不得解,就算如此,整个过程仍觉得十分有趣,推荐给想学编程的朋友试玩,也十分感兴趣. 可是有些关卡对没有编程基础的朋友来说,比较困难,即使有提示也不能完全解决. 同时,中文网页上关于codecombat的介绍的文章也不少,但作攻略解析的毛都没有,不不不,还有一二根的. 大眼一看,代码生硬,没有解析,硬生生

Asp.net MVC 3 防止 Cross-Site Request Forgery (CSRF)原理及扩展

原理:http://blog.csdn.net/cpytiger/article/details/8781457 原文地址:http://www.cnblogs.com/wintersun/archive/2011/12/09/2282675.html Cross-Site Request Forgery (CSRF) 是我们Web站点中常见的安全隐患. 下面我们在Asp.net MVC3 来演示一下. 例如我们有一个HomeContoller中一个Submit Action,我们标记了Http

struts2 自定义校验规则

自定义校验规则:(了解) 在Struts2自定义校验规则: 1.实现一个Validator 接口. 2.一般开发中继承ValidatorSupport 或者 FieldValidatorSupport * ValidatorSupport           :不是针对一个字段校验. 校验确认密码(与密码字段比较) * FieldValidatorSupport :针对一个字段的校验. 步骤: 1.编写一个类继承FieldValidatorSupport public void validate

struts2 之 struts2数据校验

1. 数据校验一般分为2类:前端的校验(js校验),后端的校验(java代码):实际开发中大部分情况下都是采用js校验.在对数据安全要求较高的情况下可能会采用后端验证. 2.  Struts2提供了后端验证机制.有两种方式来实现,一种是通过硬编码来实现,一种是通过校验框架来实现.在struts2如果使用struts2提供校验机制,那么必须继承ActionSuport类.该类中有一个vaildete方法,在处理的action类中重写该方法,那么在执行所有方法的时候,会执行validate方法.va

jquery.validate使用攻略(表单校验)

目录 jquery.validate使用攻略1 第一章 jquery.validate使用攻略1 第二章 jQuery.validate.js API7 Custom selectors7 Utilities8 Validator8 List of built-in Validation methods9 validate ()的可选项11 debug:进行调试模式11 第三章 自定义jquery-validate的验证行为23 第四章 自定义错误消息的显示方式25 第五章 一些常用的验证脚本2

Struts2输入校验之validate输入校验方式

一.在Web系统项目中有大量的视图页面需要用户自行输入很多数据.这些数据的类型有很多种.为了防止某些客户的恶意输入以及对Web项目的恶意破坏,必须引入输入校验,像Windows操作系统的防火墙一样把一些垃圾数据过滤掉,挡在Web系统之外.接下来就来介绍一下validate输入校验方式: 1.validate方法进行输入校验:这里直接附上一个简单的用户注册功能具体介绍利用validate方法对数字.字符串.日期等类型数据进行输入校验方式介绍: (1).首先,新建一个Struts2项目InputVa

跨站请求伪造CSRF原理

跨站请求伪造CSRF原理 CSRF是Cross Site Request Forgery的缩写,CSRF是伪造成合法用户发起请求 先了解下session: 用户登录时服务器端生成一个Session 返回给用户session_id在cookie中 用户下次登录带着有session_id的cookie登录 跨站请求伪造例子 只有session的话,如果有一个网站可以访问所有的cookie,那么就会获取到所有的session,然后带着你的session去访问别的网站. 比如:在一些诱骗性的网站上,可以

axios全攻略

随着 vuejs 作者尤雨溪发布消息,不再继续维护vue-resource,并推荐大家使用 axios 开始,axios 被越来越多的人所了解.本来想在网上找找详细攻略,突然发现,axios 的官方文档本身就非常详细!!有这个还要什么自行车!!所以推荐大家学习这种库,最好详细阅读其官方文档.大概翻译了一下 axios 的官方文档,相信大家只要吃透本文再加以实践,axios 就是小意思啦!! 如果您觉得本文对您有帮助,不妨点个赞或关注收藏一下,您的鼓励对我非常重要. axios 简介 axios