owasp-security
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOWASP Top 10 Security
OWASP Top 10 安全实践
Prevent common security vulnerabilities in web applications.
预防Web应用中的常见安全漏洞。
OWASP Top 10 (2021)
OWASP Top 10 (2021)
| # | Vulnerability | Prevention |
|---|---|---|
| A01 | Broken Access Control | Proper authorization checks |
| A02 | Cryptographic Failures | Strong encryption, secure storage |
| A03 | Injection | Input validation, parameterized queries |
| A04 | Insecure Design | Threat modeling, secure patterns |
| A05 | Security Misconfiguration | Hardened configs, no defaults |
| A06 | Vulnerable Components | Dependency scanning, updates |
| A07 | Auth Failures | MFA, secure session management |
| A08 | Data Integrity Failures | Input validation, signed updates |
| A09 | Logging Failures | Comprehensive audit logs |
| A10 | SSRF | URL validation, allowlists |
| 编号 | 漏洞类型 | 预防措施 |
|---|---|---|
| A01 | 访问控制失效 | 实施适当的授权检查 |
| A02 | 加密失效 | 采用强加密算法、安全存储方式 |
| A03 | 注入攻击 | 输入验证、参数化查询 |
| A04 | 不安全设计 | 威胁建模、采用安全设计模式 |
| A05 | 安全配置错误 | 加固配置、禁用默认配置 |
| A06 | 易受攻击的组件 | 依赖扫描、及时更新组件 |
| A07 | 身份验证失效 | 多因素认证、安全会话管理 |
| A08 | 数据完整性失效 | 输入验证、签名更新 |
| A09 | 日志记录失效 | 完善审计日志 |
| A10 | SSRF(服务器端请求伪造) | URL验证、使用白名单 |
A01: Broken Access Control
A01:访问控制失效
Prevention Patterns
预防模式
typescript
// ❌ BAD: No authorization check
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});
// ✅ GOOD: Verify ownership
app.get('/api/users/:id', authenticate, async (req, res) => {
const userId = req.params.id;
// Users can only access their own data
if (req.user.id !== userId && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await db.users.findById(userId);
res.json(user);
});
// ✅ GOOD: Role-based access control (RBAC)
const requireRole = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!roles.includes(req.user?.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
app.delete('/api/posts/:id', authenticate, requireRole('admin', 'moderator'), deletePost);typescript
// ❌ 不良示例:未进行授权检查
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user);
});
// ✅ 良好示例:验证数据归属权
app.get('/api/users/:id', authenticate, async (req, res) => {
const userId = req.params.id;
// 用户仅能访问自身数据
if (req.user.id !== userId && req.user.role !== 'admin') {
return res.status(403).json({ error: '禁止访问' });
}
const user = await db.users.findById(userId);
res.json(user);
});
// ✅ 良好示例:基于角色的访问控制(RBAC)
const requireRole = (...roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!roles.includes(req.user?.role)) {
return res.status(403).json({ error: '权限不足' });
}
next();
};
};
app.delete('/api/posts/:id', authenticate, requireRole('admin', 'moderator'), deletePost);Insecure Direct Object Reference (IDOR)
不安全的直接对象引用(IDOR)
typescript
// ❌ BAD: Predictable IDs exposed
GET /api/invoices/1001
GET /api/invoices/1002 // Can enumerate others' invoices
// ✅ GOOD: Use UUIDs + ownership check
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoices.findOne({
id: req.params.id,
userId: req.user.id, // Enforce ownership
});
if (!invoice) {
return res.status(404).json({ error: 'Not found' });
}
res.json(invoice);
});typescript
// ❌ 不良示例:暴露可预测的ID
GET /api/invoices/1001
GET /api/invoices/1002 // 可枚举他人发票
// ✅ 良好示例:使用UUID + 归属权校验
app.get('/api/invoices/:id', authenticate, async (req, res) => {
const invoice = await db.invoices.findOne({
id: req.params.id,
userId: req.user.id, // 强制校验归属权
});
if (!invoice) {
return res.status(404).json({ error: '未找到资源' });
}
res.json(invoice);
});A02: Cryptographic Failures
A02:加密失效
Password Hashing
密码哈希
typescript
import bcrypt from 'bcrypt';
import crypto from 'crypto';
// ✅ Hash passwords with bcrypt
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// ✅ Secure token generation
function generateSecureToken(length = 32): string {
return crypto.randomBytes(length).toString('hex');
}
// ✅ Encrypt sensitive data
const ALGORITHM = 'aes-256-gcm';
const KEY = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', 32);
function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
encrypted,
iv: iv.toString('hex'),
tag: cipher.getAuthTag().toString('hex'),
};
}
function decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'));
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}typescript
import bcrypt from 'bcrypt';
import crypto from 'crypto';
// ✅ 使用bcrypt对密码进行哈希
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// ✅ 生成安全令牌
function generateSecureToken(length = 32): string {
return crypto.randomBytes(length).toString('hex');
}
// ✅ 加密敏感数据
const ALGORITHM = 'aes-256-gcm';
const KEY = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', 32);
function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return {
encrypted,
iv: iv.toString('hex'),
tag: cipher.getAuthTag().toString('hex'),
};
}
function decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'));
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}Secure Headers
安全响应头
typescript
import helmet from 'helmet';
app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'strict-dynamic'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
},
}));typescript
import helmet from 'helmet';
app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'strict-dynamic'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
},
}));A03: Injection
A03:注入攻击
SQL Injection Prevention
SQL注入预防
typescript
// ❌ BAD: String concatenation
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ GOOD: Parameterized queries
// With Prisma
const user = await prisma.user.findUnique({ where: { email } });
// With raw SQL (parameterized)
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
// With Knex
const user = await knex('users').where({ email }).first();typescript
// ❌ 不良示例:字符串拼接
const query = `SELECT * FROM users WHERE email = '${email}'`;
// ✅ 良好示例:参数化查询
// 使用Prisma
const user = await prisma.user.findUnique({ where: { email } });
// 使用原生SQL(参数化)
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
// 使用Knex
const user = await knex('users').where({ email }).first();NoSQL Injection Prevention
NoSQL注入预防
typescript
// ❌ BAD: Direct user input in query
const user = await User.findOne({ username: req.body.username });
// Attack: { "username": { "$gt": "" } } returns first user
// ✅ GOOD: Validate input type
import { z } from 'zod';
const loginSchema = z.object({
username: z.string().min(3).max(50),
password: z.string().min(8),
});
app.post('/login', async (req, res) => {
const { username, password } = loginSchema.parse(req.body);
const user = await User.findOne({ username: String(username) });
// ...
});typescript
// ❌ 不良示例:直接将用户输入用于查询
const user = await User.findOne({ username: req.body.username });
// 攻击 payload: { "username": { "$gt": "" } } 会返回第一个用户
// ✅ 良好示例:验证输入类型
import { z } from 'zod';
const loginSchema = z.object({
username: z.string().min(3).max(50),
password: z.string().min(8),
});
app.post('/login', async (req, res) => {
const { username, password } = loginSchema.parse(req.body);
const user = await User.findOne({ username: String(username) });
// ...
});Command Injection Prevention
命令注入预防
typescript
import { execFile } from 'child_process';
// ❌ BAD: Shell injection
exec(`convert ${userInput} output.png`); // userInput: "; rm -rf /"
// ✅ GOOD: Use execFile with array args
execFile('convert', [userInput, 'output.png'], (error, stdout) => {
// Safe - arguments are not shell-interpreted
});
// ✅ GOOD: Validate and sanitize
const allowedFormats = ['png', 'jpg', 'gif'];
if (!allowedFormats.includes(format)) {
throw new Error('Invalid format');
}typescript
import { execFile } from 'child_process';
// ❌ 不良示例:Shell注入
exec(`convert ${userInput} output.png`); // userInput: "; rm -rf /"
// ✅ 良好示例:使用execFile并传入参数数组
execFile('convert', [userInput, 'output.png'], (error, stdout) => {
// 安全 - 参数不会被Shell解析
});
// ✅ 良好示例:验证并清理输入
const allowedFormats = ['png', 'jpg', 'gif'];
if (!allowedFormats.includes(format)) {
throw new Error('无效格式');
}A04: Insecure Design
A04:不安全设计
Rate Limiting
速率限制
typescript
import rateLimit from 'express-rate-limit';
// General rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
// Strict limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 failed attempts
skipSuccessfulRequests: true,
});
app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);typescript
import rateLimit from 'express-rate-limit';
// 通用速率限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个窗口最多100次请求
standardHeaders: true,
legacyHeaders: false,
});
// 身份验证端点的严格速率限制
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1小时
max: 5, // 最多5次失败尝试
skipSuccessfulRequests: true,
});
app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);Input Validation
输入验证
typescript
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
password: z.string()
.min(8)
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[a-z]/, 'Must contain lowercase')
.regex(/[0-9]/, 'Must contain number')
.regex(/[^A-Za-z0-9]/, 'Must contain special character'),
age: z.number().int().min(13).max(120),
role: z.enum(['user', 'admin']).default('user'),
});
app.post('/api/users', async (req, res) => {
try {
const data = userSchema.parse(req.body);
// Validated data is safe to use
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ errors: error.errors });
}
throw error;
}
});typescript
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
password: z.string()
.min(8)
.regex(/[A-Z]/, '必须包含大写字母')
.regex(/[a-z]/, '必须包含小写字母')
.regex(/[0-9]/, '必须包含数字')
.regex(/[^A-Za-z0-9]/, '必须包含特殊字符'),
age: z.number().int().min(13).max(120),
role: z.enum(['user', 'admin']).default('user'),
});
app.post('/api/users', async (req, res) => {
try {
const data = userSchema.parse(req.body);
// 验证后的数据可安全使用
} catch (error) {
if (error instanceof z.ZodError) {
return res.status(400).json({ errors: error.errors });
}
throw error;
}
});A05: Security Misconfiguration
A05:安全配置错误
Environment Configuration
环境配置
typescript
// ✅ Never expose stack traces in production
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack); // Log for debugging
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message,
});
});
// ✅ Disable sensitive headers
app.disable('x-powered-by');
// ✅ Secure cookie configuration
app.use(session({
secret: process.env.SESSION_SECRET!,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
resave: false,
saveUninitialized: false,
}));typescript
// ✅ 生产环境绝不暴露堆栈跟踪
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack); // 日志用于调试
res.status(500).json({
error: process.env.NODE_ENV === 'production'
? '内部服务器错误'
: err.message,
});
});
// ✅ 禁用敏感响应头
app.disable('x-powered-by');
// ✅ 安全Cookie配置
app.use(session({
secret: process.env.SESSION_SECRET!,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000, // 24小时
},
resave: false,
saveUninitialized: false,
}));A06: Vulnerable Components
A06:易受攻击的组件
Dependency Scanning
依赖扫描
bash
undefinedbash
undefinedCheck for vulnerabilities
检查漏洞
npm audit
npm audit fix
npm audit
npm audit fix
Use Snyk for deeper scanning
使用Snyk进行深度扫描
npx snyk test
npx snyk monitor
npx snyk test
npx snyk monitor
Keep dependencies updated
保持依赖更新
npx npm-check-updates -u
```json
// package.json - Use exact versions or ranges
{
"dependencies": {
"express": "^4.18.0", // Minor updates OK
"lodash": "4.17.21" // Exact version
},
"overrides": {
"vulnerable-package": "^2.0.0" // Force safe version
}
}npx npm-check-updates -u
```json
// package.json - 使用精确版本或版本范围
{
"dependencies": {
"express": "^4.18.0", // 允许小版本更新
"lodash": "4.17.21" // 精确版本
},
"overrides": {
"vulnerable-package": "^2.0.0" // 强制使用安全版本
}
}A07: Authentication Failures
A07:身份验证失效
Secure Session Management
安全会话管理
typescript
import jwt from 'jsonwebtoken';
// ✅ JWT with short expiry + refresh tokens
function generateTokens(userId: string) {
const accessToken = jwt.sign(
{ userId },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // Short-lived
);
const refreshToken = jwt.sign(
{ userId, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// ✅ Secure password reset
async function initiatePasswordReset(email: string) {
const user = await db.users.findByEmail(email);
if (!user) return; // Don't reveal if email exists
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
await db.passwordResets.create({
userId: user.id,
token: hashedToken,
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
});
await sendEmail(email, `Reset link: /reset?token=${token}`);
}typescript
import jwt from 'jsonwebtoken';
// ✅ 使用短有效期JWT + 刷新令牌
function generateTokens(userId: string) {
const accessToken = jwt.sign(
{ userId },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // 短有效期
);
const refreshToken = jwt.sign(
{ userId, type: 'refresh' },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// ✅ 安全的密码重置流程
async function initiatePasswordReset(email: string) {
const user = await db.users.findByEmail(email);
if (!user) return; // 不泄露邮箱是否存在
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto.createHash('sha256').update(token).digest('hex');
await db.passwordResets.create({
userId: user.id,
token: hashedToken,
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1小时有效期
});
await sendEmail(email, `重置链接: /reset?token=${token}`);
}Multi-Factor Authentication
多因素认证
typescript
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
// Setup TOTP
async function setupMFA(userId: string) {
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(userId, 'MyApp', secret);
const qrCode = await QRCode.toDataURL(otpauth);
await db.users.update(userId, { mfaSecret: encrypt(secret) });
return { qrCode, secret };
}
// Verify TOTP
function verifyMFA(token: string, secret: string): boolean {
return authenticator.verify({ token, secret });
}typescript
import { authenticator } from 'otplib';
import QRCode from 'qrcode';
// 设置TOTP
async function setupMFA(userId: string) {
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(userId, 'MyApp', secret);
const qrCode = await QRCode.toDataURL(otpauth);
await db.users.update(userId, { mfaSecret: encrypt(secret) });
return { qrCode, secret };
}
// 验证TOTP
function verifyMFA(token: string, secret: string): boolean {
return authenticator.verify({ token, secret });
}A08: XSS Prevention
A08:XSS预防
typescript
// ✅ React auto-escapes by default
const UserProfile = ({ user }) => (
<div>{user.name}</div> // Safe - auto-escaped
);
// ⚠️ Dangerous - avoid if possible
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// ✅ Sanitize HTML if needed
import DOMPurify from 'dompurify';
const sanitizedHtml = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// ✅ Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
scriptSrc: ["'self'"], // No inline scripts
styleSrc: ["'self'", "'unsafe-inline'"],
},
}));typescript
// ✅ React默认自动转义
const UserProfile = ({ user }) => (
<div>{user.name}</div> // 安全 - 自动转义
);
// ⚠️ 危险场景:尽量避免使用
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />
// ✅ 若需使用HTML则先清理
import DOMPurify from 'dompurify';
const sanitizedHtml = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// ✅ 内容安全策略(CSP)
app.use(helmet.contentSecurityPolicy({
directives: {
scriptSrc: ["'self'"], // 禁止内联脚本
styleSrc: ["'self'", "'unsafe-inline'"],
},
}));A09: Logging & Monitoring
A09:日志记录与监控
typescript
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// ✅ Log security events
function logSecurityEvent(event: string, details: object) {
logger.warn({
type: 'security',
event,
...details,
timestamp: new Date().toISOString(),
});
}
// Usage
logSecurityEvent('failed_login', { email, ip: req.ip, userAgent: req.headers['user-agent'] });
logSecurityEvent('access_denied', { userId, resource, action });
logSecurityEvent('suspicious_activity', { userId, pattern: 'rapid_requests' });typescript
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// ✅ 记录安全事件
function logSecurityEvent(event: string, details: object) {
logger.warn({
type: 'security',
event,
...details,
timestamp: new Date().toISOString(),
});
}
// 使用示例
logSecurityEvent('failed_login', { email, ip: req.ip, userAgent: req.headers['user-agent'] });
logSecurityEvent('access_denied', { userId, resource, action });
logSecurityEvent('suspicious_activity', { userId, pattern: 'rapid_requests' });A10: SSRF Prevention
A10:SSRF预防
typescript
import { URL } from 'url';
// ✅ Validate URLs against allowlist
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
function isAllowedUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
// Block private IPs
const privatePatterns = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[01])\./,
/^192\.168\./,
/^0\./,
/^169\.254\./, // Link-local
];
if (privatePatterns.some(p => p.test(url.hostname))) {
return false;
}
// Check allowlist
return ALLOWED_HOSTS.includes(url.hostname);
} catch {
return false;
}
}
app.post('/api/fetch-url', async (req, res) => {
const { url } = req.body;
if (!isAllowedUrl(url)) {
return res.status(400).json({ error: 'URL not allowed' });
}
const response = await fetch(url);
// ...
});typescript
import { URL } from 'url';
// ✅ 根据白名单验证URL
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];
function isAllowedUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
// 阻止私有IP
const privatePatterns = [
/^localhost$/i,
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[01])\./,
/^192\.168\./,
/^0\./,
/^169\.254\./, // 链路本地地址
];
if (privatePatterns.some(p => p.test(url.hostname))) {
return false;
}
// 检查白名单
return ALLOWED_HOSTS.includes(url.hostname);
} catch {
return false;
}
}
app.post('/api/fetch-url', async (req, res) => {
const { url } = req.body;
if (!isAllowedUrl(url)) {
return res.status(400).json({ error: 'URL不被允许' });
}
const response = await fetch(url);
// ...
});Security Checklist
安全检查清单
markdown
undefinedmarkdown
undefinedPre-Deployment Checklist
部署前检查清单
Authentication
身份验证
- Passwords hashed with bcrypt (cost ≥ 12)
- JWT tokens have short expiry
- Session cookies are httpOnly, secure, sameSite
- Rate limiting on auth endpoints
- 密码使用bcrypt哈希(cost ≥ 12)
- JWT令牌设置短有效期
- Session Cookie配置为httpOnly、secure、sameSite
- 身份验证端点启用速率限制
Authorization
授权
- All endpoints have auth checks
- RBAC implemented correctly
- No IDOR vulnerabilities
- 所有端点均有身份验证检查
- 正确实现RBAC
- 无IDOR漏洞
Input/Output
输入/输出
- All input validated with Zod/Joi
- SQL queries parameterized
- XSS prevented (CSP, escaping)
- File uploads validated and sandboxed
- 所有输入通过Zod/Joi验证
- SQL查询使用参数化
- 已预防XSS(CSP、转义)
- 文件上传已验证并沙箱化
Infrastructure
基础设施
- HTTPS enforced
- Security headers configured
- Dependencies audited
- Secrets in environment variables
- 强制使用HTTPS
- 配置安全响应头
- 已审计依赖
- 密钥存储在环境变量中
Monitoring
监控
- Security events logged
- Error monitoring enabled
- Alerts configured
undefined- 已记录安全事件
- 启用错误监控
- 配置告警
undefinedResources
参考资源
- OWASP Top 10: https://owasp.org/Top10/
- OWASP Cheat Sheets: https://cheatsheetseries.owasp.org/
- Node.js Security: https://nodejs.org/en/docs/guides/security/
- Snyk: https://snyk.io/
- OWASP Top 10: https://owasp.org/Top10/
- OWASP cheat sheets: https://cheatsheetseries.owasp.org/
- Node.js 安全指南: https://nodejs.org/en/docs/guides/security/
- Snyk: https://snyk.io/