CSRF是什么
CSRF在百度百科中是这么说的:“CSRF(Cross-site request forgery跨站请求伪造,也被称为“one
click attack”或者session riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。”。
CSRF攻击原理
从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成2个步骤
1.登录受信任网站A,并在本地生成Cookie。
2.在不登出A的情况下,访问危险网站B (这个过程完成攻击)
![](images/2015082841.png)
CSRF 举例
CSRF 攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作。
比如说,受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2可以使
Bob 把 1000000 的存款转到 bob2 的账号下。
通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session
的用户 Bob 已经成功登陆。
黑客 Mallory 自己在该银行也有账户,他知道上文中的 URL 可以把钱进行转帐操作。
Mallory 可以自己发送一个请求给银行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是这个请求来自
Mallory 而非 Bob,他不能通过安全认证,因此该请求不会起作用。
这时,Mallory 想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码:<img
src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory
” />,并且通过广告等诱使 Bob 来访问他的网站。当 Bob 访问该网站时,上述 url 就会从
Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一起发向银行服务器。大多数情况下,该请求会失败,因为他要求
Bob 的认证信息。
但是,如果 Bob 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的
cookie 之中含有 Bob 的认证信息。
这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 Bob 的账号转移到 Mallory 的账号,而
Bob 当时毫不知情。等以后 Bob 发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而
Mallory 则可以拿到钱后逍遥法外。
CSRF 防御
对于Web应用来说可以有以下几种方案:
1)验证 HTTP Referer 字段
根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问
http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用户必须先登陆
bank.example,然后通过点击页面上的按钮来触发转账事件。这时,该转帐请求的 Referer 值就会是转账按钮所在的页面的
URL,通常是以 bank.example 域名开头的地址。而如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的
Referer 是指向黑客自己的网站。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其
Referer 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果
Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。
这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查
Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。
然而,这种方法并非万无一失。Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于
Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如
IE6 或 FF2,目前已经有一些方法可以篡改 Referer 值。如果 bank.example 网站支持
IE6 浏览器,黑客完全可以把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就可以通过验证,从而进行
CSRF 攻击。
即便是使用最新的浏览器,黑客无法篡改 Referer 值,这种方法仍然有问题。因为 Referer
值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心 Referer
值会把组织内网中的某些信息泄露到外网中。因此,用户自己可以设置浏览器使其在发送请求时不再提供 Referer。当他们正常访问银行网站时,网站会因为请求没有
Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问。
2)在请求地址中添加 token 并验证
CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于
cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御
CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP
请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有
token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session
之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把
token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成
http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form
的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,这样就把
token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上
token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript
遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的
html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。
该方法还有一个缺点是难以保证 token 本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上
token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击。为了避免这一点,系统可以在添加
token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加。不过,即使这个
csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个
token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。
3)在 HTTP 头中自定义属性并验证
这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于
HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上
csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入
token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心
token 会透过 Referer 泄露到其他网站中去。
然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于
Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行
CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest
请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。
Tomcat中的CsrfPreventionFilter
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package org.apache.catalina.filters;
import java.io.IOException;
import java.io.Serializable;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Provides basic CSRF protection for a web application.
The filter assumes
* that:
* <ul>
* <li>The filter is mapped to /*</li>
* <li>{@link HttpServletResponse#encodeRedirectURL(String)}
and
* {@link HttpServletResponse#encodeURL(String)}
are used to encode all URLs
* returned to the client
* </ul>
*/
public class CsrfPreventionFilter extends FilterBase
{
private static final Log log =
LogFactory.getLog(CsrfPreventionFilter.class);
private String randomClass = SecureRandom.class.getName();
private Random randomSource;
private final Set<String> entryPoints
= new HashSet<String>();
private int nonceCacheSize = 5;
@Override
protected Log getLogger() {
return log;
}
/**
* Entry points are URLs that will not be tested
for the presence of a valid
* nonce. They are used to provide a way to navigate
back to a protected
* application after navigating away from it. Entry
points will be limited
* to HTTP GET requests and should not trigger
any security sensitive
* actions.
*
* @param entryPoints Comma separated list of URLs
to be configured as
* entry points.
*/
public void setEntryPoints(String entryPoints)
{
String values[] = entryPoints.split(",");
for (String value : values) {
this.entryPoints.add(value.trim());
}
}
/**
* Sets the number of previously issued nonces
that will be cached on a LRU
* basis to support parallel requests, limited
use of the refresh and back
* in the browser and similar behaviors that may
result in the submission
* of a previous nonce rather than the current
one. If not set, the default
* value of 5 will be used.
*
* @param nonceCacheSize The number of nonces to
cache
*/
public void setNonceCacheSize(int nonceCacheSize)
{
this.nonceCacheSize = nonceCacheSize;
}
/**
* Specify the class to use to generate the nonces.
Must be in instance of
* {@link Random}.
*
* @param randomClass The name of the class to
use
*/
public void setRandomClass(String randomClass)
{
this.randomClass = randomClass;
}
@Override
public void init(FilterConfig filterConfig) throws
ServletException {
// Set the parameters
super.init(filterConfig);
try {
Class<?> clazz = Class.forName(randomClass);
randomSource = (Random) clazz.newInstance();
} catch (ClassNotFoundException e) {
ServletException se = new ServletException(sm.getString(
"csrfPrevention.invalidRandomClass",
randomClass), e);
throw se;
} catch (InstantiationException e) {
ServletException se = new ServletException(sm.getString(
"csrfPrevention.invalidRandomClass",
randomClass), e);
throw se;
} catch (IllegalAccessException e) {
ServletException se = new ServletException(sm.getString(
"csrfPrevention.invalidRandomClass",
randomClass), e);
throw se;
}
}
public void doFilter(ServletRequest request, ServletResponse
response,
FilterChain chain) throws IOException, ServletException
{
ServletResponse wResponse = null;
if (request instanceof HttpServletRequest &&
response instanceof HttpServletResponse) {
HttpServletRequest req = (HttpServletRequest)
request;
HttpServletResponse res = (HttpServletResponse)
response;
boolean skipNonceCheck = false;
if (Constants.METHOD_GET.equals(req.getMethod()))
{
String path = req.getServletPath();
if (req.getPathInfo() != null) {
path = path + req.getPathInfo();
}
if (entryPoints.contains(path)) {
skipNonceCheck = true;
}
}
HttpSession session = req.getSession(false);
@SuppressWarnings("unchecked")
LruCache<String> nonceCache = (session ==
null) ? null
: (LruCache<String>) session.getAttribute(
Constants.CSRF_NONCE_SESSION_ATTR_NAME);
// 对请求进行验证
if (!skipNonceCheck) {
String previousNonce =
req.getParameter(Constants.CSRF_NONCE_REQUEST_PARAM);
if (nonceCache == null || previousNonce == null
||
!nonceCache.contains(previousNonce)) {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
}
if (nonceCache == null) {
nonceCache = new LruCache<String>(nonceCacheSize);
if (session == null) {
session = req.getSession(true);
}
session.setAttribute(
Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
}
// 为下一次请求,生成一个随机数放在URL中
String newNonce = generateNonce();
// 同时将随机数存储在session中
nonceCache.add(newNonce);
wResponse = new CsrfResponseWrapper(res, newNonce);
} else {
wResponse = response;
}
chain.doFilter(request, wResponse);
}
@Override
protected boolean isConfigProblemFatal() {
return true;
}
/**
* Generate a once time token (nonce) for authenticating
subsequent
* requests. This will also add the token to the
session. The nonce
* generation is a simplified version of ManagerBase.generateSessionId().
*
*/
protected String generateNonce() {
byte random[] = new byte[16];
// Render the result as a String of hexadecimal
digits
StringBuilder buffer = new StringBuilder();
randomSource.nextBytes(random);
for (int j = 0; j < random.length; j++) {
byte b1 = (byte) ((random[j] & 0xf0) >>
4);
byte b2 = (byte) (random[j] & 0x0f);
if (b1 < 10)
buffer.append((char) ('0' + b1));
else
buffer.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
buffer.append((char) ('0' + b2));
else
buffer.append((char) ('A' + (b2 - 10)));
}
return buffer.toString();
}
protected static class CsrfResponseWrapper
extends HttpServletResponseWrapper {
private String nonce;
public CsrfResponseWrapper(HttpServletResponse
response, String nonce) {
super(response);
this.nonce = nonce;
}
@Override
@Deprecated
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
@Override
public String encodeRedirectURL(String url) {
return addNonce(super.encodeRedirectURL(url));
}
@Override
@Deprecated
public String encodeUrl(String url) {
return encodeURL(url);
}
@Override
public String encodeURL(String url) {
return addNonce(super.encodeURL(url));
}
/**
* Return the specified URL with the nonce added
to the query string.
*
* @param url URL to be modified
* @param nonce The nonce to add
*/
private String addNonce(String url) {
if ((url == null) || (nonce == null))
return (url);
String path = url;
String query = "";
String anchor = "";
int pound = path.indexOf('#');
if (pound >= 0) {
anchor = path.substring(pound);
path = path.substring(0, pound);
}
int question = path.indexOf('?');
if (question >= 0) {
query = path.substring(question);
path = path.substring(0, question);
}
StringBuilder sb = new StringBuilder(path);
if (query.length() >0) {
sb.append(query);
sb.append('&');
} else {
sb.append('?');
}
sb.append(Constants.CSRF_NONCE_REQUEST_PARAM);
sb.append('=');
sb.append(nonce);
sb.append(anchor);
return (sb.toString());
}
}
protected static class LruCache<T> implements
Serializable {
private static final long serialVersionUID =
1L;
// Although the internal implementation uses
a Map, this cache
// implementation is only concerned with the keys.
private final Map<T,T> cache;
public LruCache(final int cacheSize) {
cache = new LinkedHashMap<T,T>() {
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(Map.Entry<T,T>
eldest) {
if (size() > cacheSize) {
return true;
}
return false;
}
};
}
public void add(T key) {
synchronized (cache) {
cache.put(key, null);
}
}
public boolean contains(T key) {
synchronized (cache) {
return cache.containsKey(key);
}
}
}
} |
CSRF是Web应用程序的一种常见漏洞,其攻击特性是危害性大但非常隐蔽,尤其是在大量Web 2.0技术的应用背景下,攻击者完全可以在用户毫无察觉的情况下发起CSRF攻击。本文将对其基本特性、攻击原理、攻击分类、检测方法及防范手段做一个系统的阐述,并列举攻击实例。
1 CSRF漏洞简介
CSRF(Cross-Site Request Forgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
CSRF攻击方式并不为大家所熟知,实际上很多网站都存在CSRF的安全漏洞。早在2000年,CSRF这种攻击方式已经由国外的安全人员提出,但在国内,直到2006年才开始被关注。2008年,国内外多个大型社区和交互网站先后爆出CSRF漏洞,如:百度HI、NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站)和YouTube等。但直到现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”,其威胁程度由此“美誉”便可见一斑。
2 CSRF攻击原理及实例
CSRF攻击原理
CSRF攻击原理比较简单,如图1所示。其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User
C为Web A网站的合法用户。
![](images/2015082842.jpg)
图1 CSRF攻击原理
1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
CSRF攻击分类
CSRF漏洞一般分为站外和站内两种类型。
CSRF站外类型的漏洞本质上就是传统意义上的外部提交数据问题。通常程序员会考虑给一些留言或者评论的表单加上水印以防止SPAM问题(这里,SPAM可以简单的理解为垃圾留言、垃圾评论,或者是带有站外链接的恶意回复),但是有时为了提高用户的体验性,可能没有对一些操作做任何限制,所以攻击者可以事先预测并设置请求的参数,在站外的Web页面里编写脚本伪造文件请求,或者和自动提交的表单一起使用来实现GET、POST请求,当用户在会话状态下点击链接访问站外Web页面,客户端就被强迫发起请求。
CSRF站内类型的漏洞在一定程度上是由于程序员滥用$_REQUEST类变量造成的。在一些敏感的操作中(如修改密码、添加用户等),本来要求用户从表单提交发起POST请求传递参数给程序,但是由于使用了$_REQUEST等变量,程序除支持接收POST请求传递的参数外也支持接收GET请求传递的参数,这样就会为攻击者使用CSRF攻击创造条件。一般攻击者只要把预测的请求参数放在站内一个贴子或者留言的图片链接里,受害者浏览了这样的页面就会被强迫发起这些请求。
CSRF攻击实例
下面以Axous 1.1.1 CSRF Add Admin Vulnerability(漏洞CVE编号:CVE-2012-2629)为例,介绍CSRF攻击具体实施过程。
Axous是一款网上商店应用软件。Axous 1.1.1以及更低版本在实现上存在一个CSRF漏洞,远程攻击者可以通过构造特制的网页,诱使该软件管理员访问,成功利用此漏洞的攻击者可以添加系统管理员。
利用此漏洞主要包含以下三个过程:
1. 攻击者构造恶意网页。在实施攻击前,攻击者需要构造一个与正常添加管理员用户基本一样的网页,在该恶意网页中对必要的参数项进行赋值,并将该网页的action指向正常添加管理员用户时访问的URL,核心代码如图2所示;
2. 攻击者利用社会工程学诱使Axous系统管理员访问其构造的恶意网页;
3. 执行恶意代码。当系统管理员访问恶意网页时,恶意代码在管理员不知情的情况下以系统管理员的合法权限被执行,攻击者伪造的管理员账户添加成功。
![](images/2015082843.jpg)
图2 CSRF攻击添加管理员核心代码
3 CSRF 漏洞检测
检测CSRF漏洞是一项比较繁琐的工作,最简单的方法就是抓取一个正常请求的数据包,去掉Referer字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。
随着对CSRF漏洞研究的不断深入,不断涌现出一些专门针对CSRF漏洞进行检测的工具,如CSRFTester,CSRF
Request Builder等。
以CSRFTester工具为例,CSRF漏洞检测工具的测试原理如下:使用CSRFTester进行测试时,首先需要抓取我们在浏览器中访问过的所有链接以及所有的表单等信息,然后通过在CSRFTester中修改相应的表单等信息,重新提交,这相当于一次伪造客户端请求。如果修改后的测试请求成功被网站服务器接受,则说明存在CSRF漏洞,当然此款工具也可以被用来进行CSRF攻击。
4 CSRF漏洞防御
CSRF漏洞防御主要可以从三个层面进行,即服务端的防御、用户端的防御和安全设备的防御。
4.1 服务端的防御
目前业界服务器端防御CSRF攻击主要有三种策略:验证HTTP Referer字段,在请求地址中添加token并验证,在HTTP头中自定义属性并验证。下面分别对这三种策略进行简要介绍。
4.1.1 验证HTTP Referer字段
根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限页面的请求必须来自于同一个网站。比如某银行的转账是通过用户访问http://bank.test/test?page=10&userID=101&money=10000页面完成,用户必须先登录bank.
test,然后通过点击页面上的按钮来触发转账事件。当用户提交请求时,该转账请求的Referer值就会是转账按钮所在页面的URL(本例中,通常是以bank.
test域名开头的地址)。而如果攻击者要对银行网站实施CSRF攻击,他只能在自己的网站构造请求,当用户通过攻击者的网站发送请求到银行时,该请求的Referer是指向攻击者的网站。因此,要防御CSRF攻击,银行网站只需要对于每一个转账请求验证其Referer值,如果是以bank.
test开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果Referer是其他网站的话,就有可能是CSRF攻击,则拒绝该请求。
4.1.2 在请求地址中添加token并验证
CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信息不存在于Cookie之中。鉴于此,系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能是CSRF攻击而拒绝该请求。
4.1.3 在HTTP头中自定义属性并验证
自定义属性的方法也是使用token并进行验证,和前一种方法不同的是,这里并不是把token以参数的形式置于HTTP请求之中,而是把它放到HTTP头中自定义的属性里。通过XMLHttpRequest这个类,可以一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中。这样解决了前一种方法在请求中加入token的不便,同时,通过这个类请求的地址不会被记录到浏览器的地址栏,也不用担心token会通过Referer泄露到其他网站。
4.2 用户端的防御
对于普通用户来说,都学习并具备网络安全知识以防御网络攻击是不现实的。但若用户养成良好的上网习惯,则能够很大程度上减少CSRF攻击的危害。例如,用户上网时,不要轻易点击网络论坛、聊天室、即时通讯工具或电子邮件中出现的链接或者图片;及时退出长时间不使用的已登录账户,尤其是系统管理员,应尽量在登出系统的情况下点击未知链接和图片。除此之外,用户还需要在连接互联网的计算机上安装合适的安全防护软件,并及时更新软件厂商发布的特征库,以保持安全软件对最新攻击的实时跟踪。
4.3 安全设备的防御
由于从漏洞的发现到补丁的发布需要一定的时间,而且相当比例的厂商对漏洞反应不积极,再加之部分系统管理员对系统补丁的不够重视,这些都给了攻击者可乘之机。鉴于上述各种情况,用户可以借助第三方的专业安全设备加强对CSRF漏洞的防御。
CSRF攻击的本质是攻击者伪造了合法的身份,对系统进行访问。如果能够识别出访问者的伪造身份,也就能识别CSRF攻击。研究发现,有些厂商的安全产品能基于硬件层面对HTTP头部的Referer字段内容进行检查来快速准确的识别CSRF攻击。图3展示了这种防御方式的简图。目前H3C公司的IPS产品采用了特殊技术,支持对部分常用系统的CSRF漏洞攻击进行检测和阻断。
![](images/2015082844.png)
图3 安全设备传统防御方式
5 结束语
CSRF攻击作为一种存在已久的攻击方式,在大量的商业网站上都可以找出。对广大系统维护者来说需要深入理解CSRF攻击,并制定最适合当前系统的防御方案,在不损害应用程序性能的前提下,提高系统安全性;而对即将开发的网络应用系统来说,深刻理解CSRF的危害性,在设计阶段就考虑到对CSRF的防范将会取得事半功倍的效果。 |