CSRF 攻击" />

浅谈 CSRF 攻击

跨过山和大海,伪装成熟悉的陌生人

什么是CSRF

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF

有何危害

故事要从三天前说起,你是一个月薪 3000 的程序员,好不容易存够了 20000 打算更新自己的主机。

这天你在网吧上网,想抽烟(抽烟有害健康)。于是你登陆账户,打算从里面取 100 大洋。与此同时,一个网页吸引你的注意,硕大的“屠龙宝刀,点击就送”使你情不自禁地点击。点进去以后,你发现跳出的居然是转账页面。定睛一看,转入的居然还不是你的账号,金额是一万。你两眼发昏,陷入沉思。

为什么会发生这样的情况呢?我们来看看后台发生了什么:

首先,你登陆进去以后,cookie 里会储存一个你的登陆信息,告诉服务器,你登陆了。

from flask import Flask
from flask import redirect, request, session, url_for

app = Flask(__name__)
app.secret_key = 'APPLE_suck5'

@app.route('/')
def hello():
      return 'Hello World'

@app.route('/login')
def login():
      session['logged_in'] = True
      return redirect(url_for('hello'))

@app.route('/transfer')
def transfer():
      bank_id = request.args.get('bank_id')
      money = request.args.get('money')
      if session.get('logged_in', None):
            if bank_id and money:
                  return f'You transfer {money}$ to bank id:{bank_id}'
            return 'bank id and money amount required'
      return 'Not Authenticated'

而屠龙宝刀网页中的按钮,直接请求了 transfer,向攻击者的账户转钱,而由于你的登陆信息还在,服务器以为这个请求是你发出的(实际上的确是,但并非你主观上想发出)。

<!DOCTYPE html>
<html lang="en">
<head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
</head>
<body>
      <button><a href="http://127.0.0.1:8081/transfer?bank_id=4003&money=10000">屠龙宝刀,点击就送</a></button>
</body>
</html>

可是你仔细一想,这个敏感数据的修改,不应该用 GET 啊,这有问题。

那么,让我们把时间再次回到三天前,这次的转账页面用的是表单,POST 的那种。 与此同时,一个网页吸引你的注意,硕大的“屠龙宝刀,点击就送”使你情不自禁地点击。还没来得及点击,你发现跳出的居然是转账页面。定睛一看,转入的居然还不是你的账号,金额是一万。你两眼发昏,陷入沉思。

明明使用的就是 POST 表单啊,为什么还是会出现问题 ?

我们不妨假设现在的转账函数变为这样:

@app.route('/transfer', methods=['POST', 'GET'])
def withdraw():
      if session.get('logged_in', None):
            bank_id = request.form.get('bank_id')
            money = request.form.get('money')
            if bank_id and money:
                  return f'You transfer {money}$ to bank id:{bank_id}'
      return render_template('index.html')

显而易见,我可以针对你的转账表单构造一个页面,使其在加载过程中提交一个 POST 请求,由于你的登录信息仍存储在 cookie 里面, 服务器以为这个请求是你发出的 ,最终达到邪恶的目的。

构建的页面:

<!DOCTYPE html>
<html lang="en">
<head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
      <script type="text/javascript">
            function steal() {
                  form = document.getElementById('postForm');
                  form.submit()
            }
      </script>
</head>
<body onload="steal()">
      <button>屠龙宝刀,点击就送</button>
      <form method="POST" style="display: none" id="postForm" action="http://127.0.0.1:8081/transfer">
            <input type="hidden" name="bank_id" value="4003">
            <input type="hidden" name="money" value="10000">
      </form>
</body>
</html>

如何预防

1. 尽量使用 POST,限制 GET

GET 接口太容易被拿来做 CSRF 攻击,看第一个示例就知道,只要构造一个 img 标签,而 img 标签又是不能过滤的数据。接口最好限制为 POST 使用,GET 则无效,降低攻击风险。

当然 POST 并不是万无一失,攻击者只要构造一个 form 就可以,但需要在第三方页面做,这样就增加暴露的可能性。

2. 浏览器 Cookie 策略

IE6、7、8、Safari 会默认拦截第三方本地 Cookie(Third-party Cookie)的发送。但是 Firefox2、3、Opera、Chrome、Android 等不会拦截,所以通过浏览器 Cookie 策略来防御 CSRF 攻击不靠谱,只能说是降低了风险。

PS:Cookie 分为两种,Session Cookie(在浏览器关闭后,就会失效,保存到内存里),Third-party Cookie(即只有到了 Exprie 时间后才会失效的 Cookie,这种 Cookie 会保存到本地)。

PS:另外如果网站返回 HTTP 头包含 P3P Header,那么将允许浏览器发送第三方 Cookie。

3. 加验证码

验证码,强制用户必须与应用进行交互,才能完成最终请求。在通常情况下,验证码能很好遏制 CSRF 攻击。但是出于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段,不能作为主要解决方案。

4. Referer Check

Referer Check 在 Web 最常见的应用就是“防止图片盗链”。同理,Referer Check 也可以被用于检查请求是否来自合法的“源”(Referer 值是否是指定页面,或者网站的域),如果都不是,那么就极可能是 CSRF 攻击。

但是因为服务器并不是什么时候都能取到 Referer,所以也无法作为 CSRF 防御的主要手段。但是用 Referer Check 来监控 CSRF 攻击的发生,倒是一种可行的方法。

5. Anti CSRF Token

现在业界对 CSRF 的防御,一致的做法是使用一个 Token(Anti CSRF Token)。

例子:

1. 用户访问某个表单页面。

2. 服务端生成一个 Token,放在用户的 Session 中,或者浏览器的 Cookie 中。

3. 在页面表单附带上 Token 参数。

4. 用户提交请求后, 服务端验证表单中的 Token 是否与用户 Session(或 Cookies)中的 Token 一致,一致为合法

这个 Token 的值必须是随机的,不可预测的。由于 Token 的存在,攻击者无法再构造一个带有合法 Token 的请求实施 CSRF 攻击。另外使用 Token 时应注意 Token 的保密性,尽量把敏感操作由 GET 改为 POST,以 form 或 AJAX 形式提交,避免 Token 泄露。

注意:

CSRF 的 Token 仅仅用于对抗 CSRF 攻击。当网站同时存在 XSS 漏洞时候,那这个方案也是空谈。所以 XSS 带来的问题,应该使用 XSS 的防御方案予以解决。

预防手段摘抄自 hyddd 的博客园