Skip to content
快速预览

CSRF攻击

✍️ w 🕒 2023-06-15 06:21:46(a year ago) 🔗 F.开发中杂七杂八 CSRF 网站安全 cookie

CSRF(Cross-site request forgery)攻击,也称为 跨站请求伪造 ,是一种利用用户已经登录的身份,在用户不知情的情况下,通过伪造请求来执行恶意操作的攻击方式。攻击者通过构造一个恶意网站或者发送恶意邮件等方式,诱导用户点击链接或者访问网站,从而触发攻击。一旦用户在已经登录的情况下访问了恶意网站,攻击者就可以利用用户的身份,发送恶意请求,执行一些危害性操作,比如修改用户密码、转账等。由于攻击者无法获取用户的密码等敏感信息,所以这种攻击方式很难被用户察觉

Cookie 容易受到 CSRF 攻击。甚至可以理解为没有 Cookie 没有 CSRF 攻击。只要身份验证不是自动的(比如在使用 Cookies 的浏览器中) ,就不必担心 CSRF 攻击

利用 cookie 完成一次 CSRF 攻击,Cookie标准规范规定,如果某域名下设置了Cookie,不管你是直接跳转到该域名,或是加载了该域名的某些资源(例如图片),或是向该域名发送POST请求,亦或将其嵌入iframe,浏览器访问该域名的每次请求,都将带上这个Cookie。

如果 cookie 中 保存了用户的认证信息,但是却没做 限制。 例如你的网站是 A 使用了cookie 但没做 CSRF 防范,B 网站的开发通过调用你 A 网站的接口,此时发送的瞬间 虽然是 B 网站发起的 A 请求,但是 A 请求自动携带了 cookie 完成一次请求。说到这里可能有点懵在举个例子

此时 你 A 网站有个请求是GET ,www.aaa.com/user?removeId='1000' 删除用户 id 是1000的用户,原本这个操作在A网站时候你已经登录了 cookie 信息也已经在浏览器中了,因此你发送请求时候自然帮你携带了cookie 完成了这一系列的操作,现在 B 网站的人知道这个接口请求传参的规则,他在自己网站只要伪造这个 get 请求即可,他创建了 一个 img 标签 <img src="www.aaa.com/user?removeId='1000'"> 当用户进来的时候图片发送了请求获取到了你的cookie 完成了一次删除

设想如果有一个钓鱼网站,他嵌入某个已知具备CSRF 网站的漏洞 发起这种嵌入的请求,盗取你某个网站你所具备权限发起请求(如果是购物网站发起转账)

那是否设置为 post 请求能解决问题,钓鱼网站 依旧可以利用 from 表单发起 post 请求但你的A网站

html
<form action="www.aaa.com/user" method=POST>
    <input type="hidden" name="removeId" value="1000" />
</form>
<script> document.forms[0].submit(); </script>

这个过程就是钓鱼网站B 例用浏览器会默认把存储的cookie一起发送到网站A,网站A识别用户的依据就是通过cookie里的sessionid或其他标识

整个过程攻击者其实并没有获取用户的 cookie,一切都是用户自己操作的,攻击者拿不到(如果设置了httponly)也不需要拿,完全都是浏览器帮助整个过程进行发送

攻击的几种形式

  1. 隐藏表单攻击:攻击者在恶意网站上构造一个表单,通过隐藏表单的方式,诱导用户在已经登录的情况下提交表单,从而触发攻击。

  2. URL 攻击:攻击者在恶意网站上构造一个 URL,通过诱导用户点击链接的方式,触发攻击。

  3. 图片攻击:攻击者在恶意网站上构造一个图片链接,通过诱导用户点击图片的方式,触发攻击。

  4. 脚本攻击:攻击者在恶意网站上构造一个 JavaScript 脚本,通过诱导用户访问网站的方式,触发攻击。

  5. Flash 攻击:攻击者在恶意网站上构造一个 Flash 文件,通过诱导用户访问网站的方式,触发攻击。

实现一个最简单的csrf

先关闭了 SameSite 让任何页面发起都可以携带cookie

js
// 导入express
const express = require('express')

// 导入cookie 中间件
const cookieParser = require('cookie-parser')

// 注册
const app = express()
app.use(cookieParser())

// 创建一个cookie 路由
app.get('/set-cook', (req, res) => {
	// 使用cookie key name value 是wcy  ,设置有效时间为60s
	const cookieOptions = {
		maxAge: 60 * 1000,
		httpOnly: true,
		sameSite: 'none',
		secure: true,
	}
	res.cookie('name', 'wcy', cookieOptions)
	// 设置多个key
	res.cookie('age', '18', cookieOptions)

	res.cookie('sex', '1') // 会在浏览器关闭的时候, 销毁 没设置时间的话

	res.send('123')
})

app.get('/', (res, req) => {
	console.log(res.cookies)
	req.send('789')
})
app.listen(3000)

用一个攻击 5500作为端口页面 通过 img 请求 完成某种操作,可以看到 在 789 的console.log 会打印出来cookie 携带过去

html
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<img src="http://localhost:3000/" />

	</body>
</html>

防范

  1. 对于防范 CSRF 攻击,2016年开始,Chrome 51版本对Cookie新增了一个SameSite属性,用来防止CSRF攻击。SameSite cookie规范于2016年起草。对于发送cookie我们有了更多的控制权:现在可以明确指定每个cookie是否被发送。规范引入了同站/跨站cookie的概念,如果浏览器访问A域名,请求服务端也是A域名,这些cookie就叫同站cookies(same-site cookies),如果浏览器访问A域名,请求服务端是B域名,这些cookie就叫跨站cookies(cross-site cookies)。我们可以针对实际情况将一些关键的 Cookie 的 sameSite 设置为 Strict 或者 Lax 模式,这样在跨站点请求时,这些关键的 Cookie 就不会被发送到服务器,从而使得黑客的 CSRF 攻击失效在实际开发中,你必须手动指定 SameSite=Lax 或者 SameSite=Strict,来能使用这项特性加固安全但大部分的开发并没有去设置Google决定推进这项特性的使用。他们决定修改世界上最多人使用的浏览器,从80版本开始的增量式更好 Cookies 规范,并将默认设置改为 Lax。但在这之前的浏览器默认设置依旧是SameSite=None
    • Strict 时,最为严格只有从主站点设置的请求才能够携带相应的 Cookie。也就是说,如果当前用户在主站点登录,并设置了一个带 Cookie 的会话,那么这个会话只能在访问主站点的请求中携带,所有其他的第三方请求都不能够携带该 Cookie。举个例子,假设当前用户在主站点 example.com 上登录,该站点设置了一个 Cookie,而在用户尝试访问子域名 blog.example.com 时,该请求就不能够携带来自 example.com 的 Cookie 或者 当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
    • Lax 时,主站点设置的 Cookie 可以被从该站点链接的 GET 请求所带例如 连接 预加载 GET表单等,而对于受限的非 GET 请求通过 img、iframe 等标签加载的 URL,Cookie 则不会被携带例如在第三方站点中使用 Post 方法,这些场景都不会携带 Cookie。举个例子,假设当前用户在主站点 example.com 上登录,该站点设置了一个 Cookie,而在用户尝试访问子域名 blog.example.com 时,如果这是一个 GET 请求,那么它可以携带来自 example.com 的 Cookie,但其他类型的请求则不能够携带 Cookie。
    • None 时,任何请求都可以携带来自主站点设置的 Cookie,但是必须同时设置 Secure 属性,即 Secure 属性必须为 true。这意味着,Cookie 只能在 HTTPS 连接中使用,而不能在非 HTTPS 连接中使用。举个例子,假设当前用户在主站点 example.com 上登录,该站点设置了一个 Cookie,而在用户尝试通过一个 HTTPS 连接访问非主站点的网站或路径时,它就可以携带来自 example.com 的 Cookie,但在非 HTTPS 连接中则不能携带。

IOS 12 的 Safari 以及老版本的一些 Chrome 会把 SameSite=none 识别成 SameSite=Strict,所以服务端必须在下发 Set-Cookie 响应头时进行 User-Agent 检测,对这些浏览器不下发 SameSite=none 属性,这在老版本中产生不兼容问题,要想解决参考

  1. 针对一些关键的业务操作,需要验证码进行校验,基本上可以完全避免CSRF攻击。如银行转账或支付的时候都要求输入密码或者验证码。该方案不足的地方在于比较影响用户的使用体验。所以,只有对于一些私密,以及较为重要的操作,才尽量使用二次安全验证,比如短信,以及动态口令,或者是验证码的安全效验

  2. 验证请求的来源站点,Referer 是 HTTP 请求头中的一个字段,记录了该 HTTP 请求的来源地址,可以通过 Referer 告诉服务器 HTTP 请求的来源,在服务器端验证请求头中的 Referer 并不是太可靠,因此标准委员会又制定了 Origin 属性,在一些重要的场合,比如通过 XMLHttpRequest、Fecth 发起跨站请求或者通过 Post 方法发送请求时,都会带上 Origin 属性。Origin 属性只包含了域名信息,并没有包含具体的 URL 路径,这是 Origin 和 Referer 的一个主要区别。在这里需要补充一点,Origin 的值之所以不包含详细路径信息,是有些站点因为安全考虑,不想把源站点的详细路径暴露给服务器。服务器的策略是优先判断 Origin,如果请求头中没有包含 Origin 属性,再根据实际情况判断是否使用 Referer 值。

图 4

  1. CSRF Token,在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中,在浏览器端如果要发起转账的请求,那么需要带上页面中的 CSRF Token,然后服务器会验证该 Token 是否合法。如果是从第三方站点发出的请求,那么将无法获取到 CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求

  2. Cookie-to-header 令牌模式是每个会话设置一个 Cookie,让 JavaScript 读取该 Cookie 并设置一个定制的 HTTP 头(通常称为 X-CSRF-TOKEN 或 X-XSRF-TOKEN 或只是 XSRF-TOKEN)。任何请求都会发送头(由 Javascript 设置)和 cookie (由浏览器设置为标准 HTTP 头) ,然后服务器可以检查 X-CSRF-TOKEN 头中的值与 cookie 头中的值是否匹配。这个想法是只有运行在同一个域上的 JavaScript 才能访问这个 cookie,因此来自另一个域的 JavaScript 不能将这个头设置为正确的值(假设页面不容易受到 XSS 的攻击,而 XSS 会提供对这个 cookie 的访问)。即使是假的链接(例如钓鱼邮件)也不会起作用,因为即使它们看起来来自正确的域,只有 cookie 会被设置,而不是 X-CSRF-TOKEN 头

  3. 使用 token 替代 cookie, token 在客户端也有一份,通常是存储在浏览器的本地存储(如 localStorage 或 sessionStorage)中。但是与 cookie 不同,token 不会自动发送给服务器,而是需要手动添加到请求头或请求参数中。这样,攻击者就无法利用跨站点请求来获取 token。

其他参考

解决 Chrome Cookie SameSite跨站限制

如何解决 Chrome Cookie Same-Site 跨域问题

SameSite cookie 的说明

chrome 默认 cookie 的 SameSite=Lax,导致 http 模式的站点的第三方 cookie 无法进行跨域传输

防止CSRF攻击三种方法

Difference between CSRF and X-CSRF-Token CSRF 与 X-CSRF 令牌的区别

https://tech.meituan.com/2018/10/11/fe-security-csrf.html ???? 后续看

Released under the MIT License.