论坛首页 Java企业应用论坛

bboss 动态令牌机制轻松搞定网站跨站攻击和表单重复提交问题

浏览 4076 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-08-25   最后修改:2012-08-26
bboss 动态令牌机制轻松搞定网站跨站攻击和表单重复提交问题,本文详细介绍之。
最新代码请参考文档获取:
bbossgroups 项目下载地址

一、概述
   boss在安全方面下了不少功夫,为了解决网站跨站攻击和表单重复提交问题提供了动态令牌机制,具体内容如下:
1.增加动态令牌检测过滤器(可独立使用,也可与安全认证过滤器结合使用)
2.增加dtoken标签(用来在表单或者请求中投放动态令牌)
3.增加assertdtoken标签(用来在接收请求的服务器jsp页面头部判断客户端是否正确传递令牌,并检测令牌是否有效,如果没有令牌或者令牌无效,那么拒绝客户端请求)
4.assertdtoken注解(用来在接收请求的服务器mvc控制器方法执行前判断客户端是否正确传递令牌,并检测令牌是否有效,如果没有令牌或者令牌无效,那么拒绝客户端请求)

令牌的过滤器的详细配置请参考web.xml文件:
/bboss-mvc/WebRoot/WEB-INF/web.xml
/bboss-mvc/WebRoot/WEB-INF/web-tokenfilter.xml(令牌过滤器独立使用配置示例)
/bboss-mvc/WebRoot/WEB-INF/web-token.xml(与安全认证过滤器结合使用示例)

动态令牌机制可以有效解决以下问题:
1.防止表单重复提交
2.防止跨站脚本编制漏洞
3.防止跨站请求伪造攻击
4.有效避免框架钓鱼攻击
5.有效防止链接注入攻击

二、过滤器组件
我们通过令牌过滤器来拦截需要做令牌检测的url请求,过滤器组件如下:
1.org.frameworkset.web.token.TokenFilter(单独使用的令牌过滤器)
TokenFilter配置示例如下:
<filter>

		<filter-name>tokenFilter</filter-name>
		<filter-class>org.frameworkset.web.token.TokenFilter</filter-class>

		<!-- 防止跨站请求过滤器相关参数开始 ,与安全认证过滤器结合使用 -->
		<!-- 防止跨站请求过滤器相关参数 bboss防止跨站请求过滤器的机制如下: 采用动态令牌和session相结合的方式产生客户端令牌,一次请求产生一个唯一令牌 
			令牌识别采用客户端令牌和服务端session标识混合的方式进行判别,如果客户端令牌和服务端令牌正确匹配,则允许访问,否则认为用户为非法用户并阻止用户访问并跳转到 
			tokenfailpath参数对应的地址,如果没有指定直接响应403错误。 令牌存储机制通过参数tokenstore指定,包括两种,内存存储和session存储,默认为session存储,当令牌失效(匹配后立即失效,或者超时失效)后,系统自动清除失效的令牌;采用session方式 
			存储令牌时,如果客户端页面没有启用session,那么令牌还是会存储在内存中。 令牌生命周期:客户端的令牌在服务器端留有存根,当令牌失效(匹配后立即失效,或者超时失效)后,系统自动清除失效的令牌; 
			当客户端并没有正确提交请求,会导致服务端令牌存根变为垃圾令牌,需要定时清除这些 垃圾令牌;如果令牌存储在session中,那么令牌的生命周期和session的生命周期保持一致,无需额外清除机制; 
			如果令牌存储在内存中,那么令牌的清除由令牌管理组件自己定时扫描清除,定时扫描时间间隔为由tokenscaninterval参数指定,单位为毫秒,默认为30分钟,存根保存时间由tokendualtime参数指定,默认为1个小时 
			可以通过enableToken参数配置指定是否启用令牌检测机制,true检测,false不检测,默认为false不检测 enableToken是否启用令牌检测机制,true 
			启用,false 不启用,默认为不启用 -->
		<init-param>
			<param-name>enableToken</param-name>
			<param-value>true</param-value>
		</init-param>
		<!-- 指定动态令牌校验失败跳转地址
		如果没有指定直接响应403错误-->
		<init-param>
		      <param-name>tokenfailpath</param-name>
		      <param-value>/login.jsp</param-value>
	    </init-param>
		<!-- 指定令牌存储机制,目前提供两种机制: mem:将令牌直接存储在内存空间中 session:将令牌存储在session中,如果对应的页面不存在session,则将token存储在内存中 
			默认存储在session中 -->
		<init-param>
			<param-name>tokenstore</param-name>
			<param-value>mem</param-value>
		</init-param>

		<!-- 内存令牌保留时间,默认为1小时,超过指定的时间,系统将自动清除超时的令牌,如果指定为-1将不检测 单位为毫秒 session中的令牌不受影响 -->
		<init-param>
			<param-name>tokendualtime</param-name>
			<param-value>3600000</param-value>
		</init-param>
		<!-- 内存令牌令牌超时检测时间间隔 单位为毫秒 默认为30分钟,如果需要检测,那么只要令牌持续时间超过tokendualtime 对应的令牌将会被清除,session中的令牌不受影响 -->
		<init-param>
			<param-name>tokenscaninterval</param-name>
			<param-value>1800000</param-value>
		</init-param>
		<!-- 防止跨站请求过滤器相关参数结束 -->
	</filter>
         <filter-mapping>
		<filter-name>tokenFilter</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>tokenFilter</filter-name>
		<url-pattern>*.page</url-pattern>
	</filter-mapping>


tokenFilter具有以下参数:
enableToken-是否启用令牌检测机制,true 启用,false 不启用,默认为不启用
tokenfailpath-指定动态令牌校验失败跳转地址,如果没有指定直接响应403错误
tokenstore-指定令牌存储机制,目前提供两种机制:
            mem:将令牌直接存储在内存空间中
            session:将令牌存储在session中,如果对应的页面不存在session,则将token存储在内存中,默认存储在session中
tokendualtime- 内存令牌保留时间,默认为1小时,超过指定的时间,系统将自动清除超时的令牌,如果指定为-1将不检测, 单位为毫秒, session中的令牌不受影响
tokenscaninterval-内存令牌令牌超时检测时间间隔 单位为毫秒 默认为30分钟,如果需要检测,那么只要令牌持续时间超过tokendualtime 对应的令牌将会被清除,session中的令牌不受影响

2.org.frameworkset.web.interceptor.AuthenticateFilter(抽象安全认证过滤器,是TokenFilter的子类,所有安全认证过滤器的实现类只需要配置令牌管理的相关参数既可以实现动态令牌机制),具体的配置示例如下:
<filter>
		<filter-name>securityFilter</filter-name>
		<filter-class>org.frameworkset.web.interceptor.MyFirstAuthFilter</filter-class>
		。。。。。。。
		<!-- 防止跨站请求过滤器相关参数开始 ,与安全认证过滤器结合使用 -->
		<!-- 防止跨站请求过滤器相关参数 bboss防止跨站请求过滤器的机制如下: 采用动态令牌和session相结合的方式产生客户端令牌,一次请求产生一个唯一令牌 ,令牌识别采用客户端令牌和服务端session标识混合的方式进行判别,如果客户端令牌和服务端令牌正确匹配,则允许访问,否则认为用户为非法用户并阻止用户访问并跳转到 
			tokenfailpath参数对应的地址,如果没有指定直接响应403错误。 令牌存储机制通过参数tokenstore指定,包括两种,内存存储和session存储,默认为session存储,当令牌失效(匹配后立即失效,或者超时失效)后,系统自动清除失效的令牌;采用session方式 
			存储令牌时,如果客户端页面没有启用session,那么令牌还是会存储在内存中。 令牌生命周期:客户端的令牌在服务器端留有存根,当令牌失效(匹配后立即失效,或者超时失效)后,系统自动清除失效的令牌; 
			当客户端并没有正确提交请求,会导致服务端令牌存根变为垃圾令牌,需要定时清除这些 垃圾令牌;如果令牌存储在session中,那么令牌的生命周期和session的生命周期保持一致,无需额外清除机制; 
			如果令牌存储在内存中,那么令牌的清除由令牌管理组件自己定时扫描清除,定时扫描时间间隔为由tokenscaninterval参数指定,单位为毫秒,默认为30分钟,存根保存时间由tokendualtime参数指定,默认为1个小时 
			可以通过enableToken参数配置指定是否启用令牌检测机制,true检测,false不检测,默认为false不检测 enableToken是否启用令牌检测机制,true 
			启用,false 不启用,默认为不启用  -->
		<init-param>
			<param-name>enableToken</param-name>
			<param-value>true</param-value>
		</init-param>

		<!-- 指定令牌存储机制,目前提供两种机制: mem:将令牌直接存储在内存空间中 session:将令牌存储在session中,如果对应的页面不存在session,则将token存储在内存中 
			默认存储在session中 -->
		<init-param>
			<param-name>tokenstore</param-name>
			<param-value>mem</param-value>
		</init-param>
		<!-- 指定动态令牌校验失败跳转地址
		如果没有指定直接响应403错误-->
<!-- 		<init-param> -->
<!-- 		      <param-name>tokenfailpath</param-name> -->
<!-- 		      <param-value>/login.jsp</param-value> -->
<!-- 	    </init-param> -->
		<!-- 内存令牌保留时间,默认为1小时,超过指定的时间,系统将自动清除超时的令牌,如果指定为-1将不检测 单位为毫秒 session中的令牌不受影响 -->
		<init-param>
			<param-name>tokendualtime</param-name>
			<param-value>3600000</param-value>
		</init-param>
		<!-- session中的令牌不受影响 内存令牌令牌超时检测时间间隔 单位为毫秒 默认为30分钟,如果需要检测,那么只要令牌持续时间超过tokendualtime 对应的令牌将会被清除 -->
		<init-param>
			<param-name>tokenscaninterval</param-name>
			<param-value>1800000</param-value>
		</init-param>
		<!-- 防止跨站请求过滤器相关参数结束 -->
	</filter>


	<filter-mapping>
		<filter-name>securityFilter</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>securityFilter</filter-name>
		<url-pattern>*.page</url-pattern>
	</filter-mapping>


三、dtoken标签
有了令牌过滤器后,我们就需要在在客户端生成动态令牌,并将其放置在:
1.表单中input hidden元素中
2.url的参数串中
3.ajax请求的json参数中
这些都可以通过dtoken标签来实现,dtoken定义在pager-taglib.tld文件中,三种方式分别举例说明如下:
1.表单中input hidden元素中
<form action="sayHelloNumber.page" method="post">
			<pg:dtoken/>
			
			<table cellspacing="0" >
				<tbody>
					<tr><td>
							请输入您的幸运数字:
						<input name="name" type="text">
						</td>
					</tr>
					<tr>
						<td>
							来自服务器的问候:
							aa</td>
						

						
					</tr>
					<tr>
						<td><input type="submit" name="确定" value="确定"></td>						
					</tr>
				</tbody>
			</table></form>

<pg:dtoken/>标签将生成一个隐藏的动态令牌参数,将随着表单的提交而提交,该令牌只在本次请求中有效,不能被重复使用,这样就达到了解决了表单重复提交和跨站攻击的问题。

2.url的参数串中
<a href="/aaa/xxx.page?<pg:dtoken element='param'/>"
通过element='param'指定令牌以参数名=令牌的方式追加在url的参数中,随着url一起提交。该令牌只在本次url请求中有效,不能被重复使用,这样就达到了解决了表单重复提交和跨站攻击的问题。

3.ajax请求的json参数中
$("#queryresult").load("sayHelloEnums.page",{sex:$("#sex").val(),<pg:dtoken element="json"/>});
通过element=="json"指定令牌以参数名:令牌的json参数格式追加在ajax参数中,随着ajax请求一起被提交。该令牌只在本次ajax请求中有效,不能被重复使用,这样就达到了解决了表单重复提交和跨站攻击的问题。

四、assertdtoken标签和assertdtoken注解
通过上述三种方式能够有效地防止表单重复提交,但是能不能有效地防止跨站攻击呢,答案是不能,因为服务端在校验令牌时,首先检测有没有令牌被提交过来,如果没有则忽略令牌检查,认为请求是合法的,这样黑客可以根据该规则尝试将令牌参数从请求参数中去除来达到攻击的目的。为了防止这个漏洞,我们需要借助于assertdtoken标签和assertdtoken注解在服务器端强制要求令牌参数。

assertdtoken标签主要用于接收请求的jsp页面头部,例如:
<%@ taglib uri="/WEB-INF/pager-taglib.tld" prefix="pg"%>
<pg:assertdtoken/>
只有assertdtoken检测到令牌并且令牌合法,才执行后续的处理操作,否则直接跳转到过滤器参数tokenfailpath对应的地址或者抛出403异常。

assertdtoken注解用在bboss mvc控制器方法上,用来标注mvc控制器方法强制要求进行动态令牌校验,只有检测到令牌并且令牌合法,才继续开始执行控制器方法,否则直接跳转到过滤器参数tokenfailpath对应的地址或者抛出403异常。
assertdtoken注解使用实例:
@AssertDToken
	public String sayHelloNumber(@RequestParam(name = "name") int ynum,
			ModelMap model)
	{

		if (ynum != 0)
		{
			model.addAttribute("serverHelloNumber", "幸运数字为[" + ynum + "]!");
		}
		else
			model.addAttribute("serverHelloNumber", "幸运数字为[" + ynum
					+ "]!,好像有点不对哦。");

		return "path:sayHello";
	}


五、bboss动态令牌实现原理总结

bboss动态令牌机制采用动态令牌和session相结合的方式产生客户端令牌,一次请求产生一个唯一令牌,令牌识别采用客户端令牌和服务端session标识混合的方式进行判别,如果客户端令牌和服务端令牌正确匹配,则允许访问,否则认为用户为非法用户并阻止用户访问并跳转到tokenfailpath参数对应的地址,默认为空(直接响应403)。
令牌存储机制通过参数tokenstore指定,包括两种,内存存储和session存储,默认为session存储,当令牌失效(匹配后立即失效,或者超时失效)后,系统自动清除失效的令牌;采用session方式存储令牌时,如果客户端页面没有启用session,那么令牌还是会存储在内存中。
令牌生命周期:客户端的令牌在服务器端留有存根,当令牌失效(匹配后立即失效,或者超时失效)后,系统自动清除失效的令牌;当客户端并没有正确提交请求,会导致服务端令牌存根变为垃圾令牌,需要定时清除这些 垃圾令牌;如果令牌存储在session中,那么令牌的生命周期和session的生命周期保持一致,无需额外清除机制;如果令牌存储在内存中,那么令牌的清除由令牌管理组件自己定时扫描清除,定时扫描时间间隔由tokenscaninterval参数指定,单位为毫秒,默认为30分钟,存根保存时间由tokendualtime参数指定,默认为1个小时。
可以通过enableToken参数配置指定是否启用令牌检测机制,true检测,false不检测,默认为false不检测。

动态令牌机制可以有效解决以下问题:
1.防止表单重复提交
2.防止跨站脚本编制漏洞
3.防止跨站请求伪造攻击
4.有效避免框架钓鱼攻击
5.有效防止链接注入攻击

最后再总结一下:
1.tokenFilter拦截需要进行令牌校验客户端请求并执行校验逻辑、同时管理令牌的生命周期和提供定时清楚垃圾令牌机制
2.dtoken标签在客户端表单、url请求和ajax请求中投放动态令牌,仅仅在客户端放置动态随机令牌,可有效防止表单重复提交问题,但是不能彻底解决跨站攻击问题(因为狡猾的黑客会想尽办法去除令牌,绕过服务端令牌校验机制,从而达成跨站攻击目标),那么我们需要assertdtoken标签和assertdtoken注解来防止这种欺骗行为。
3.assertdtoken标签在接收请求的服务器jsp页面头部判断客户端是否正确传递令牌,并检测令牌是否有效,如果没有令牌或者令牌无效,那么拒绝客户端请求
4.assertdtoken注解在接收请求的服务器mvc控制器方法执行前判断客户端是否正确传递令牌,并检测令牌是否有效,如果没有令牌或者令牌无效,那么拒绝客户端请求
   发表时间:2012-08-27  
我如果使用FreeMarker模板来做页面展示,如何在表单中使用你这个<pg:dtoken/>标签?
0 请登录后投票
   发表时间:2012-08-27  
bolo 写道
我如果使用FreeMarker模板来做页面展示,如何在表单中使用你这个<pg:dtoken/>标签?

freemark暂时没考虑,不过可以直接掉java方法获取一个令牌,自己放到隐藏域中
0 请登录后投票
   发表时间:2012-08-27  
yin_bp 写道
bolo 写道
我如果使用FreeMarker模板来做页面展示,如何在表单中使用你这个<pg:dtoken/>标签?

freemark暂时没考虑,不过可以直接掉java方法获取一个令牌,自己放到隐藏域中

呃。。。。
0 请登录后投票
   发表时间:2012-08-27  
生成的隐藏域的样例如下:
<input type="hidden" name="_dt_token_" value="-1518435257">
0 请登录后投票
   发表时间:2012-08-27   最后修改:2012-08-27
为了方便在java代码中直接生成,bboss的令牌管理组件提供四个工具方法:
/**
	 * 生成隐藏域令牌,输出值为:
	 * <input type="hidden" name="_dt_token_" value="-1518435257">
	 * @param request
	 * @return
	 */
	public String buildHiddenDToken(HttpServletRequest request)
	
	/**
	 * 生成json串令牌
	 * 如果jsonsplit为',则输出值为:
	 * _dt_token_:'1518435257'
	 * 如果如果jsonsplit为",则输出值为:
	 * _dt_token_:"1518435257"
	 * @param jsonsplit
	 * @param request
	 * @return
	 */
	public String buildJsonDToken(String jsonsplit,HttpServletRequest request)
	
	/**
	 * 生成url参数串令牌
	 * 输出值为:
	 * _dt_token_=1518435257
	 * @param request
	 * @return
	 */
	public String buildParameterDToken(HttpServletRequest request)
        /**
	 * 只生成令牌,对于这种方式,客户端必须将该token以参数名_dt_token_传回服务端,否则不起作用
	 * 输出值为:
	 * 1518435257[align=center][/align]
	 * @param request
	 * @return
	 */
	public String buildDToken(HttpServletRequest request)

具体的使用方法为:

MemTokenManager memTokenManager = org.frameworkset.web.token.MemTokenManagerFactory.getMemTokenManagerNoexception();
		if(memTokenManager != null)//如果开启令牌机制就会存在memTokenManager对象,否则不存在
		{
			String inputtoken = memTokenManager.buildHiddenDToken(request);
	
			String jsontoken = memTokenManager.buildJsonDToken("'",request);
	
			String parametertoken = memTokenManager.buildParameterDToken(request);
			String token = memTokenManager.buildDToken(request);
		}
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics