1. 程式人生 > >程式碼審計--13--CSRF漏洞

程式碼審計--13--CSRF漏洞

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 />

在這裡插入圖片描述

插入之後當其他已登入使用者瀏覽其頁面,即可執行修改個人資料操作

在這裡插入圖片描述

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);
}