网络安全实战实践笔记
这次做网络安全加固,从 XSS 到 CSRF,。
XSS 攻击
反射型 XSS
<!-- 恶意 URL -->
http://example.com/search?q=<script>alert('XSS')</script>
<!-- 页面渲染 -->
<div>搜索结果: <script>alert('XSS')</script></div>
存储型 XSS
// 用户提交包含恶意脚本的内容
const comment = '<script>alert("XSS")</script>';
db.insert('comments', { comment });
// 页面渲染时直接输出
<div>${comment}</div>
DOM 型 XSS
// 从 URL 获取参数
const param = new URLSearchParams(window.location.search).get('data');
// 直接操作 DOM
document.getElementById('output').innerHTML = param;
XSS 防御
输出编码
// 使用模板引擎自动编码
// EJS
<div><%= userComment %></div>
// React(自动转义)
<div>{userComment}</div>
// Vue
<div>{{ userComment }}</div>
输入验证
// 白名单验证
function sanitizeInput(input) {
// 只允许字母、数字、空格
return input.replace(/[^a-zA-Z0-9\s]/g, '');
}
// 使用验证库
const validator = require('validator');
if (!validator.isEmail(userEmail)) {
throw new Error('Invalid email');
}
Content Security Policy
# CSP 头
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; font-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self';
HTTPOnly Cookie
# 设置 HTTPOnly Cookie
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict
CSRF 攻击
攻击原理
<!-- 恶意网站 -->
<img src="https://bank.example.com/transfer?to=hacker&amount=10000">
CSRF Token
// 生成 CSRF Token
const crypto = require('crypto');
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex');
}
// 在 Session 中存储 Token
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = generateCSRFToken();
}
next();
});
// 在表单中包含 Token
app.get('/form', (req, res) => {
res.send(`
<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="${req.session.csrfToken}">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
`);
});
// 验证 Token
app.post('/submit', (req, res) => {
const token = req.body.csrf_token;
if (token !== req.session.csrfToken) {
return res.status(403).send('Invalid CSRF token');
}
// 处理表单提交
...
});
SameSite Cookie
# 设置 SameSite Cookie
Set-Cookie: sessionid=abc123; SameSite=Strict
SQL 注入
注入示例
// 不安全的查询(易受 SQL 注入)
const query = `SELECT * FROM users WHERE username = '${username}' AND password = '${password}'`;
db.query(query);
// 恶意输入
username = "admin' --"
password = "anything"
// 实际执行的 SQL
SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
防御方法
// 使用参数化查询
const query = 'SELECT * FROM users WHERE username = ? AND password = ?';
db.query(query, [username, password]);
// 使用 ORM
const user = await User.findOne({
where: {
username,
password
}
});
// 使用输入验证
if (!validator.isAlphanumeric(username)) {
throw new Error('Invalid username');
}
认证与授权
密码存储
const bcrypt = require('bcrypt');
// 哈希密码
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// 验证密码
const isValid = await bcrypt.compare(password, hashedPassword);
JWT 认证
const jwt = require('jsonwebtoken');
// 生成 Token
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
// 验证 Token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log(decoded.userId);
基于角色的访问控制(RBAC)
// 用户模型
const User = {
roles: ['user', 'admin']
};
// 权限检查
function hasPermission(user, permission) {
const rolePermissions = {
user: ['read'],
admin: ['read', 'write', 'delete']
};
return user.roles.some(role =>
rolePermissions[role]?.includes(permission)
);
}
// 中间件
function requirePermission(permission) {
return (req, res, next) => {
if (!hasPermission(req.user, permission)) {
return res.status(403).send('Permission denied');
}
next();
};
}
// 使用中间件
app.get('/admin', requirePermission('write'), (req, res) => {
res.send('Admin page');
});
HTTPS 加密
配置 HTTPS
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
安全头
# 安全响应头
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
安全监控
安全日志
// 记录安全事件
function logSecurityEvent(event) {
logger.info({
type: 'security',
event: event.type,
userId: event.userId,
ip: event.ip,
userAgent: event.userAgent,
timestamp: new Date()
});
}
// 登录失败
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user || !await bcrypt.compare(password, user.password)) {
logSecurityEvent({
type: 'login_failed',
userId: user?.id,
ip: req.ip,
userAgent: req.headers['user-agent']
});
return res.status(401).send('Invalid credentials');
}
logSecurityEvent({
type: 'login_success',
userId: user.id,
ip: req.ip,
userAgent: req.headers['user-agent']
});
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
res.json({ token });
});
安全告警
// 检测异常行为
async function detectSuspiciousActivity(userId) {
const recentAttempts = await db.query(`
SELECT COUNT(*) as count
FROM security_events
WHERE user_id = ? AND type = 'login_failed'
AND timestamp > DATE_SUB(NOW(), INTERVAL 1 HOUR)
`, [userId]);
if (recentAttempts[0].count > 5) {
// 发送告警
sendAlert({
type: 'suspicious_activity',
userId,
attempts: recentAttempts[0].count
});
// 锁定账户
await User.update(userId, { locked: true });
}
}
踩过的坑
坑一:忘记转义输出
使用了模板引擎,但忘记转义输出。
解决:始终使用模板引擎的转义功能。
坑二:Cookie 没有设置 HttpOnly
Cookie 可以被 JavaScript 访问,导致 XSS 攻击。
解决:设置 HttpOnly 和 Secure。
res.cookie('sessionid', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
坑三:敏感信息泄露
错误信息中包含敏感信息。
解决:生产环境不暴露详细信息。
if (process.env.NODE_ENV === 'production') {
res.status(500).send('Internal server error');
} else {
res.status(500).send(error.message);
}
安全检查清单
输入验证
- 所有输入都经过验证
- 使用白名单验证
- 限制输入长度
输出编码
- HTML 内容正确转义
- URL 参数正确编码
- JSON 正确转义
认证授权
- 密码正确哈希
- Token 正确验证
- 权限正确检查
数据保护
- HTTPS 启用
- Cookie 安全设置
- 敏感数据加密
监控告警
- 安全日志记录
- 异常行为检测
- 安全事件告警
写在最后
网络安全这东西,不是技术问题,是意识和流程问题。
解决了:
- XSS 攻击
- CSRF 攻击
- SQL 注入
- 认证授权问题
带来了:
- 开发成本
- 性能影响
- 用户体验变化
安全加固之前先评估:
- 业务需求
- 用户规模
- 预算
- 法规要求
不是所有安全措施都需要,但基础安全不能少。
这次网络安全加固花了一个月,从 XSS 到 CSRF,再到完整的安全体系。加固完成后,安全漏洞数量从 10 个降到 0 个,安全事件减少了 90%。
可用性说明:本文发布于 2019 年 6 月,距今已超过五年。文中涉及的软件版本、接口、下载地址、命令参数和操作界面可能已经发生变化,部分方案在当前环境下可能失效。请结合官方最新文档核对后再操作,生产环境使用前务必先行验证。
版权声明: 本文首发于 指尖魔法屋-网络安全实战实践笔记(https://blog.thinkmoon.cn/post/76-network-security-xss-csrf-defense-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。