1、漏洞描述

漏洞描述:

Cross-Site Request Forgery(CSRF),跨站请求伪造攻击。

攻击者在用户浏览网页时,利用页面元素(例如img的src),强迫受害者的浏览器向Web应用程序发送一个改变用户信息的请求。

由于发生CSRF攻击后,攻击者是强迫用户向服务器发送请求,所以会造成用户信息被迫修改,更严重者引发蠕虫攻击。

CSRF攻击可以从站外和站内发起。从站内发起CSRF攻击,需要利用网站本身的业务,比如“自定义头像”功能,恶意用户指定自己的头像URL是一个修改用户信息的链接,当其他已登录用户浏览恶意用户头像时,会自动向这个链接发送修改信息请求。

从站外发送请求,则需要恶意用户在自己的服务器上,放一个自动提交修改个人信息的htm页面,并把页面地址发给受害者用户,受害者用户打开时,会发起一个请求。

如果恶意用户能够知道网站管理后台某项功能的URL,就可以直接攻击管理员,强迫管理员执行恶意用户定义的操作。

2、漏洞场景复现

漏洞场景一:

一个没有CSRF安全防御的代码如下:

HttpServletRequest request, HttpServletResponse response)
{
	int userid=Integer.valueOf( request.getSession().getAttribute("userid").toString());
	String email=request.getParameter("email");
	String tel=request.getParameter("tel");
	String realname=request.getParameter("realname");
	Object[] params = new Object[4];
	params[0] = email;
	params[1] = tel;
	params[2] = realname;
	params[3] = userid;
	final String sql = "update user set email=?,tel=?,realname=? where userid=?";
	conn.execUpdate(sql,params);
}

代码中接收用户提交的参数“email,tel,realname”,之后修改了该用户的数据,一旦接收到一个用户发来的请求,就执行修改操作。提交表单代码:

<form action="http://localhost/servlet/modify" method="POST">
	<input name="email">
	<input name="tel">
	<input name="realname">
	<input name="userid">
	<input type="submit">
</form>

当用户点提交时,就会触发修改操作。

本例子是一个站外发起CSRF攻击例子。

如果“代码示例”中的代码,是xxxx.com上的一个web应用,那么恶意用户为了攻击xxxx.com的登录用户,可以构造2个HTML页面。

1、页面a.htm中,iframe一下b.htm,把宽和高都设为0。

<iframe src="b.htm" width="0" height="0"></frame>

这是为了当攻击发生时,受害用户看不到提交成功结果页面。

2、页面b.htm中,有一个表单,和一段脚本,脚本的作用是,当页面加载时,自动提交这个表单。

<form id="modify" action="http://xxxx.com/servlet/modify" method="POST">
	<input name="email">
	<input name="tel">
	<input name="realname">
	<input name="userid">
	<input type="submit">
</form>

<script>
	document.getElementById("modify").submit();
</script>

3、攻击者只要把页面a.htm放在自己的web服务器上,并发送给登录用户即可。

4、用户打开a.htm后,会自动提交表单,发送给xxxx.com下的那个存在CSRF漏洞的web应用,所以用户的信息,就被迫修改了。
在整个攻击过程中,受害者用户仅仅看到了一个空白页面(可以伪造成其他无关页面),并且一直不知道自己的信息已经被修改了。

漏洞场景二:

一个没有CSRF安全防御的代码如下:

String info=request.getParameter("info");
String id=session.getAttribute("userid").toString();
if(info!=null && !info.equals("") && id!=null)
{
	Statement stmt = con.createStatement();
	stmt.executeUpdate("Update users set about='"+info+"' where id="+id);
	out.print("<b class='fail'>info Changed</b>");
}
out.print("<br/><br/><a href='"+path+"/myprofile.jsp?id="+id+"'>Return to Profile Page &gt;&gt;</a>");

代码中接收用户提交的参数“info”,之后修改了该用户的数据,一旦接收到一个用户发来的请求,就执行修改操作,通过get请求构造CSRF链接:
http://localhost:8080/WebLab/vulnerability/csrf/change-info.jsp?info=test&change=Change

本例子是一个站内发起CSRF攻击例子
1、在网站公共区域处,例如发布帖子处插入img标签,src设置为

http://localhost:8080/WebLab/vulnerability/csrf/change-info.jsp?info=test&change=Change

<img src=http://localhost:8080/WebLab/vulnerability/csrf/change-info.jsp?info=test&change=Change />

代码审计--13--CSRF漏洞-LMLPHP

插入之后当其他已登录用户浏览其页面,即可执行修改个人资料操作

代码审计--13--CSRF漏洞-LMLPHP

3、漏洞修复建议

要防御CSRF攻击,应遵循以下过程:

1、在用户登陆时,设置一个TOKEN;

2、表单被提交后,在接收用户请求的Web应用中,判断表单中的TOKEN值是否和系统记录的TOKEN值一致,如果不一致或没有这个值,就判断为CSRF攻击,同时记录攻击日志。由于攻击者无法预测每一个用户登录时生成的那个随机TOKEN值,所以无法伪造这个参数。

具体实现代码如下:

前端表单修改成:
<form action="change-info.jsp" method="GET">
	Description:
	<input type="text" name="info" value=""/>
	<input type="hidden" name="token" value="<%=session.getAttribute("csrftoken").toString()%>" />
	<br/><br/>
	<input type="submit" name="change" value="Change"/>

1、代码中<%=session.getAttribute(“csrftoken”).toString()%>将会生成一个隐藏域,用于生成验证token,它将会作为表单的其中一个参数一起提交。

2、当出现GET请求修改用户数据时,若在url中出现了token,当前页面就不允许出现用户定义的站外链接,否则攻击者可以引诱用户点击攻击者定义的链接,访问在自己的网站,从referer中,获取url中的token,造成token泄露。

后台Java防御代码参考实现如下:

第一步,新建CSRF令牌添加进用户每次登陆以及存储在httpsession里,这种令牌至少对每个用户会话应是唯一的,或者是对每个请求是唯一的。

//this code is in the Defaulter implementation of ESAPI
/**this user’s CSRF token. */
Private String csrfToken = resetCSRFToken();
Public StringresetCSRFToken() {
	csrfToken = ESAPI.random().getRandomString(8, DefaultEncoder.CHAR_ALPHANUMBERICS);
	//利用ESAPI生成随机TOKEN

	Return csrfToken
}

第二步,令牌可以包含在URL中或作为一个URL参数记/隐藏字段。

//from HTTP Utilitiles interface
Final static String CSRF_TOKEN_NAME="token";
//this code is from the Default HTTP Utilities implementation in ESAPI
Public  String addCSRFToken(Stringhref) {
	User user=ESAPI.authenticator().getCurrentUser();
	if(user.isAnonymous()){returnhref;}
	//if there are already parameters append with&,otherwise append with?
	String token=CSRF_TOKEN_NAME+"="+user.getCSRFToken();
	return href.indexOf('?')!=-1?href+"&"+token:href+"?"+token;
}
...

public StringgetCSRFToken() {
	User user=ESAPI.authenticator().getCurrentUser();
	if(user==null) return null;return user.getCSRFToken();
}

第三步,在服务器端检查提交令牌与用户会话对象令牌是否匹配。

//this code is from the Defaul tHTTP Utilities implementation in
//ESAPI
Public  void verifyCSRFToken(HttpServletRequest request) throws IntrusionException {
	User user=ESAPI.authenticator().getCurrentUser();
	//check if user authenticated with this request-noCSRFprotection required
	if(request.getAttribute(user.getCSRFToken())!=null) {
		return;
	}
	String token=request.getParameter(CSRF_TOKEN_NAME);
	if(!user.getCSRFToken().equals(token)) {
		//比较session中token与客户端参数中token是否一致
		throw new IntrusionException("Authenticationfailed","Possibly forgeted HTTP request without proper CSRFtokendetected");
	}
}

第四步,在注销和会话超时,删除用户对象会话和会话销毁。

//this code is in the DefaultUser implementation of ESAPI
Public  void logout() {
	ESAPI.httpUtilities().killCookie(ESAPI.currentResponse(),ESAPI.currentRequest(),HTTPUtilities.REMEMBER_TOKEN_COOKIE_NAME);
	HttpSession session=ESAPI.currentRequest().getSession(false);
	if(session!=null) {
		removeSession(session);
		session.invalidate();
	}
	ESAPI.httpUtilities().killCookie(ESAPI.currentRequest(),ESAPI.currentResponse(),"JSESSIONID");
	loggedIn=false;
	logger.info(Logger.SECURITY_SUCCESS,"Logout successful");
	ESAPI.authenticator().setCurrentUser(User.ANONYMOUS);
}
10-05 20:17