JS `CSRF` (跨站请求伪造) 攻击与 `SameSite Cookie` / `CSRF Token` 绕过

各位观众老爷,大家好!今天咱们就来聊聊Web安全里一个让人头疼的家伙:CSRF(Cross-Site Request Forgery,跨站请求伪造),以及它的小伙伴们——SameSite Cookie和CSRF Token,还有怎么绕过它们。准备好了吗?咱们这就开讲!

什么是CSRF? 你好骚啊!

想象一下,你正在一家银行的网站上愉快地浏览账户余额,突然,你的朋友发来一个链接,说点开有惊喜。你手贱点开了,结果…你的银行账户里的钱被转走了!是不是感觉自己被绿了?

这就是CSRF攻击。攻击者利用你已经登录的身份,在你不知情的情况下,冒充你向服务器发送恶意请求。服务器一看,请求头里带着你的Cookie,心想:“嗯,这货是自己人,没毛病,准了!” 于是,攻击就成功了。

简单来说,CSRF就是“冒名顶替”攻击。攻击者不需要知道你的密码,只需要利用你的身份就行。

CSRF攻击的条件

要成功实施CSRF攻击,需要满足以下几个条件:

  1. 用户已经登录目标网站。 这是基础,没有登录,哪来的身份冒充?
  2. 用户没有退出目标网站。 登录状态需要保持,不然Cookie就失效了。
  3. 存在可被利用的漏洞。 目标网站的某些请求没有做CSRF防护。
  4. 用户访问了恶意网站或链接。 这是触发攻击的入口。

CSRF攻击的例子

假设银行网站有一个转账接口:

POST /transfer.do HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=abcdefg; ...
Content-Type: application/x-www-form-urlencoded

account=hacker&amount=1000

如果这个接口没有做CSRF防护,攻击者就可以构造一个恶意网页:

<!DOCTYPE html>
<html>
<head>
  <title>免费领取游戏皮肤</title>
</head>
<body>
  <h1>恭喜你获得免费游戏皮肤!</h1>
  <img src="http://bank.example.com/transfer.do?account=hacker&amount=1000" width="0" height="0">
</body>
</html>

当你访问这个恶意网页时,浏览器会自动向bank.example.com发送一个GET请求,这个请求携带了你的Cookie,服务器一看,是你发起的,就执行了转账操作。

或者,攻击者可以使用form表单来发起POST请求:

<!DOCTYPE html>
<html>
<head>
  <title>免费领取游戏皮肤</title>
</head>
<body>
  <h1>恭喜你获得免费游戏皮肤!</h1>
  <form action="http://bank.example.com/transfer.do" method="POST">
    <input type="hidden" name="account" value="hacker">
    <input type="hidden" name="amount" value="1000">
    <input type="submit" value="领取">
  </form>
  <script>
    document.forms[0].submit(); // 自动提交表单
  </script>
</body>
</html>

这个网页会自动提交表单,发起POST请求,效果和GET请求一样。

如何防御CSRF?

防御CSRF的常用方法有两种:

  1. SameSite Cookie
  2. CSRF Token

SameSite Cookie: 防御CSRF的第一道防线

SameSite Cookie是Cookie的一个属性,它可以控制Cookie在跨站请求中的发送行为。SameSite属性有三个值:

  • Strict: Cookie只会在同站请求中发送。也就是说,只有当你访问bank.example.com的页面时,Cookie才会被发送。
  • Lax: Cookie在同站请求和部分跨站请求中发送。例如,当用户通过链接、预加载请求或GET表单导航到目标站点时,Cookie会被发送。
  • None: Cookie在所有请求中都会发送,包括跨站请求。如果要使用None,必须同时设置Secure属性,表示Cookie只能通过HTTPS连接发送。

SameSite Cookie可以有效地防御CSRF攻击,特别是Strict模式。但是,它也有一些缺点:

  • 兼容性问题。 较旧的浏览器可能不支持SameSite属性。
  • Lax模式的局限性。 Lax模式只允许GET请求携带Cookie,如果你的接口使用POST请求,Lax模式就无法防御CSRF攻击。
  • 无法防御所有类型的CSRF攻击。 例如,如果攻击者控制了目标网站的一个子域名,就可以通过设置Cookie的domain属性来绕过SameSite限制。

CSRF Token:终极解决方案

CSRF Token是一种更加可靠的防御CSRF攻击的方法。它的原理是:

  1. 服务器在生成页面时,生成一个随机的Token,并将它保存在Session中。
  2. 服务器将Token嵌入到HTML表单中。
  3. 当用户提交表单时,浏览器会将Token一起发送到服务器。
  4. 服务器验证Token是否和Session中的Token一致。如果一致,说明请求是合法的;否则,说明请求是CSRF攻击。

CSRF Token可以有效地防御CSRF攻击,因为它要求攻击者不仅要冒充用户的身份,还要知道服务器生成的Token。由于Token是随机的,攻击者很难猜测到。

CSRF Token的实现

下面是一个简单的CSRF Token的实现:

import uuid
from flask import Flask, session, render_template, request

app = Flask(__name__)
app.secret_key = 'your_secret_key'  # 一定要设置一个复杂的密钥

@app.route('/')
def index():
  # 生成 CSRF Token
  session['csrf_token'] = str(uuid.uuid4())
  return render_template('index.html', csrf_token=session['csrf_token'])

@app.route('/transfer', methods=['POST'])
def transfer():
  # 验证 CSRF Token
  csrf_token = request.form.get('csrf_token')
  if csrf_token != session.get('csrf_token'):
    return 'CSRF Attack Detected!', 403

  account = request.form.get('account')
  amount = request.form.get('amount')

  # 执行转账操作
  print(f"Transferring {amount} to {account}")
  return 'Transfer Successful!'

if __name__ == '__main__':
  app.run(debug=True)

index.html模板:

<!DOCTYPE html>
<html>
<head>
  <title>Transfer Money</title>
</head>
<body>
  <h1>Transfer Money</h1>
  <form action="/transfer" method="POST">
    <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
    <label for="account">Account:</label><br>
    <input type="text" id="account" name="account"><br><br>
    <label for="amount">Amount:</label><br>
    <input type="text" id="amount" name="amount"><br><br>
    <input type="submit" value="Transfer">
  </form>
</body>
</html>

在这个例子中,我们使用Flask框架来生成CSRF Token,并将它嵌入到HTML表单中。当用户提交表单时,服务器会验证Token是否和Session中的Token一致。

CSRF Token的注意事项

  • Token的随机性。 Token必须是随机的,否则攻击者可以通过猜测Token来绕过防护。
  • Token的唯一性。 每个用户的Token应该是不一样的。
  • Token的有效期。 Token应该有一个有效期,过期后需要重新生成。
  • Token的保密性。 Token不能泄露给攻击者。
  • Token的存储。 Token应该存储在Session中,而不是Cookie中。

CSRF Token的绕过

即使使用了CSRF Token,仍然存在被绕过的风险。下面是一些常见的CSRF Token绕过方法:

  1. Token泄露。 如果Token通过GET请求传递,或者Token出现在URL中,攻击者可以通过Referer头或者服务器日志来获取Token。
  2. Token预测。 如果Token的生成算法不够安全,攻击者可以通过分析Token的规律来预测Token。
  3. Token重放。 如果Token没有有效期,攻击者可以重复使用同一个Token来发起多次攻击。
  4. Token绕过。 有些网站的CSRF防护机制存在漏洞,攻击者可以通过修改请求参数或者请求头来绕过防护。
  5. CORS配置错误。 如果网站的CORS配置不正确,攻击者可以通过跨域请求来获取Token。

SameSite Cookie 和 CSRF Token 的绕过姿势

好了,重头戏来了!让我们看看有哪些骚操作可以绕过这些防御措施。

SameSite Cookie 的绕过

  • 子域名攻击: 如果你的站点example.com设置了SameSite=Strict,但是你的子域名evil.example.com存在漏洞,攻击者可以利用这个漏洞来设置domain=.example.com的Cookie,从而绕过SameSite限制。
  • Punycode 攻击: 攻击者注册一个看起来很像你域名的域名,比如xn--exmple-cua.com(看起来像example.com),然后诱导用户访问,由于浏览器认为这是不同的站点,SameSite限制可能不会生效。
  • 老旧浏览器: 如果用户使用的浏览器不支持SameSite属性,那么SameSite限制就形同虚设。
  • 302跳转: 某些浏览器在302跳转的时候,可能会忽略SameSite的限制。 攻击者可以利用这个特性,先让用户访问一个自己的页面,然后通过302跳转到目标站点,从而携带Cookie。

CSRF Token 的绕过

  • Token 泄露:
    • Referer 泄露: 如果你的服务器记录了Referer头,并且Token是通过GET请求传递的,那么攻击者就可以从Referer头中获取Token。
    • 日志泄露: 如果你的服务器日志记录了包含Token的URL,那么攻击者就可以从日志中获取Token。
    • XSS 攻击: 如果你的网站存在XSS漏洞,攻击者可以通过XSS漏洞来获取用户的Token。
  • Token 可预测:
    • 时间戳: 有些网站使用时间戳作为Token的一部分,攻击者可以通过分析时间戳的规律来预测Token。
    • 弱随机数: 如果你的Token生成算法使用了弱随机数生成器,攻击者可以通过分析随机数的规律来预测Token。
  • Token 重放:
    • 没有有效期: 如果你的Token没有有效期,攻击者可以重复使用同一个Token来发起多次攻击。
    • 有效期过长: 如果你的Token有效期过长,攻击者有足够的时间来获取Token并利用它发起攻击。
  • Token 绕过:
    • 参数污染: 有些网站的CSRF防护机制只验证了第一个csrf_token参数,攻击者可以通过添加第二个csrf_token参数来绕过防护。
    • 大小写绕过: 有些网站的CSRF防护机制没有区分大小写,攻击者可以通过修改csrf_token参数的大小写来绕过防护。
    • 空值绕过: 有些网站的CSRF防护机制允许csrf_token参数为空,攻击者可以通过将csrf_token参数设置为空来绕过防护。
  • CORS 配置错误:
    • 允许任意来源: 如果你的网站的CORS配置允许任意来源的跨域请求,攻击者可以通过跨域请求来获取Token。
    • 允许携带 Cookie: 如果你的网站的CORS配置允许跨域请求携带Cookie,攻击者可以通过跨域请求来冒充用户发起攻击。

代码演示: Token 泄露 (Referer)

假设我们的转账接口是这样的:

GET /transfer.do?account=hacker&amount=1000&csrf_token=xxxxxxxx

攻击者的恶意网站:

<!DOCTYPE html>
<html>
<head>
  <title>领取奖励</title>
</head>
<body>
  <h1>点击领取你的专属奖励!</h1>
  <img src="http://bank.example.com/transfer.do?account=hacker&amount=1000&csrf_token=get_token_from_referer" width="0" height="0">
</body>
</html>

在这个例子中,攻击者假设服务器会从Referer头中获取Token,并替换csrf_token参数的值。如果服务器没有正确处理Referer头,攻击就可能成功。当然,现在浏览器通常会默认限制Referer头,使其不包含完整路径,但仍然存在一些场景可能泄露。

代码演示: CORS 配置错误

假设你的服务器CORS配置如下:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

攻击者就可以通过以下代码来获取用户的Token:

fetch('http://bank.example.com/get_token', {
  credentials: 'include'
})
.then(response => response.text())
.then(token => {
  // 使用 token 发起 CSRF 攻击
  fetch('http://bank.example.com/transfer', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: `account=hacker&amount=1000&csrf_token=${token}`,
    credentials: 'include'
  });
});

由于CORS配置允许任意来源的跨域请求,并且允许携带Cookie,攻击者就可以通过这个JavaScript代码来获取用户的Token,并利用它发起CSRF攻击。

如何防止CSRF Token被绕过?

  • 使用 POST 请求: 尽量使用POST请求,避免将Token放在URL中。
  • 验证 Referer 和 Origin 头: 验证请求的Referer和Origin头,确保请求来自可信的来源。
  • 严格的 CORS 配置: 只允许可信的域名进行跨域请求,并且不要允许跨域请求携带Cookie。
  • Token 和用户绑定: 将Token和用户的Session绑定,确保Token只对当前用户有效。
  • 双重提交 Cookie: 除了在表单中包含Token外,还在Cookie中设置一个Token,并在服务器端验证这两个Token是否一致。
  • 使用同步器令牌模式(Synchronizer Token Pattern): 这是一种更安全的CSRF防御模式,它使用服务器端存储的唯一令牌来验证每个请求。

最佳实践

  1. 同时使用 SameSite Cookie 和 CSRF Token。 SameSite Cookie可以作为第一道防线,CSRF Token可以作为第二道防线。
  2. 定期更新 CSRF Token。 建议每次请求都更新CSRF Token,或者至少每隔一段时间更新一次。
  3. 对所有敏感操作都进行 CSRF 防护。 包括修改密码、修改邮箱、转账等操作。
  4. 进行安全审计和渗透测试。 定期对网站进行安全审计和渗透测试,及时发现和修复漏洞。
  5. 教育用户安全意识。 提醒用户不要随意点击不明链接,不要在公共场合使用公共WiFi,不要轻易泄露个人信息。

总结

CSRF攻击是一种常见的Web安全威胁,攻击者可以利用用户的身份来发起恶意请求。防御CSRF攻击的方法有很多,包括SameSite Cookie和CSRF Token。但是,这些防御方法也存在被绕过的风险。为了有效地防御CSRF攻击,我们需要同时使用多种防御方法,并且定期进行安全审计和渗透测试。

希望今天的讲座对大家有所帮助!记住,安全无小事,防患于未然!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注