前端常见的安全问题及防范措施

在当下这个通信技术高速发展的时代,网站的出现拉近了用户和商家,用户与用户之间的距离。这也滋生了一些黑色产业,对网站进行攻击获取数据,瘫痪网站以获取不法收入。

这篇博客,我们将探讨网站安全的方方面面,从基础的防护措施到应对复杂攻击的策略。帮助开发者构建一个安全性高的网站应用。

跨域攻击

现如今的网站应用大都是前后端分离的模式,也有网站内部和其他不同的网络应用沟通的场景,这就出现了跨域的现象。

跨域是什么

跨域是指在一个网站中对另一个域名下的资源进行请求的行为。在浏览器的安全策略中,出于安全性考虑,通常不允许这种跨域请求直接发生,这被称为“同源策略”。为了突破这一限制,开发者可以使用多种跨域技术,如 CORS(跨域资源共享)、JSONP 等,来实现安全的数据通信。跨域问题在前后端开发中十分常见,合理处理跨域是确保网站功能正常和安全的关键。

示例案例:防护跨域攻击的实现

假设你刚开始搞一个前后端分离的项目,前端在 https://example-frontend.com,后端的 API 服务器跑在 https://api.example-backend.com。为了让它们互相沟通而不被跨域攻击搞崩,你需要做点防护。

1. 使用跨域资源共享(CORS)

首先,你需要在后端给前端开个“绿灯”,让它能正常访问,这就用到了 CORS。简单来说,就是给服务器加个头,告诉它哪些前端是可以被信任的,这样服务器才不会大惊小怪地阻止你的请求。

比如,在 Express.js(Node.js 框架)中,你可以这样搞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express');
const cors = require('cors');
const app = express();

// 只允许特定的前端域名访问
const corsOptions = {
origin: '<https://example-frontend.com>',
methods: ['GET', 'POST'],
credentials: true
};

app.use(cors(corsOptions));

app.get('/api/data', (req, res) => {
res.json({ message: '安全的跨域数据传输' });
});

app.listen(3000, () => {
console.log('API服务器正在运行,端口:3000');
});

2. 验证请求来源

为了更保险,你还可以检查一下请求头,确认这些请求真的是从你自己的网站来的,而不是别人伪造的。

在 Express.js 中,可以这样实现来源验证:

1
2
3
4
5
6
7
8
9
app.use((req, res, next) => {
const allowedOrigins = ['<https://example-frontend.com>'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
next();
} else {
res.status(403).send('禁止访问:来源不受信任');
}
});

3. 使用安全令牌(CSRF Token)

接下来,你可以用 CSRF Token 让请求更安全。这个 Token 就像是给每个请求都发了一把独特的“钥匙”,只有拿到正确钥匙的请求才会被服务器接受,避免别人假装是你的网站发请求。

在 Express.js 中,可以这样实现 CSRF 保护:

1
2
3
4
5
6
7
8
9
10
11
12
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.use(csrfProtection);

app.get('/form', (req, res) => {
// 在表单中嵌入 CSRF Token
res.send(`<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="${req.csrfToken()}">
<button type="submit">提交</button>
</form>`);
});

通过这些实际的代码示例,开发者可以更好地理解和实现防护跨域攻击的措施,以提高网站的安全性。

防护跨域攻击的方法:

  1. 使用跨域资源共享(CORS):配置服务器允许受信任的域名访问资源,确保跨域请求是安全的。
  2. 验证请求来源:通过检查请求头中的来源信息,阻止不受信任的请求来源。
  3. 使用预检请求:确保敏感操作使用复杂请求,浏览器会自动发送预检请求以验证是否允许跨域操作。
  4. 限制 Cookie 的传输:通过设置 SameSite 属性来限制跨站点请求中 Cookie 的使用,降低被滥用的风险。
  5. 使用安全令牌:为跨域请求生成独立的安全令牌,以验证请求的合法性并防止 CSRF(跨站请求伪造)攻击。

XSS 攻击

XSS(跨站脚本攻击)是一种常见的攻击方式,攻击者通过在网页中注入恶意脚本,使得这些脚本在其他用户的浏览器中执行,从而窃取数据或控制用户的会话。XSS 攻击可以导致用户数据被盗取、用户账户被劫持等严重后果。

XSS 的类型

XSS 通常有以下几种类型:

  1. 存储型 XSS​:攻击者将恶意脚本存储在目标服务器上,其他用户在访问受感染页面时会自动执行该脚本。
  2. 反射型 XSS​:恶意脚本作为输入内容的一部分发送到服务器,然后服务器直接将内容返回给用户,导致脚本执行。
  3. DOM 型 XSS​:攻击者通过操作页面的 DOM 元素来注入脚本,使得脚本在用户的浏览器中执行。

示例案例:防护 XSS 攻击的实现

为了防护 XSS 攻击,可以采取多种措施,确保用户输入不会被恶意利用。

1. 转义用户输入

为了防止用户输入的内容被直接当作 HTML 或 JavaScript 执行,应该对用户输入进行转义。例如在使用模板引擎时,可以使用自动转义的特性来避免 XSS 漏洞。

在 Express.js 中使用 ejs 作为模板引擎时,默认会对用户输入进行 HTML 转义,防止恶意脚本执行:

1
2
3
4
app.get('/comment', (req, res) => {
const userComment = req.query.comment;
res.render('comment', { comment: userComment }); // 自动转义 comment
});

2. 使用内容安全策略(CSP)

CSP 可以通过限制页面可以加载哪些资源(例如脚本、样式、图片等),有效地防止 XSS 攻击。通过配置 CSP,开发者可以指定哪些资源是可信任的。

在 Express.js 中,可以使用 helmet 中间件来设置 CSP:

1
2
3
4
5
6
7
8
9
10
11
12
const helmet = require('helmet');

app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", '<https://trustedscripts.example.com>'],
objectSrc: ["'none'"],
upgradeInsecureRequests: [],
},
})
);

如果你是 next.js 这种全栈开发框架,可以通过配置文件设置 http 响应头。例如下面的例子

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
// next.config.mjs
import { NextConfig } from "next";

const nextConfig = {
async headers() {
return [
{
// 匹配所有路径
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value:
"default-src 'self'; img-src 'self' https:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Permissions-Policy",
value: "geolocation=(), microphone=()",
},
],
},
];
},
};

export default nextConfig;

3. 输入验证和过滤

确保对用户输入的数据进行严格的验证和过滤,避免用户提交恶意内容。可以使用一些常见的库来进行输入验证,例如 express-validator

1
2
3
4
5
6
7
8
9
10
11
12
const { body, validationResult } = require('express-validator');

app.post('/submit',
body('username').isAlphanumeric().withMessage('用户名只能包含字母和数字'),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.send('输入验证通过');
}
);

防护 XSS 攻击的方法总结

  1. 转义用户输入​:对所有用户输入进行转义,确保特殊字符不会被当作代码执行。
  2. ​**使用内容安全策略(CSP)**​:通过设置 CSP 来限制页面可以加载的资源,降低 XSS 攻击的风险。
  3. 输入验证和过滤​:对所有用户输入进行验证,确保它们符合预期的格式,避免恶意脚本注入。
  4. ​**避免使用 eval**​:在 JavaScript 代码中避免使用 eval,因为它可能导致代码注入漏洞。

通过这些措施,开发者可以显著降低 XSS 攻击的风险,确保用户数据和网站安全。XSS 攻击的防护需要开发者在处理用户输入时保持谨慎,任何未经处理的用户输入都可能成为攻击的入口。