title: 前端安全
categories:

  • 前端安全

    tags:

  • 前端安全

  • 浏览器

  • 算法

  • XSS

  • CSRF

  • 网络安全


本文全面介绍了前端安全的各个方面,包括加密算法、常见攻击方式及其防御措施,以及现代前端安全最佳实践。

目录

  1. 加密算法
  2. 常见攻击方式
  3. 现代前端安全
  4. 安全审计与最佳实践
  5. 总结

加密算法

目前在项目中应用较多的加密算法可以分为以下几个类别:

  • 散列/哈希函数算法
  • 对称加密算法
  • 非对称加密算法
  • 组合加密技术

1. 散列函数相关

与其说散列函数是一种加密算法,不如说它是一种数据特征值提取算法。常用的散列函数包括:MD5、SHA1、SHA2(包括 SHA128、SHA256 等)散列函数的应用很广。以 MD5 算法为例,其具有以下特点:

  • 压缩性:任意长度的数据,算出的 MD5 值长度都是固定的。
  • 容易计算:从原数据计算出 MD5 值很容易。
  • 抗修改性:对原数据进行任何改动,哪怕只修改 1 个字节,所得到的 MD5 值都有很大区别。
  • 强抗碰撞:已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的。散列算法的过程是不可逆的,可以通过数据得到其特征值,但是没办法根据特征值还原数据。

① 应用场景

Ⅰ. 文件一致性检验

假如有一份 1G 大小的文件需要发送给用户,但数据传输通道不可靠,那么可以计算出文件的散列值(为保证散列值不被窃取,可用其他加密方式将散列值加密并发送给用户),用户收到文件后自行计算其散列值,计算结果和收到的散列值相同则认为文件没有被篡改,否则说明文件遭到了篡改。

Ⅱ. 文件秒传

以百度云秒传功能为例,几 G 大小的视频可能不到一分钟就成功上传到网盘,其原理并非百度的服务写入速度或用户网速有多快,而是依赖于以下流程:

image-20210501101926968
Ⅲ. 保存用户密码

为了防止被黑客脱裤,不建议在数据库明文保存用户密码,而是保存密码的散列值。每次用户登录的时候,用散列函数对用户输入的密码进行计算,得到结果后和数据库里的散列值进行比对,一致则登录成功。当用户登录某个已注册网站时,在忘记密码的情况下需要重置密码,此时网站会给用户发一个随机密码或者一个邮箱激活链接,而不是用户之前的密码,这是因为哈希算法是不可逆的。

黑客脱裤成功拿到用户密码散列值后,可能拿彩虹表对用户密码的散列值进行暴力匹配,一些较简单的密码如“123456”、生日组合等,它们的散列值是固定的,因此会被黑客匹配上。为了应对这种情况可以对用户密码“加盐”,将“盐”和散列值一起保存。“123456”对应的散列值在彩虹表中有存储,但是“123456salt-asdgasdgsagasazsdasdf233zxcsdf”,在彩虹表中就没有了,这样黑客就没办法从彩虹表中获取常见密码的原文了!

② 算法分类

Ⅰ. 哈希算法(Hash)

哈希(Hash)是将目标文本转换成具有固定长度的字符串(或叫做消息摘要)。 当输入发生改变时,产生的哈希值也完全不同。基于哈希算法的特性,其适用于该场景:被保护数据仅仅用作比较验证且不需要还原成明文形式。比较常用的哈希算法是 MD5 和 SHA1。

现在,对于简单的哈希算法的攻击方法主要有:寻找碰撞法和穷举法。所以,为了保证数据的安全,可以在哈希算法的基础上进一步的加密,常见的方法有:加盐、慢哈希、密钥哈希、XOR 等。

Ⅱ. 加盐(Adding Salt)

加盐加密是一种对系统登录口令的加密方式,它实现的方式是将每一个口令和一个叫做盐(salt)的 n 位随机数相关联。

为了方便理解,这里引用这篇文章进行说明:使用 salt 加密,它的基本思路如下。

  • 用户注册时,在密码上撒一些盐。生成一种味道,记住味道。
  • 用户再次登陆时,在输入的密码上撒盐,闻一闻,判断是否和原来的味道相同,相同就让你吃饭。

由于验证密码时和最初散列密码时使用相同的盐值,所以 salt 存储在数据库。并且这个值是由系统随机产生的,而非硬编码。这就保证了所要保护对象的机密性。

注册时:


  • 1. 用户注册,系统随机产生 salt 值。

  • 1. 将 salt 值和密码连接起来,生产 Hash 值。

  • 1. 将 Hash 值和 salt 值分别存储在数据库中。

![img](https://user-gold-cdn.xitu.io/2019/2/21/1690efb5388875bf?imageslim null)

登陆时:

  • 系统根据用户名找到与之对应的密码 Hash。
  • 将用户输入密码和 salt 值进行散列。
  • 判断生成的 Hash 值是否和数据库中 Hash 相同。

![img](https://user-gold-cdn.xitu.io/2019/2/21/1690efc4dc5d6c0f?imageslim null)

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
2
3
4
1 ^ 1 // 0
0 ^ 0 // 0
1 ^ 0 // 1
0 ^ 1 // 1

XOR 运算有一个特性:如果对一个值连续做两次 XOR,会返回这个值本身。这也是其可以用于信息加密的根本。

1
2
message XOR key // cipherText
cipherText XOR key // message

目标文本 message,key 是密钥,第一次执行 XOR 会得到加密文本;在加密文本上再用 key 做一次 XOR 就会还原目标文本 message。为了保证 XOR 的安全,需要满足以下两点:

(1)key 的长度大于等于 message;

(2)key 必须是一次性的,且每次都要随机产生。

下面以登录密码加密为例介绍下 XOR 的使用:

第一步:使用 MD5 算法,计算密码的哈希;

1
const message = md5(password);

第二步:生成一个随机 key 值;

第三步:进行 XOR 运算,求出加密后的 message。

1
2
3
4
5
6
7
8
9
10
function getXOR(message, key) {
const arr = [];
//假设 key 是 32 位的
for (let i = 0; i < 32; i++) {
const m = parseInt(message.substr(i, 1), 16);
const k = parseInt(key.substr(i, 1), 16);
arr.push((m ^ k).toString(16));
}
return arr.join('');
}

如上所示,使用 XOR 和一次性的密钥 key 对密码进行加密处理,只要 key 没有泄露,目标文本就不会被破解。

上面说了那么多,问题就来了:我们应该使用什么样的哈希算法呢?

(1)选择经过验证的成熟算法,如 PBKDF2 等;

(2)crypt 的安全版本;

(3)避免使用自己设计的加密算法。

Ⅵ. HMAC

对于 HMAC 算法,和加盐类似,就是 salt 换成后端随机生成的 (好像可以防止重放攻击),然后再通过 HMAC 算法得到摘要。关于 HMAC 算法部分可以详细看这篇文章

大概过程如下:

  • 客户端发出登录请求
  • 服务器返回一个随机值,在会话记录中保存这个随机值
  • 客户端将该随机值作为密钥,用户密码进行 HMAC 运算,递交给服务器
  • 服务器读取数据库中的用户密码,利用密钥做和客户端一样的 HMAC 运算,然后与用户发送的结果比较,如果一致,则用户身份合法。

好处:

  • 与自定义的加 salt 算法不同,HMAC 算法对所有哈希算法都通用,无论是 MD5 还是 SHA-1。采用 HMAC 替代我们自己的 salt 算法,可以使程序算法更标准化,也更安全。(摘自雪峰大佬的这篇文章)
  • 另外一个就是密码的安全性,由于不知道密钥,所以不可能获取到用户密码

2. 对称加密算法

对称加密采用了对称密码编码技术,它的特点是文件加密和解密使用相同的密钥加密。也就是加密和解密都是用同一个密钥,这种方法在密码学中叫做对称加密算法。

image-20210501105656149

对称加密算法使用起来简单快捷,密钥较短,且破译困难,除了数据加密标准(DES),另一个对称密钥加密系统是国际数据加密算法(IDEA),它比 DES 的加密性好,而且对计算机功能要求也没有那么高。

常用的对称加密算法包括 DES 算法、AES 算法等。由于对称加密需要一个秘钥,而秘钥在加密者与解密者之间传输又很难保证安全性,所以目前用对称加密算法的话主要是用在加密者解密者相同,或者加密者解密者相对固定的场景。

3. 非对称加密算法

非对称加密算法的特点是,秘钥一次会生成一对,其中一份秘钥由自己保存,不能公开出去,称为私钥,另外一份是可以公开出去的,称为公钥。将原文用公钥进行加密,得到的密文只有对应私钥才可以解密;将原文用私钥加密得到的密文,也只有用对应的公钥才能解密。这样就解决了对称加密的秘钥传输过程无法保证安全的问题,加密者想与多方通信,也只需要公开一份自己的公钥就行了,无需对每个通信者生成多份秘钥。目前应用最广泛的非对称加密是 RSA 加密。

4. 组合加密技术

各种加密算法都有自己的优缺点,很多场景并不能使用一种加密算法就能满足所有需求。现实应用中有很多“混搭”风格的加密技术正在被使用,下面列举一些我所了解到的算法或技术组合。

Ⅰ. HMAC 加密算法

HMAC 加密算法是一种需要秘钥的散列算法,作为一个使用者我们暂时不去关心它的运算步骤,在使用理解方面,我认为可以将 HMAC 加密就理解为加盐的散列算法,此处的“盐”就相当于 HMAC 算法的秘钥,当然它们内部的计算逻辑是不同的。

HMAC 加密算法主要用于校验客户端的身份,下面引用百度百科中的一个例子:HMAC 的一个典型应用是用在“质疑/应答”(Challenge/Response)身份认证中。

  1. 先由客户端向服务器发出一个验证请求。
  2. 服务器接到此请求后生成一个随机数并通过网络传输给客户端(此为质疑)。
  3. 客户端将收到的随机数提供给 ePass,由 ePass 使用该随机数与存储在 ePass 中的密钥进行 HMAC-MD5 运算并得到一个结果作为认证证据传给服务器(此为响应)。
  4. 与此同时,服务器也使用该随机数与存储在服务器数据库中的该客户密钥进行 HMAC-MD5 运算,如果服务器的运算结果与客户端传回的响应结果相同,则认为客户端是一个合法用户安全性浅析由上面的介绍,我们可以看出,HMAC 算法更象是一种加密算法,它引入了密钥,其安全性已经不完全依赖于所使用的 HASH 算法,安全性主要有以下几点保证:(1)使用的密钥是双方事先约定的,第三方不可能知道。由 3.2 介绍的应用流程可以看出,作为非法截获信息的第三方,能够得到的信息只有作为“挑战”的随机数和作为“响应”的 HMAC 结果,无法根据这两个数据推算出密钥。由于不知道密钥,所以无法仿造出一致的响应。

Ⅱ. RSA 签名、校验

RSA 签名算法是 RSA 加密配合散列函数的一种数据加密技术。如百度的证书,CA 机构采用的就是 sha256RSA 算法对颁发给百度的证书进行签名。其实 sha256RSA 签名校验算法并不是单独发明的一种新的算法,而是 RSA 加密和 sha256 散列函数的一个组合使用。

Ⅲ. 现代加密实践

Web Crypto API

Web Crypto API 是浏览器内置的加密 API,提供了现代加密算法的实现。

使用示例:生成密钥对并进行加密/解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 生成 RSA 密钥对
async function generateKeyPair() {
const keyPair = await window.crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt']
);
return keyPair;
}

// 加密数据
async function encryptData(data, publicKey) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const encrypted = await window.crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
publicKey,
dataBuffer
);
return new Uint8Array(encrypted);
}

// 解密数据
async function decryptData(encryptedData, privateKey) {
const decrypted = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
privateKey,
encryptedData
);
const decoder = new TextDecoder();
return decoder.decode(decrypted);
}

// 使用示例
async function example() {
const keyPair = await generateKeyPair();
const data = 'Hello, World!';
const encrypted = await encryptData(data, keyPair.publicKey);
const decrypted = await decryptData(encrypted, keyPair.privateKey);
console.log('Original:', data);
console.log('Decrypted:', decrypted);
}

example();
密码存储最佳实践

现代应用中,密码存储应使用专门的密码哈希函数,如 bcrypt、Argon2 等。

使用 bcrypt 存储密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const bcrypt = require('bcrypt');
const saltRounds = 12;

// 哈希密码
async function hashPassword(password) {
const salt = await bcrypt.genSalt(saltRounds);
const hash = await bcrypt.hash(password, salt);
return hash;
}

// 验证密码
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}

// 使用示例
async function example() {
const password = 'mySecurePassword123';
const hash = await hashPassword(password);
console.log('Hashed password:', hash);

const isMatch = await verifyPassword(password, hash);
console.log('Password match:', isMatch);

const isWrongMatch = await verifyPassword('wrongPassword', hash);
console.log('Wrong password match:', isWrongMatch);
}

example();
前端加密示例

使用 AES-GCM 加密敏感数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 生成密钥
async function generateKey() {
return await window.crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256,
},
true,
['encrypt', 'decrypt']
);
}

// 加密数据
async function encryptData(data, key) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const iv = window.crypto.getRandomValues(new Uint8Array(12));

const encrypted = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: iv,
},
key,
dataBuffer
);

return {
encrypted: new Uint8Array(encrypted),
iv: iv
};
}

// 解密数据
async function decryptData(encryptedData, iv, key) {
const decrypted = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: iv,
},
key,
encryptedData
);

const decoder = new TextDecoder();
return decoder.decode(decrypted);
}

// 使用示例
async function example() {
const key = await generateKey();
const data = 'Sensitive information';

const { encrypted, iv } = await encryptData(data, key);
console.log('Encrypted:', encrypted);

const decrypted = await decryptData(encrypted, iv, key);
console.log('Decrypted:', decrypted);
}

example();

Ⅳ. 加密算法选择指南

场景推荐算法原因
密码存储bcrypt、Argon2慢哈希,抵抗暴力破解
数据加密AES-256-GCM安全、高效
数字签名RSA、ECC非对称加密,便于验证
消息认证HMAC-SHA256带密钥的哈希,防止篡改
数据完整性SHA-256、SHA-3强哈希函数,碰撞 resistance 高

Ⅴ. 前端加密注意事项

  1. 不要在前端存储密钥:前端加密的密钥应该由服务器生成并安全传输
  2. 不要依赖前端加密:前端加密只是额外的安全层,不能替代服务器端安全措施
  3. 使用 HTTPS:确保所有传输都通过 HTTPS 进行
  4. 定期更新算法:随着密码学的发展,定期更新使用的加密算法
  5. 注意性能影响:加密操作可能会影响前端性能,特别是在移动设备上
  6. 使用成熟的库:使用经过验证的加密库,避免自行实现加密算法

常见攻击方式

XSS 攻击

Cross-Site Scripting(跨站脚本)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、Session ID 等,进而危害数据安全。

网页上任何可以输入的地方都有可能引起 XSS 攻击,包括 URL。XSS 常见的注入方法:

  • 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
  • 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
  • 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
  • 在标签的 href、src 等属性中,包含 javascript: (伪协议) 等可执行代码。
  • onloadonerroronclick 等事件中,注入不受控制代码。
  • 在 style 属性和标签中,包含类似 background-image: url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
  • 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

现代前端框架的 XSS 防御

现代前端框架如 React、Vue、Angular 等,都内置了 XSS 防御机制,但开发人员仍需了解如何正确使用这些框架以避免安全漏洞。

React 中的 XSS 防御

React 默认会对用户输入进行转义,防止 XSS 攻击。但在以下情况下需要特别注意:

  • dangerouslySetInnerHTML:使用此属性时,React 不会转义内容,需要手动确保内容安全
  • URL 处理:当使用用户输入作为 URL 时,需要确保 URL 是安全的
  • 事件处理:避免直接将用户输入作为事件处理函数

安全实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 安全的做法
function SafeComponent({ userInput }) {
return <div>{userInput}</div>; // React 会自动转义
}

// 不安全的做法 - 避免使用
function UnsafeComponent({ userInput }) {
return <div dangerouslySetInnerHTML={{ __html: userInput }} />; // 可能导致 XSS
}

// 安全的 URL 处理
function SafeLink({ url }) {
// 验证 URL 是否安全
const isSafeUrl = /^https?:\/\//.test(url);
return isSafeUrl ? <a href={url}>Link</a> : <a href="#">Invalid Link</a>;
}
Vue 中的 XSS 防御

Vue 的模板系统会自动转义用户输入,但在以下情况下需要特别注意:

  • v-html:使用此指令时,Vue 不会转义内容,需要手动确保内容安全
  • 动态绑定:使用 :href:src 等动态绑定属性时,需要确保值是安全的
  • 事件处理:避免直接将用户输入作为事件处理函数

安全实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<!-- 安全的做法 -->
<div>{{ userInput }}</div> <!-- Vue 会自动转义 -->

<!-- 不安全的做法 - 避免使用 -->
<div v-html="userInput"></div> <!-- 可能导致 XSS -->

<!-- 安全的 URL 处理 -->
<a :href="safeUrl">Link</a>
</template>

<script>
export default {
data() {
return {
userInput: '<script>alert("XSS")</script>',
unsafeUrl: 'javascript:alert("XSS")',
safeUrl: 'https://example.com'
};
},
computed: {
safeUrl() {
// 验证 URL 是否安全
return /^https?:\/\//.test(this.unsafeUrl) ? this.unsafeUrl : '#';
}
}
};
</script>
Angular 中的 XSS 防御

Angular 的模板系统会自动转义用户输入,并提供了 DomSanitizer 服务来处理不安全的内容。

安全实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Component, OnInit } from '@angular/core';
import { DomSanitizer, SafeHtml, SafeUrl } from '@angular/platform-browser';

@Component({
selector: 'app-safe-component',
template: `
<!-- 安全的做法 -->
<div>{{ userInput }}</div> <!-- Angular 会自动转义 -->

<!-- 安全地处理 HTML -->
<div [innerHTML]="safeHtml"></div>

<!-- 安全地处理 URL -->
<a [href]="safeUrl">Link</a>
`
})
export class SafeComponent implements OnInit {
userInput = '<script>alert("XSS")</script>';
unsafeHtml = '<script>alert("XSS")</script>';
unsafeUrl = 'javascript:alert("XSS")';

safeHtml: SafeHtml;
safeUrl: SafeUrl;

constructor(private sanitizer: DomSanitizer) {}

ngOnInit() {
// 安全地处理 HTML
this.safeHtml = this.sanitizer.bypassSecurityTrustHtml('<p>安全的 HTML</p>');

// 安全地处理 URL
this.safeUrl = this.sanitizer.bypassSecurityTrustUrl('https://example.com');
}
}

① 分类

根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。

存储型 XSS

攻击步骤

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户调用目标网站接口执行攻击者指定的操作。

存储型 XSS(又称持久性 XSS)攻击常见于带有保存用户数据的网站功能,如论坛发帖、商品评论、用户私信等。它是最危险的一种跨站脚本,相比反射型 XSS 和 DOM 型 XSS 具有更高的隐蔽性,所以危害更大,因为它不需要用户手动触发。任何允许用户存储数据的 web 程序都可能存在存储型 XSS 漏洞,当攻击者提交一段 XSS 代码后,被服务器端接收并存储,当所有浏览者访问某个页面时都会被 XSS。

实例分析

这是一个可以发表评论的文章界面。但是服务端并没有对评论进行处理,所以如果在评论区输入 <script>alert("xss")<script> 并发送,那么所有打开这篇文章的人都会遭到存储型 XSS 攻击。这只是一个弹框,在实际攻击中,XSS 代码还能用于加载第三方 js 文件,或者用 document.cookie 盗取 cookie。

image-20210503094040445
反射型 XSS

攻击步骤

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户调用目标网站接口执行攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。反射型 XSS(也称非持久性 XSS)漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。

实例分析

打开某书城首页,输入内容进行搜索。如果搜索的词条存在对应书籍,则直接返回相关列表。

https://www.kkkk1000.com/xss/Reflected/searchResult.html?kw=斗罗大陆

image-20210503094859145

如果没有搜索到结果,则后端会直接返回用户输入的内容,并显示在页面上。

https://www.kkkk1000.com/xss/Reflected/searchResult.html?kw=xxx

image-20210503095218734

后端在这里并没有对用户输入的数据进行处理,所以如果构造这样一个链接:

https://www.kkkk1000.com/xss/Reflected/searchResult.html?kw=alert("xss")

然后诱导他人点击该链接,就可以完成一次反射性 XSS 攻击。对于这么长的链接,还可以选择将其伪装成短网址或二维码。

image-20210503095552845
DOM 型 XSS

攻击步骤

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

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

image-20210503143908134

已知页面上显示的快递编号,是直接来自 URL 上的参数。所以如果构造这样一个网址:

https://www.kkkk1000.com/xss/dom/index.html?serialNumber=alert("xss")

然后诱导他人点击这个链接,就可以完成一次 DOM 型 XSS 攻击。


对比

类型存储区插入点
存储型 XSS后端数据库HTML
反射型 XSSURLHTML
DOM 型 XSS后端数据库/前端存储/URL前端 JavaScript

② 防御 XSS

只要有输入数据的地方,就可能存在 XSS 危险。

Ⅰ. 常用防范方法
  • HTTPOnly :在 cookie 中设置 HTTPOnly 属性后,js 脚本将无法读取到 cookie 信息。
  • 输入过滤:一般是用于对于输入格式的检查,例如:邮箱、电话号码等,按照规定的格式输入。不仅仅是前端负责,后端也要做相同的过滤检查。因为攻击者完全可以绕过正常的输入流程,直接利用相关接口向服务器发送设置。
  • 转义 HTML:如果拼接 HTML 是必要的,就需要对引号、尖括号、斜杠进行转义,但这不够完善。要想对 HTML 模板各处插入点进行充分转义,就需要采用合适的转义库 (可以看下这个,还是中文的)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function escape(str) {
    str = str.replace(/&/g, '&amp;')
    str = str.replace(/</g, '&lt;')
    str = str.replace(/>/g, '&gt;')
    str = str.replace(/"/g, '&quto;')
    str = str.replace(/'/g, '&#39;')
    str = str.replace(/`/g, '&#96;')
    str = str.replace(/\//g, '&#x2F;')
    return str
    }
  • **白名单:**对于显示富文本来说,不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。这种情况通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。
Ⅱ. 预防存储型和反射型 XSS 攻击

存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的数据被内嵌到代码中,被浏览器所执行。预防这两种漏洞,有两种常见做法:

  • 改成纯前端渲染,把代码和数据分隔开。
  • 对 HTML 做充分转义。

HTML 转义前面已经说过,这里仅谈纯前端渲染。

纯前端渲染的过程:

  1. 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据;
  2. 然后浏览器执行 HTML 中的 JavaScript;
  3. JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。

在纯前端渲染中,浏览器被明确告知:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还又或者式(.style)等。这样浏览器就不会被轻易欺骗,执行预期外的代码了。

但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,请参考下文预防 DOM 型 XSS 攻击部分)。

在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题,这时就需要对 HTML 进行充分的转义。

Ⅲ. 预防 DOM 型 XSS 攻击

DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。

在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTMLouterHTML 的 XSS 隐患。

DOM 中的内联事件监听器,如 locationonclickonerroronloadonmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 内联事件监听器中包含恶意代码 -->
<img onclick="UNTRUSTED" onerror="UNTRUSTED" src="data:image/png,">

<!-- 链接内包含恶意代码 -->
<a href="UNTRUSTED">1</a>

<script>
// setTimeout()/setInterval() 中调用恶意代码
setTimeout("UNTRUSTED")
setInterval("UNTRUSTED")

// location 调用恶意代码
location.href = 'UNTRUSTED'

// eval() 中调用恶意代码
eval("UNTRUSTED")
</script>

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

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 就会收到包含受害者登录信息的一次跨域请求。


  • #### POST 类型的 CSRF

这种类型的 CSRF 利用起来通常使用的是一个自动提交的表单,如:

1
2
3
4
5
6
<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>

访问该页面后,表单会自动提交,相当于模拟用户完成了一次 POST 操作。

POST 类型的攻击通常比 GET 要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许 POST 上面


  • #### 链接类型的 CSRF

链接类型的 CSRF 并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以夸张的说辞诱骗用户点击,例如:

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

Ⅲ. 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 漏洞的时候,这个方法也是空谈。
  • SameSite Cookie:设置 Cookie 的 SameSite 属性为 Strict 或 Lax,防止跨站请求携带 Cookie。
  • 双重提交 Cookie:将 CSRF token 同时存储在 Cookie 和请求头中,服务器验证两者是否一致。

现代前端框架的 CSRF 防御

React 中的 CSRF 防御

在 React 应用中,可以使用以下方法防御 CSRF 攻击:

使用 axios 拦截器添加 CSRF token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import axios from 'axios';

// 创建 axios 实例
const api = axios.create({
baseURL: '/api'
});

// 请求拦截器,添加 CSRF token
api.interceptors.request.use(config => {
// 从 Cookie 中获取 CSRF token
const token = document.cookie.split(';').find(cookie =>
cookie.trim().startsWith('XSRF-TOKEN=')
)?.split('=')[1];

if (token) {
config.headers['X-XSRF-TOKEN'] = token;
}

return config;
});

// 使用 api 发送请求
export default api;
Vue 中的 CSRF 防御

在 Vue 应用中,可以使用以下方法防御 CSRF 攻击:

使用 axios 拦截器添加 CSRF token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import axios from 'axios';

// 创建 axios 实例
const api = axios.create({
baseURL: '/api'
});

// 请求拦截器,添加 CSRF token
api.interceptors.request.use(config => {
// 从 Cookie 中获取 CSRF token
const token = document.cookie.split(';').find(cookie =>
cookie.trim().startsWith('XSRF-TOKEN=')
)?.split('=')[1];

if (token) {
config.headers['X-XSRF-TOKEN'] = token;
}

return config;
});

// 将 api 实例添加到 Vue 原型
Vue.prototype.$api = api;

// 使用 this.$api 发送请求
Angular 中的 CSRF 防御

Angular 内置了 CSRF 防御机制,使用 HttpClientXsrfModule

1
2
3
4
5
6
7
8
9
10
11
12
13
import { NgModule } from '@angular/core';
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';

@NgModule({
imports: [
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
})
]
})
export class AppModule {}

CSRF 防御最佳实践

  1. 使用 token 验证:为每个用户会话生成唯一的 CSRF token
  2. 设置 SameSite Cookie:将 Cookie 的 SameSite 属性设置为 Strict 或 Lax
  3. 验证 Referer 和 Origin 头:检查请求的来源
  4. 使用 HTTPS:确保所有请求都通过 HTTPS 发送
  5. 保持 token 安全:确保 CSRF token 不会被 XSS 攻击窃取
  6. 定期更新 token:定期更换 CSRF token,减少被攻击的风险

实际案例:银行转账 CSRF 攻击防御

攻击场景:攻击者通过欺骗用户点击恶意链接,诱导用户在已登录银行网站的情况下执行转账操作。

防御措施

  1. 使用 CSRF token:在转账表单中添加隐藏的 CSRF token 字段
  2. 验证 Referer:确保转账请求来自银行网站本身
  3. 使用 SameSite Cookie:防止跨站请求携带会话 Cookie
  4. 二次验证:对于大额转账,要求用户输入密码或验证码
  5. 交易确认:显示交易详情并要求用户确认

实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 转账表单 -->
<form action="/transfer" method="POST">
<!-- CSRF token -->
<input type="hidden" name="_csrf" value="<%= csrfToken %>">

<!-- 转账信息 -->
<div>
<label>收款人</label>
<input type="text" name="recipient" required>
</div>
<div>
<label>金额</label>
<input type="number" name="amount" required>
</div>
<div>
<label>密码</label>
<input type="password" name="password" required>
</div>

<button type="submit">确认转账</button>
</form>

服务器验证

1
2
3
4
5
6
7
8
9
10
11
// Express.js 中间件验证 CSRF token
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

// 转账路由
app.post('/transfer', csrfProtection, (req, res) => {
// 验证 CSRF token
// 验证用户密码
// 执行转账操作
res.send('转账成功');
});

点击劫持

点击劫持(Clickjacking)是一种视觉欺骗攻击,攻击者通过在受害者不知情的情况下,将一个透明或半透明的 iframe 覆盖在正常网页上,诱导用户点击实际上是指向恶意网站的链接。

攻击原理

  1. 攻击者创建一个包含恶意操作的页面
  2. 攻击者使用 iframe 将目标网站嵌入到自己的页面中
  3. 通过 CSS 使 iframe 透明或半透明,并覆盖在自己页面的按钮或链接上
  4. 当用户点击攻击者页面上的按钮时,实际上是点击了 iframe 中的目标网站按钮

防御措施

  • X-Frame-Options 头:设置 X-Frame-Options: DENYX-Frame-Options: SAMEORIGIN 来防止网站被嵌入到 iframe 中
  • Content-Security-Policy:使用 frame-ancestors 指令来限制哪些网站可以嵌入当前网站
  • JavaScript 防御:使用 window.top !== window.self 来检测是否被嵌入到 iframe 中
  • 验证码:对于敏感操作,使用验证码来确保用户的真实意图

敏感数据泄露

敏感数据泄露是指网站或应用程序意外地暴露了敏感信息,如用户密码、个人信息、API 密钥等。

常见原因

  • 不安全的存储:明文存储密码或敏感信息
  • 日志记录过多:日志中包含敏感信息
  • 错误处理不当:错误信息中包含敏感数据
  • API 设计不当:API 返回过多敏感信息
  • 第三方依赖:第三方库或服务存在安全漏洞

防御措施

  • 加密存储:使用加密算法存储敏感信息
  • 最小权限原则:只授予必要的访问权限
  • 数据脱敏:在返回给前端的数据中对敏感信息进行脱敏处理
  • 安全日志:确保日志中不包含敏感信息
  • 定期安全审计:定期检查代码和配置中的安全问题

CSP(内容安全策略)

内容安全策略(Content Security Policy,CSP)是一种安全机制,用于防止 XSS 攻击和其他代码注入攻击。通过 CSP,网站可以指定哪些资源可以被加载和执行。

工作原理

CSP 通过 HTTP 头 Content-Security-Policy<meta> 标签来定义策略,告诉浏览器只能从指定的源加载资源。

基本指令

  • default-src:默认的资源加载策略
  • script-src:脚本的加载策略
  • style-src:样式表的加载策略
  • img-src:图片的加载策略
  • font-src:字体的加载策略
  • connect-src:AJAX 请求的加载策略
  • frame-src:iframe 的加载策略
  • object-src:插件的加载策略

使用示例

通过 HTTP 头设置:

1
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:;

通过 meta 标签设置:

1
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:">

最佳实践

  • 尽可能使用严格的 CSP 策略
  • 避免使用 'unsafe-inline''unsafe-eval'
  • 使用 noncehash 来允许特定的内联脚本
  • 定期更新 CSP 策略以适应新的需求

现代前端安全

现代前端框架安全

现代前端框架如 React、Vue、Angular 等,都内置了一些安全措施来防止常见的安全漏洞。

React 安全

  • 默认防 XSS:React 默认会对用户输入进行转义,防止 XSS 攻击
  • dangerouslySetInnerHTML:只有使用 dangerouslySetInnerHTML 时才会允许 HTML 注入
  • useState 和 useEffect:使用这些钩子可以避免直接操作 DOM,减少安全风险
  • **React 16+**:引入了错误边界,可以捕获和处理渲染过程中的错误,防止敏感信息泄露

Vue 安全

  • 默认防 XSS:Vue 的模板系统会自动转义用户输入
  • v-html:只有使用 v-html 指令时才会允许 HTML 注入
  • 计算属性和 watch:使用这些特性可以避免直接操作 DOM
  • Vue 3:引入了 Composition API,提供了更好的状态管理和安全性

Angular 安全

  • 默认防 XSS:Angular 的模板系统会自动转义用户输入
  • DomSanitizer:提供了 DomSanitizer 服务来处理不安全的内容
  • HttpClient:内置的 HttpClient 提供了 CSRF 保护
  • 依赖注入:严格的依赖注入系统可以防止恶意代码注入

前端安全工具

代码分析工具

  • ESLint:可以使用 ESLint 插件如 eslint-plugin-security 来检测代码中的安全问题
  • SonarQube:静态代码分析工具,可以检测代码中的安全漏洞
  • Snyk:可以检测项目依赖中的安全漏洞

安全测试工具

  • OWASP ZAP:开源的 Web 应用安全扫描工具
  • Burp Suite:功能强大的 Web 应用安全测试工具
  • Google Lighthouse:可以检测网站的安全性能

安全库

  • DOMPurify:用于清理 HTML 内容,防止 XSS 攻击
  • js-cookie:安全的 cookie 操作库
  • crypto-js:提供了各种加密算法的 JavaScript 实现
  • helmet:Express.js 的安全中间件,设置各种安全头

安全审计与最佳实践

安全审计

安全审计是指对网站或应用程序进行全面的安全检查,识别潜在的安全漏洞和风险。

审计步骤

  1. 代码审查:检查代码中的安全漏洞和风险
  2. 依赖检查:检查项目依赖中的安全漏洞
  3. 配置审查:检查服务器和应用程序的配置
  4. 渗透测试:模拟攻击者的攻击行为,测试系统的安全性
  5. 安全扫描:使用安全扫描工具扫描系统中的安全漏洞

前端安全最佳实践

开发阶段

  • 使用 HTTPS:确保网站使用 HTTPS 协议
  • 输入验证:对所有用户输入进行验证和过滤
  • 密码安全:使用强密码哈希算法,如 bcrypt
  • CORS 配置:正确配置 CORS 策略
  • CSP 配置:使用内容安全策略防止 XSS 攻击
  • X-Frame-Options:设置 X-Frame-Options 防止点击劫持
  • HttpOnly Cookie:对敏感 cookie 设置 HttpOnly 属性
  • SameSite Cookie:对 cookie 设置 SameSite 属性防止 CSRF 攻击

部署阶段

  • 定期更新依赖:定期更新项目依赖,修复安全漏洞
  • 安全配置:正确配置服务器和应用程序的安全设置
  • 监控:设置安全监控,及时发现和处理安全事件
  • 备份:定期备份数据,防止数据丢失

维护阶段

  • 安全更新:及时应用安全补丁和更新
  • 安全培训:对开发人员进行安全培训,提高安全意识
  • 安全审计:定期进行安全审计,识别和修复安全漏洞
  • 事件响应:制定安全事件响应计划,及时处理安全事件

总结

前端安全是 Web 应用安全的重要组成部分,需要从多个方面进行防护:

  1. 加密算法:使用安全的加密算法保护敏感数据
  2. 常见攻击:了解和防御 XSS、CSRF、点击劫持等常见攻击
  3. 现代框架:利用现代前端框架的安全特性
  4. 安全工具:使用安全工具检测和修复安全漏洞
  5. 最佳实践:遵循前端安全最佳实践,从开发到部署全程关注安全

通过综合运用这些措施,可以大大提高前端应用的安全性,保护用户数据和隐私。前端开发人员应该始终将安全放在首位,不断学习和更新安全知识,以应对不断演变的安全威胁。