CSRF進攻 (跨站域請求僞造)

CSRF 攻擊的對象

在討論如何抵禦 CSRF 之前,先要明確 CSRF 攻擊的對象,也就是要保護的對象。從以上的例子可知,CSRF 攻擊是黑客藉助受害者的 cookie 騙取服務器的信任,但是黑客並不能拿到 cookie,也看不到 cookie 的內容。另外,對於服務器返回的結果,由於瀏覽器同源策略的限制,黑客也無法進行解析。因此,黑客無法從返回的結果中得到任何東西,他所能做的就是給服務器發送請求,以執行請求中所描述的命令,在服務器端直接改變數據的值,而非竊取服務器中的數據。所以,我們要保護的對象是那些可以直接產生數據改變的服務,而對於讀取數據的服務,則不需要進行 CSRF的保護。比如銀行系統中轉賬的請求會直接改變賬戶的金額,會遭到 CSRF 攻擊,需要保護。而查詢餘額是對金額的讀取操作,不會改變數據,CSRF 攻擊無法解析服務器返回的結果,無需保護。\

 

Csrf攻擊方式:

對象:A:普通用戶,B:攻擊者

1、假設A已經登錄過xxx.com並且取得了合法的session,假設用戶中心地址爲:http://xxx.com/ucenter/index.do

2B想把A餘額轉到自己的賬戶上,但是B不知道A的密碼,通過分析轉賬功能發現xxx.com網站存在CSRF攻擊漏洞和XSS漏洞。

3B通過構建轉賬鏈接的URL如:http://xxx.com/ucenter/index.do?action=transfer&money=100000 &toUser=(B的帳號),因爲A已經登錄了所以後端在驗證身份信息的時候肯定能取得A的信息。B可以通過xss或在其他站點構建這樣一個URL誘惑A去點擊或觸發Xss。一旦A用自己的合法身份去發送一個GET請求後A100000元人民幣就轉到B賬戶去了。當然了在轉賬支付等操作時這種低級的安全問題一般都很少出現。

 

防禦CSRF

1、驗證 HTTP Referer 字段

2、在請求地址中添加 token 並驗證

3、 HTTP 頭中自定義屬性並驗證

4、加驗證碼

 (copy防禦CSRF毫無意義,參考上面給的IBM專題的URL)

Token

最常見的做法是加token,Java裏面典型的做法是用filterhttps://code.google.com/p/csrf-filter/(鏈接由plt提供,源碼上面的在:http://ahack.iteye.com/blog/1900708)

 

 

csrf-filter:

 

 

package com.google.code.csrf;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StatelessCookieFilter implements Filter {

	private final static Logger LOG = LoggerFactory.getLogger(StatelessCookieFilter.class);
	private final static Pattern COMMA = Pattern.compile(",");

	private String csrfTokenName;
	private String oncePerRequestAttributeName;
	private int cookieMaxAge;
	private Set<String> excludeURLs;
	private List<String> excludeStartWithURLs;
	private Set<String> excludeFormURLs;
	private Random random;

	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest httpReq = (HttpServletRequest) req;
		HttpServletResponse httpResp = (HttpServletResponse) resp;

		if (httpReq.getAttribute(oncePerRequestAttributeName) != null) {
			chain.doFilter(httpReq, httpResp);
		} else {
			httpReq.setAttribute(oncePerRequestAttributeName, Boolean.TRUE);
			try {
				doFilterInternal(httpReq, httpResp, chain);
			} finally {
				httpReq.removeAttribute(oncePerRequestAttributeName);
			}
		}
	}

	private void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException {
		if (!req.getMethod().equals("POST")) {
			if (excludeFormURLs.contains(req.getServletPath())) {
				chain.doFilter(req, resp);
				return;
			}
			for (String curStart : excludeStartWithURLs) {
				if (req.getServletPath().startsWith(curStart)) {
					chain.doFilter(req, resp);
					return;
				}
			}
			String token = Long.toString(random.nextLong(), 36);
			LOG.debug("new csrf token generated: {} path: {}", token, req.getServletPath());
			req.setAttribute(csrfTokenName, token);
			Cookie cookie = new Cookie(csrfTokenName, token);
			cookie.setPath("/");
			cookie.setMaxAge(cookieMaxAge);
			resp.addCookie(cookie);
			chain.doFilter(req, resp);
			return;
		}

		if (excludeURLs.contains(req.getServletPath())) {
			chain.doFilter(req, resp);
			return;
		}

		String csrfToken = req.getParameter(csrfTokenName);
		if (csrfToken == null) {
			LOG.error("csrf token not found in POST request: {}", req);
			if (!resp.isCommitted()) {
				resp.sendError(400);
			}
			return;
		}
		req.setAttribute(csrfTokenName, csrfToken);

		for (Cookie curCookie : req.getCookies()) {
			if (curCookie.getName().equals(csrfTokenName)) {
				if (curCookie.getValue().equals(csrfToken)) {
					chain.doFilter(req, resp);
					return;
				} else {
					LOG.error("mismatched csrf token. expected: {} received: {}", csrfToken, curCookie.getValue());
					if (!resp.isCommitted()) {
						resp.sendError(400);
					}
					return;
				}
			}
		}

		LOG.error("csrf cookie not found at: {}", req.getServletPath());
		if (!resp.isCommitted()) {
			resp.sendError(400);
		}
	}

	public void destroy() {
		// do nothing
	}

	public void init(FilterConfig config) throws ServletException {
		String value = config.getInitParameter("csrfTokenName");
		if (value == null || value.trim().length() == 0) {
			throw new ServletException("csrfTokenName parameter should be specified");
		}
		csrfTokenName = value;
		String excludedURLsStr = config.getInitParameter("exclude");
		if (excludedURLsStr != null) {
			String[] parts = COMMA.split(excludedURLsStr);
			excludeURLs = new HashSet<String>(parts.length);
			for (String cur : parts) {
				excludeURLs.add(cur);
			}
		} else {
			excludeURLs = new HashSet<String>(0);
		}
		String excludedFormURLsStr = config.getInitParameter("excludeGET");
		if (excludedFormURLsStr != null) {
			String[] parts = COMMA.split(excludedFormURLsStr);
			excludeFormURLs = new HashSet<String>(parts.length);
			for (String cur : parts) {
				excludeFormURLs.add(cur.trim());
			}
		} else {
			excludeFormURLs = new HashSet<String>(0);
		}
		String excludeStartWithURLsStr = config.getInitParameter("excludeGETStartWith");
		if (excludeStartWithURLsStr != null) {
			String[] parts = COMMA.split(excludeStartWithURLsStr);
			excludeStartWithURLs = new ArrayList<String>(parts.length);
			for (String curPart : parts) {
				excludeStartWithURLs.add(curPart.trim());
			}
		} else {
			excludeStartWithURLs = new ArrayList<String>(0);
		}
		String cookieMaxAgeStr = config.getInitParameter("cookieMaxAge");
		if (cookieMaxAgeStr != null) {
			try {
				cookieMaxAge = Integer.parseInt(cookieMaxAgeStr);
			} catch (NumberFormatException nfe) {
				throw new ServletException("cookieMaxAge must be an integer: " + cookieMaxAgeStr, nfe);
			}
		} else {
			cookieMaxAge = 3600; // 60*60 seconds = 1 hour
		}
		oncePerRequestAttributeName = getFirstTimeAttributeName();
		random = new SecureRandom();
	}

	public static String getFirstTimeAttributeName() {
		return StatelessCookieFilter.class.getName() + ".ATTR";
	}

}

 

 

HowTo

  • Configure web.xml:
<filter>
                <filter-name>csrfFilter</filter-name>
                <filter-class>com.google.code.csrf.StatelessCookieFilter</filter-class>
                <init-param> 
                        <param-name>csrfTokenName</param-name> 
                        <param-value>csrf</param-value> 
                </init-param>
                <init-param>
                        <!-- optional. urls to exclude from check -->
                        <param-name>exclude</param-name> 
                        <param-value>/url1,/url/url2</param-value> 
                </init-param>
                <init-param>
                        <!-- optional. urls to exclude from generating csrf cookie. Useful for ajax requests that do not contain forms -->
                        <param-name>excludeGET</param-name> 
                        <param-value>/url3,/url/url4</param-value> 
                </init-param>
                <init-param>
                        <!-- optional. urls to exclude from generating csrf cookie. Exclude do check servletPath().startsWith() -->
                        <param-name>excludeGETStartWith</param-name> 
                        <param-value>/js/,/css/,/img/</param-value> 
                </init-param>
                <init-param>
                        <!-- optional. cookieMaxAge. By default 3600 seconds -->
                        <param-name>cookieMaxAge</param-name> 
                        <param-value>18000</param-value> 
                </init-param>
        </filter>
        <filter-mapping>
                <filter-name>csrfFilter</filter-name>
                <url-pattern>/*</url-pattern>
        </filter-mapping>

 

 

  • Add to every POST request parameter "csrf". For example form.jsp:

 

<form method="POST">
                <input type="hidden" name="csrf" value="${csrf}">
 </form>

 

  • For "multipart/form-data" requests add to "action" attribute:
<form action="/url?csrf=${csrf}" method="POST" enctype="multipart/form-data">
                <input type="file" name="file" size="50"/>
</form>

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章