这一次彻底搞懂CSRF与XSS

2,020 阅读9分钟

CSRF攻击

以一个具体的银行转账场景来说明:

第一步:用户正常登录银行网站

用户打开浏览器,访问 https://bank.com,输入用户名和密码登录成功。

这时候,银行服务器会返回一个 Set-Cookie 响应头:

Set-Cookie: sessionId=abc123xyz; HttpOnly; Secure; SameSite=None

浏览器会把这个 Cookie 存储起来,关联到 bank.com 这个域名。


第二步:用户继续浏览,保持登录状态

用户可能在银行网站查看账户余额、交易记录等,这个 sessionId Cookie 一直有效,证明用户的身份。


第三步:用户访问恶意网站(关键)

用户在同一个浏览器中,打开了一个新标签页,可能是:

  • 点击了钓鱼邮件里的链接
  • 访问了被植入恶意代码的论坛
  • 或者只是随便浏览到了 https://evil.com

重点:此时用户并没有退出银行网站,sessionId Cookie 仍然有效。


第四步:恶意网站构造攻击请求

evil.com 的页面里包含了恶意代码,比如:

方式一:使用隐藏表单

<form id="hack" action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker_account" />
  <input type="hidden" name="amount" value="10000" />
</form>
<script>
  document.getElementById('hack').submit();
</script>

方式二:使用 AJAX 请求

fetch('https://bank.com/transfer', {
  method: 'POST',
  credentials: 'include',  // 关键:携带 Cookie
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    to: 'attacker_account',
    amount: 10000
  })
});

方式三:使用 img 标签(GET 请求)

<img src="https://bank.com/transfer?to=attacker_account&amount=10000" style="display:none">

第五步:浏览器自动携带 Cookie(核心机制)

当恶意网站发起对 https://bank.com/transfer 的请求时:

  1. 浏览器检测到目标域名是 bank.com
  2. 自动从 Cookie 存储中取出 bank.com 的 Cookie
  3. 将 sessionId=abc123xyz 附加到请求头中

实际发送的请求头像这样:

POST /transfer HTTP/1.1
Host: bank.com
Cookie: sessionId=abc123xyz
Content-Type: application/json

{"to":"attacker_account","amount":10000}

注意:虽然这个请求是从 evil.com 发起的,但 Cookie 仍然会被携带!


第六步:银行服务器处理请求

银行服务器收到请求后:

  1. 检查 Cookie 中的 sessionId
  2. 验证发现 sessionId=abc123xyz 是有效的
  3. 认为这是合法用户的操作
  4. 执行转账:从用户账户转 10000 元到攻击者账户
  5. 返回响应:转账成功

服务器完全不知道这个请求是从恶意网站发起的,因为它只看到了合法的 sessionId。


第七步:攻击完成

  • 用户的钱被转走了
  • 用户可能毫无察觉(evil.com 可以隐藏表单,用户看不到)
  • 恶意网站虽然无法读取响应内容(同源策略限制),但转账已经完成

为什么这个攻击能成功?

关键点在于:

  1. Cookie 的自动携带机制:浏览器默认会携带目标域名的 Cookie
  2. 服务器只验证 Cookie:没有其他验证机制来确认请求来源
  3. 跨域请求允许:虽然同源策略限制读取响应,但不限制发送请求

如何防御?

最有效的方法是使用 CSRF Token:

// 银行网站在表单中添加随机 Token
<form action="/transfer" method="POST">
  <input type="hidden" name="csrf_token" value="random_xyz_789" />
  <input name="to" />
  <input name="amount" />
</form>

// 服务器验证
if (request.csrf_token !== session.csrf_token) {
  return "非法请求";
}

因为恶意网站无法读取银行页面的内容(同源策略保护),所以它无法获取 CSRF Token,攻击就会失败。

其他防御方法:

  • 设置 SameSite=Strict/Lax Cookie 属性
  • 验证 Referer/Origin 请求头
  • 对敏感操作使用二次验证(如短信验证码)

这就是 CSRF 攻击的完整流程。核心就是利用了浏览器会自动携带 Cookie 的特性,而服务器仅依赖 Cookie 来验证身份的漏洞。

一、什么是CSRF攻击?

CSRF指的是跨站请求伪造,是一种挟制用户在当前已经登录的Web应用程序上执行非本意操作的攻击方法。CSRF利用的是:一旦用户通过网站服务的身份认证,网站就完全信任该用户,受害者持有的权限级别决定了CSRF攻击的影响范围。

二、CSRF攻击流程

主要步骤包含下列六个步骤:

  1. 用户浏览并登陆信任网站A。
  2. 网站A验证通过后,在用户处产生A的Cookie。
  3. 用户在没有退出网站A的情况下,访问了危险网站B。
  4. 危险网站B要求用户访问第三方站点A,并发出了一个请求。
  5. 用户根据危险网站B的要求,携带着A的cookie对网站A进行了请求。
  6. 网站A并不知道这个请求是用户发出的还是危险网站B发出的,但是因为这个cookie的存在,A会处理这个请求,从而危险网站B达到了自己获取用户信息的目的。

三、常见的CSRF攻击类型

GET类型的CSRF攻击

这种方式可以通过向受害者访问的网页中注入一个img标签,这个标签的src属性指向共计发出者的服务器,这样攻击者就会获取到含有受害者登录信息的一次跨域请求。

<img src="http://bank.example/withdraw?amount=10000&for=hacker" > 

POST类型的CSRF

这种类型的CSRF主要利用的是一个隐藏的input表单,当受害者访问该页面的时候,这个表单会自动提交获取到的用户信息。

<form action="http://bank.example/withdraw" method=POST>
    <input type="hidden" name="account" value="xiaoming" />
    <input type="hidden" name="amount" value="10000" />
    <input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script> 

链接类型的CSRF

链接类型的CSRF需要用户点击链接才会触发,这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户点击。

<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
    重磅消息!!
<a/>

四、CSRF攻击有什么特点?

  • CSRF通常发生在第三方域名。
  • CSRF攻击者不能获取到Cookie等信息,只是使用。

五、如何防范CSRF攻击?

CSRF Token

token验证的CSRF防御机制是公认最合适的方案。CSRF Token的防护策略主要包括以下三个步骤:

  1. 将CSRF Token输出到页面中。

首先,用户打开页面的时候,服务器需要给用户生成一个Token,这个Token一般是随机字符串和时间戳通过加密算法加密后的结果,这个Token不能保存在cookie中,否则可能会被攻击者冒用,为了安全起见,这个Token可以存在服务器的Session中,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。

  1. 页面提交的请求需要携带Token。

对于GET请求,Token将附加在请求地址之后,对于POST请求来说,可以在form表单中加入一个隐藏的表单域input,这个input的value值中需要携带Token。

  1. 服务端验证Token是否正确。

当用户再次访问服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串和时间戳,如果加密字符串一致且时间未过期,则判断这个Token是有效的。

验证码

CSRF攻击往往是在用户不知情的情况下构造了网络请求,而验证码会强制用户必须与应用进行交互,才能完成最终请求,但是验证码不是万能的,不能给网站的所有操作都加上验证码。

Referer check

根据HTTP协议,在HTTP头部字段中有一个字段交referer,它记录了该HTTP请求的来源地址,通过Referer check可以验证目标请求是否来自合法的源。

cookie的SameSite属性设置为strict

设置了这个属性,会使得在跨站情况下,任何情况下都不会发送cookie。

XSS攻击

一、什么是XSS攻击?

XSS攻击即Cross Site Script可以译为跨站脚本攻击,为了和CSS区分开来,所以叫做XSS,XSS攻击指的是攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。攻击者对客户端网页注入的恶意脚本一般包括JS,有时也会包含HTML。XSS攻击的共同点是将一些隐私数据像cookie、session发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。

二、XSS攻击的类型

反射型(非持久型)

反射型XSS把用户输入的数据反射给浏览器,这种攻击的港式需要攻击者诱骗用户点击一个恶意连接或者提交一个表单,或者进入一个恶意网站时,将恶意脚本注入攻击者的网站。当用户点击恶意链接时,页面跳转到攻击者预先准备的页面,会发现在攻击者的页面执行了JS脚本,这样产生了反射型XSS攻击,攻击者可以注入任意的恶意脚本进行攻击,可能注入恶作剧脚本,也可以注入获取用户隐私数据的脚本。

存储型(持久型)

存储型XSS会把用户输入的数据存储在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行,这种XSS攻击具有很强的稳定性。一个比较常见的场景是攻击者在论坛上写下一篇包含恶意JS的评论,这个评论发表后,所有访问这个评论的用户都会执行这段恶意的JS代码。

基于DOM的XSS(非持久型)

DOM型XSS是基于DOM文档对象模型的一种漏洞,DOM型XSS是一种特殊的反射型XSS,区别在于DOM型XSS并不会和后端进行交互,首先客户端的脚本程序可以通过DOM动态的检查和修改页面内容,当攻击者可以控制一些DOM对象,输入一些恶意JS脚本,而客户端脚本并没有对用户输入的内容进行有效的过滤就执行这些脚本,就会导致DOM型XSS漏洞。

三、XSS攻击的防范方法

1. HttpOnly防止窃取Cookie

将Cookie的属性HttpOnly置为true,浏览器将禁止页面的JS代码访问这个Cookie,这样能够阻止XSS攻击后的Cookie劫持攻击。

2. 输入检查

不要相信用户的任何输入,对于用户的任何输入都要进行检查、过滤和转译。建立可信任的字符和HTML标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码,在XSS防御中,输入检查一般是检查用户输入的数据中是否包含左尖括号或者右尖括号等特殊字符,有则对特殊字符进行过滤或编码来防止XSS攻击。

3. 输出检查

用户的输入会存在问题,服务端的输出也会存在问题,一般来说,除了富文本的输出外,在变量输出到HTML页面时,可以使用编码或者转译的方式来防御XSS攻击。

参考文档