前端安全
(一)加密
目前在项目中应用较多的加密算法可以分为以下几个类别:
- 散列/哈希函数算法
- 对称加密算法
- 非对称加密算法
- 组合加密技术
1. 散列函数相关
与其说散列函数是一种加密算法,不如说它是一种数据特征值提取算法。常用的散列函数包括:MD5、SHA1、SHA2(包括 SHA128、SHA256 等)散列函数的应用很广。以 MD5 算法为例,其具有以下特点:
- 压缩性:任意长度的数据,算出的 MD5 值长度都是固定的。
- 容易计算:从原数据计算出 MD5 值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改 1 个字节,所得到的 MD5 值都有很大区别。
- 强抗碰撞:已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的。散列算法的过程是不可逆的,可以通过数据得到其特征值,但是没办法根据特征值还原数据。
① 应用场景
Ⅰ. 文件一致性检验
假如有一份 1G 大小的文件需要发送给用户,但数据传输通道不可靠,那么可以计算出文件的散列值(为保证散列值不被窃取,可用其他加密方式将散列值加密并发送给用户),用户收到文件后自行计算其散列值,计算结果和收到的散列值相同则认为文件没有被篡改,否则说明文件遭到了篡改。
Ⅱ. 文件秒传
以百度云秒传功能为例,几 G 大小的视频可能不到一分钟就成功上传到网盘,其原理并非百度的服务写入速度或用户网速有多快,而是依赖于以下流程:

Ⅲ. 保存用户密码
为了防止被黑客脱裤,不建议在数据库明文保存用户密码,而是保存密码的散列值。每次用户登录的时候,用散列函数对用户输入的密码进行计算,得到结果后和数据库里的散列值进行比对,一致则登录成功。当用户登录某个已注册网站时,在忘记密码的情况下需要重置密码,此时网站会给用户发一个随机密码或者一个邮箱激活链接,而不是用户之前的密码,这是因为哈希算法是不可逆的。
黑客脱裤成功拿到用户密码散列值后,可能拿彩虹表对用户密码的散列值进行暴力匹配,一些较简单的密码如“123456”、生日组合等,它们的散列值是固定的,因此会被黑客匹配上。为了应对这种情况可以对用户密码“加盐”,将“盐”和散列值一起保存。“123456”对应的散列值在彩虹表中有存储,但是“123456salt-asdgasdgsagasazsdasdf233zxcsdf”,在彩虹表中就没有了,这样黑客就没办法从彩虹表中获取常见密码的原文了!
② 算法分类
Ⅰ. 哈希算法(Hash)
哈希(Hash)是将目标文本转换成具有固定长度的字符串(或叫做消息摘要)。 当输入发生改变时,产生的哈希值也完全不同。基于哈希算法的特性,其适用于该场景:被保护数据仅仅用作比较验证且不需要还原成明文形式。比较常用的哈希算法是 MD5 和 SHA1。
现在,对于简单的哈希算法的攻击方法主要有:寻找碰撞法和穷举法。所以,为了保证数据的安全,可以在哈希算法的基础上进一步的加密,常见的方法有:加盐、慢哈希、密钥哈希、XOR 等。
Ⅱ. 加盐(Adding Salt)
加盐加密是一种对系统登录口令的加密方式,它实现的方式是将每一个口令和一个叫做盐(salt)的 n 位随机数相关联。
为了方便理解,这里引用这篇文章进行说明:使用 salt 加密,它的基本思路如下。
- 用户注册时,在密码上撒一些盐。生成一种味道,记住味道。
- 用户再次登陆时,在输入的密码上撒盐,闻一闻,判断是否和原来的味道相同,相同就让你吃饭。
由于验证密码时和最初散列密码时使用相同的盐值,所以 salt 存储在数据库。并且这个值是由系统随机产生的,而非硬编码。这就保证了所要保护对象的机密性。
注册时:
- 用户注册,系统随机产生 salt 值。
- 将 salt 值和密码连接起来,生产 Hash 值。
- 将 Hash 值和 salt 值分别存储在数据库中。
登陆时:
- 系统根据用户名找到与之对应的密码 Hash。
- 将用户输入密码和 salt 值进行散列。
- 判断生成的 Hash 值是否和数据库中 Hash 相同。
PS: 其实图中的这种登录也是不安全的,原因是后面提到的盐值复用
使用加盐加密时需要注意以下两点:
- 短盐值(Short Slat)
如果盐值太短,攻击者可以预先制作针对所有可能的盐值的查询表。例如,如果盐值只有三个 ASCII 字符,那么只有 95x95x95=857,375 种可能性,加大了被攻击的可能性。还有,不要使用可预测的盐值,比如用户名,因为针对某系统用户名是唯一的且被经常用于其他服务。
- 盐值复用(Salt Reuse)
在项目开发中,有时会遇到将盐值写死在程序里或者只有第一次是随机生成的,之后都会被重复使用,这种加盐方法是不起作用的。以登录密码为例,如果两个用户有相同的密码,那么他们就会有相同的哈希值,攻击者就可以使用反向查表法对每个哈希值进行字典攻击,使得该哈希值更容易被破解。
所以正确的加盐方法如下:
(1)盐值应该使用加密的安全伪随机数生成器(Cryptographically Secure Pseudo-Random Number Generator,CSPRNG)产生,比如 C 语言的 rand()
函数,这样生成的随机数高度随机、完全不可预测;
(2)盐值混入目标文本中,一起使用标准的加密函数进行加密;
(3)盐值要足够长(经验表明:盐值至少要跟哈希函数的输出一样长)且永不重复;
(4)盐值最好由服务端提供,前端取值使用。
Ⅲ. 慢哈希函数(Slow Hash Function)
顾名思义,慢哈希函数是将哈希函数变得非常慢,使得攻击方法也变得很慢,慢到足以令攻击者放弃,而往往由此带来的延迟也不会引起用户的注意。降低攻击效率用到了密钥扩展(key stretching)的技术,而密钥扩展的实现使用了一种 CPU 密集型哈希函数(CPU-intensive hash function)。
如果想在一个 Web 应用中使用密钥扩展,则需要设定较低的迭代次数来降低额外的计算成本。我们一般直接选择使用标准的算法来完成,比如 PBKDF2 或 bcrypt。PHP、斯坦福大学的 JavaScript 加密库都包含了 PBKDF2 的实现,浏览器中则可以考虑使用 JavaScript 完成,否则这部分工作应该由服务端进行计算。
Ⅳ. 密钥哈希
密钥哈希是将密钥添加到哈希加密,这样只有知道密钥的人才可以进行验证。目前有两种实现方式:使用 ASE 算法对哈希值加密、使用密钥哈希算法 HMAC 将密钥包含到哈希字符串中。为了保证密钥的安全,需要将其存储在外部系统(比如一个物理上隔离的服务端)。
即使选择了密钥哈希,在其基础上进行加盐或者密钥扩展处理也是很有必要。目前密钥哈希用于服务端比较多,例如来应对常见的 SQL 注入攻击。
Ⅴ. XOR
XOR 大家都不陌生,它指的是逻辑运算中的异或运算。两个值相同时,返回 false,否则返回 true,用来判断两个值是否不同。
JavaScript 语言的二进制运算,有一个专门的 XOR 运算符,写作 ^。
1 | 1 ^ 1 // 0 |
XOR 运算有一个特性:如果对一个值连续做两次 XOR,会返回这个值本身。这也是其可以用于信息加密的根本。
1 | message XOR key // cipherText |
目标文本 message,key 是密钥,第一次执行 XOR 会得到加密文本;在加密文本上再用 key 做一次 XOR 就会还原目标文本 message。为了保证 XOR 的安全,需要满足以下两点:
(1)key 的长度大于等于 message;
(2)key 必须是一次性的,且每次都要随机产生。
下面以登录密码加密为例介绍下 XOR 的使用:
第一步:使用 MD5 算法,计算密码的哈希;
1 | const message = md5(password); |
第二步:生成一个随机 key 值;
第三步:进行 XOR 运算,求出加密后的 message。
1 | function getXOR(message, key) { |
如上所示,使用 XOR 和一次性的密钥 key 对密码进行加密处理,只要 key 没有泄露,目标文本就不会被破解。
上面说了那么多,问题就来了:我们应该使用什么样的哈希算法呢?
(1)选择经过验证的成熟算法,如 PBKDF2 等;
(2)crypt 的安全版本;
(3)避免使用自己设计的加密算法。
Ⅵ. HMAC
对于 HMAC 算法,和加盐类似,就是 salt 换成后端随机生成的 (好像可以防止重放攻击),然后再通过 HMAC 算法得到摘要。关于 HMAC 算法部分可以详细看这篇文章。
大概过程如下:
- 客户端发出登录请求
- 服务器返回一个随机值,在会话记录中保存这个随机值
- 客户端将该随机值作为密钥,用户密码进行 HMAC 运算,递交给服务器
- 服务器读取数据库中的用户密码,利用密钥做和客户端一样的 HMAC 运算,然后与用户发送的结果比较,如果一致,则用户身份合法。
好处:
- 与自定义的加 salt 算法不同,HMAC 算法对所有哈希算法都通用,无论是 MD5 还是 SHA-1。采用 HMAC 替代我们自己的 salt 算法,可以使程序算法更标准化,也更安全。(摘自雪峰大佬的这篇文章)
- 另外一个就是密码的安全性,由于不知道密钥,所以不可能获取到用户密码
2. 对称加密算法
对称加密采用了对称密码编码技术,它的特点是文件加密和解密使用相同的密钥加密。也就是加密和解密都是用同一个密钥,这种方法在密码学中叫做对称加密算法。

对称加密算法使用起来简单快捷,密钥较短,且破译困难,除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比 DES 的加密性好,而且对计算机功能要求也没有那么高。
常用的对称加密算法包括 DES 算法、AES 算法等。由于对称加密需要一个秘钥,而秘钥在加密者与解密者之间传输又很难保证安全性,所以目前用对称加密算法的话主要是用在加密者解密者相同,或者加密者解密者相对固定的场景。
3. 非对称加密算法
非对称加密算法的特点是,秘钥一次会生成一对,其中一份秘钥由自己保存,不能公开出去,称为私钥,另外一份是可以公开出去的,称为公钥。将原文用公钥进行加密,得到的密文只有对应私钥才可以解密;将原文用私钥加密得到的密文,也只有用对应的公钥才能解密。这样就解决了对称加密的秘钥传输过程无法保证安全的问题,加密者想与多方通信,也只需要公开一份自己的公钥就行了,无需对每个通信者生成多份秘钥。目前应用最广泛的非对称加密是 RSA 加密。
4. 组合加密技术
各种加密算法都有自己的优缺点,很多场景并不能使用一种加密算法就能满足所有需求。现实应用中有很多“混搭”风格的加密技术正在被使用,下面列举一些我所了解到的算法或技术组合。我要说话
Ⅰ. HMAC 加密算法
HMAC 加密算法是一种需要秘钥的散列算法,作为一个使用者我们暂时不去关心它的运算步骤,在使用理解方面,我认为可以将 HMAC 加密就理解为加盐的散列算法,此处的“盐”就相当于 HMAC 算法的秘钥,当然它们内部的计算逻辑是不同的。
HMAC 加密算法主要用于校验客户端的身份,下面引用百度百科中的一个例子:HMAC 的一个典型应用是用在“质疑/应答”(Challenge/Response)身份认证中。
先由客户端向服务器发出一个验证请求。
服务器接到此请求后生成一个随机数并通过网络传输给客户端(此为质疑)。
客户端将收到的随机数提供给 ePass,由 ePass 使用该随机数与存储在 ePass 中的密钥进行 HMAC-MD5 运算并得到一个结果作为认证证据传给服务器(此为响应)。
与此同时,服务器也使用该随机数与存储在服务器数据库中的该客户密钥进行 HMAC-MD5 运算,如果服务器的运算结果与客户端传回的响应结果相同,则认为客户端是一个合法用户安全性浅析由上面的介绍,我们可以看出,HMAC 算法更象是一种加密算法,它引入了密钥,其安全性已经不完全依赖于所使用的 HASH 算法,安全性主要有以下几点保证:(1)使用的密钥是双方事先约定的,第三方不可能知道。由 3.2 介绍的应用流程可以看出,作为非法截获信息的第三方,能够得到的信息只有作为“挑战”的随机数和作为“响应”的 HMAC 结果,无法根据这两个数据推算出密钥。由于不知道密钥,所以无法仿造出一致的响应。工作流程如下:
Ⅱ. RSA 签名、校验
RSA 签名算法是 RSA 加密配合散列函数的一种数据加密技术。如百度的证书
CA 机构采用的就是 sha256RSA 算法对颁发给百度的证书进行签名。其实 sha256RSA 签名校验算法并不是单独发明的一种新的算法,而是 RSA 加密和 sha256 散列函数的一个组合使用,其原理如下:
sha256RSA 签名流程
sha256RSA 校验流程
(二)攻击
1. XSS 攻击
Cross-Site Scripting(跨站脚本)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、Session ID 等,进而危害数据安全。
网页上任何可以输入的地方都有可能引起 XSS 攻击,包括 URL。XSS 常见的注入方法:
- 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
- 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
- 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
- 在标签的 href、src 等属性中,包含
javascript:
(伪协议) 等可执行代码。 - 在
onload
、onerror
、onclick
等事件中,注入不受控制代码。 - 在 style 属性和标签中,包含类似
background-image: url("javascript:...");
的代码(新版本浏览器已经可以防范)。 - 在 style 属性和标签中,包含类似
expression(...)
的 CSS 表达式代码(新版本浏览器已经可以防范)。
① 分类
根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。
存储型 XSS
攻击步骤
- 攻击者将恶意代码提交到目标网站的数据库中。
- 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户调用目标网站接口执行攻击者指定的操作。
存储型 XSS(又称持久性 XSS)攻击常见于带有保存用户数据的网站功能,如论坛发帖、商品评论、用户私信等。它是最危险的一种跨站脚本,相比反射型 XSS 和 DOM 型 XSS 具有更高的隐蔽性,所以危害更大,因为它不需要用户手动触发。任何允许用户存储数据的 web 程序都可能存在存储型 XSS 漏洞,当攻击者提交一段 XSS 代码后,被服务器端接收并存储,当所有浏览者访问某个页面时都会被 XSS。
实例分析
这是一个可以发表评论的文章界面。但是服务端并没有对评论进行处理,所以如果在评论区输入 <script>alert("xss")<script>
并发送,那么所有打开这篇文章的人都会遭到存储型 XSS 攻击。这只是一个弹框,在实际攻击中,XSS 代码还能用于加载第三方 js 文件,或者用 document.cookie
盗取 cookie。

反射型 XSS
攻击步骤
- 攻击者构造出特殊的 URL,其中包含恶意代码。
- 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
- 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户调用目标网站接口执行攻击者指定的操作。
反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。反射型 XSS(也称非持久性 XSS)漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。
实例分析
打开某书城首页,输入内容进行搜索。如果搜索的词条存在对应书籍,则直接返回相关列表。
https://www.kkkk1000.com/xss/Reflected/searchResult.html?kw=斗罗大陆

如果没有搜索到结果,则后端会直接返回用户输入的内容,并显示在页面上。
https://www.kkkk1000.com/xss/Reflected/searchResult.html?kw=xxx

后端在这里并没有对用户输入的数据进行处理,所以如果构造这样一个链接:
https://www.kkkk1000.com/xss/Reflected/searchResult.html?kw=alert(“xss”)
然后诱导他人点击该链接,就可以完成一次反射性 XSS 攻击。对于这么长的链接,还可以选择将其伪装成短网址或二维码。

DOM 型 XSS
攻击步骤
- 攻击者构造出特殊的 URL,其中包含恶意代码。
- 用户打开带有恶意代码的 URL。
- 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
- 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。
DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。
DOM 通常代表在 html、xhtml 和 xml 中的对象,使用 DOM 可以允许程序和脚本动态地访问和更新文档的内容、结构和样式。DOM 型 XSS 形成原因是通过修改页面的 DOM 节点形成的 XSS。
实例分析
下面是一个物流详情的页面,在 URL 上有快递编号这个参数,通过这个参数来获取数据。
https://www.kkkk1000.com/xss/dom/index.html?serialNumber=YT40359134268305

已知页面上显示的快递编号,是直接来自 URL 上的参数。所以如果构造这样一个网址:
https://www.kkkk1000.com/xss/dom/index.html?serialNumber=alert("xss")
然后诱导他人点击这个链接,就可以完成一次 DOM 型 XSS 攻击。
对比
类型 | 存储区 | 插入点 |
---|---|---|
存储型 XSS | 后端数据库 | HTML |
反射型 XSS | URL | HTML |
DOM 型 XSS | 后端数据库/前端存储/URL | 前端 JavaScript |
② 防御 XSS
只要有输入数据的地方,就可能存在 XSS 危险。
Ⅰ. 常用防范方法
HTTPOnly :在 cookie 中设置 HTTPOnly 属性后,js 脚本将无法读取到 cookie 信息。
输入过滤:一般是用于对于输入格式的检查,例如:邮箱、电话号码等,按照规定的格式输入。不仅仅是前端负责,后端也要做相同的过滤检查。因为攻击者完全可以绕过正常的输入流程,直接利用相关接口向服务器发送设置。
转义 HTML:如果拼接 HTML 是必要的,就需要对引号、尖括号、斜杠进行转义,但这不够完善。要想对 HTML 模板各处插入点进行充分转义,就需要采用合适的转义库 (可以看下这个库,还是中文的)
1
2
3
4
5
6
7
8
9
10function escape(str) {
str = str.replace(/&/g, '&')
str = str.replace(/</g, '<')
str = str.replace(/>/g, '>')
str = str.replace(/"/g, '&quto;')
str = str.replace(/'/g, ''')
str = str.replace(/`/g, '`')
str = str.replace(/\//g, '/')
return str
}白名单:对于显示富文本来说,不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。这种情况通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。
Ⅱ. 预防存储型和反射型 XSS 攻击
存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的数据被内嵌到代码中,被浏览器所执行。预防这两种漏洞,有两种常见做法:
- 改成纯前端渲染,把代码和数据分隔开。
- 对 HTML 做充分转义。
HTML 转义前面已经说过,这里仅谈纯前端渲染。
纯前端渲染的过程:
- 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据;
- 然后浏览器执行 HTML 中的 JavaScript;
- JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。
在纯前端渲染中,浏览器被明确告知:下面要设置的内容是文本(.innerText
),还是属性(.setAttribute
),还又或者式(.style
)等。这样浏览器就不会被轻易欺骗,执行预期外的代码了。
但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload
事件和 href
中的 javascript:xxx
等,请参考下文预防 DOM 型 XSS 攻击部分)。
在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题,这时就需要对 HTML 进行充分的转义。
Ⅲ. 预防 DOM 型 XSS 攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。
在使用 .innerHTML
、.outerHTML
、document.write()
时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent
、.setAttribute()
等。
如果用 Vue/React 技术栈,并且不使用 v-html
/dangerouslySetInnerHTML
功能,就在前端 render 阶段避免 innerHTML
、outerHTML
的 XSS 隐患。
DOM 中的内联事件监听器,如 location
、onclick
、onerror
、onload
、onmouseover
等,<a>
标签的 href
属性,JavaScript 的 eval()
、setTimeout()
、setInterval()
等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。
1 | <!-- 内联事件监听器中包含恶意代码 --> |
2. CSRF 攻击
跨站请求伪造(Cross-site request forgery),也称 one-click attack 或 session riding,通常缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。如:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
Ⅰ. CSRF 攻击流程
从上图可以看出,要完成一次 CSRF 攻击,受害者必须依次完成两个步骤:
- 1.登录受信任网站 A,并在本地生成 Cookie。
- 2.在不登出 A 的情况下,访问危险网站 B。
如果不满足以上条件中的任何一个,就不会受到 CSRF 的攻击。但用户往往不能保证以下情况不会发生:
- 你不能保证你登录了一个网站后,不再打开一个 tab 页面并访问另外的网站。
- 你不能保证关闭浏览器了后,你本地的 Cookie 立刻过期,你上次的会话已经结束。(事实上,关闭浏览器不能结束一个会话,但大多数人都会错误的认为关闭浏览器就等于退出登录/结束会话了)
- 上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。
Ⅱ. CSRF 攻击类型
GET 类型的 CSRF 利用非常简单,只需要一个 HTTP 请求,一般会这样利用:
1 | <img src="http://bank.example/withdraw?amount=10000&for=hacker" > |
在受害者访问含有这个 img 的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker
发出一次 HTTP 请求。bank.example
就会收到包含受害者登录信息的一次跨域请求。
这种类型的 CSRF 利用起来通常使用的是一个自动提交的表单,如:
1 | <form action="http://bank.example/withdraw" method=POST> |
访问该页面后,表单会自动提交,相当于模拟用户完成了一次 POST 操作。
POST 类型的攻击通常比 GET 要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许 POST 上面。
链接类型的 CSRF 并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以夸张的说辞诱骗用户点击,例如:
1 | <a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" target="_blank"> |
Ⅲ. CSRF 特点
- 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
- 跨站请求可以用各种方式:图片 URL、超链接、CORS、Form 提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。
CSRF 通常是跨域的,因为外域通常更容易被攻击者掌控。但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险。
Ⅳ. CSRF 与 XSS 区别
- 通常来说 CSRF 是由 XSS 实现的,CSRF 也被称为 XSRF(CSRF 实现的方式还可以是直接通过命令行发起请求等)。
- XSS 是代码注入问题,CSRF 是 HTTP 问题。XSS 是内容没有过滤导致浏览器将攻击者的输入当代码执行。CSRF 则是因为浏览器在发送 HTTP 请求时自动带上 cookie,而一般网站的 session ID 都存在 cookie 里面 (Token 验证可以避免)。
Ⅴ. 防御
- 验证码:强制用户必须与应用进行交互,才能完成最终请求。此种方式能很好的遏制 CSRF,但是用户体验比较差。
- Referer check:请求来源限制,此种方法成本最低,但是并不能保证 100% 有效,因为服务器并不是什么时候都能取到 Referer,而且低版本的浏览器存在伪造 Referer 的风险。
- token:token 验证的 CSRF 防御机制是公认最合适的方案。若网站同时存在 XSS 漏洞的时候,这个方法也是空谈。