149 lines
3.4 KiB
Python
149 lines
3.4 KiB
Python
"""
|
||
Express.js 框架安全知识
|
||
"""
|
||
|
||
from ..base import KnowledgeDocument, KnowledgeCategory
|
||
|
||
|
||
EXPRESS_SECURITY = KnowledgeDocument(
|
||
id="framework_express",
|
||
title="Express.js Security",
|
||
category=KnowledgeCategory.FRAMEWORK,
|
||
tags=["express", "nodejs", "javascript", "api"],
|
||
content="""
|
||
Express.js 是Node.js最流行的Web框架,需要注意多种安全问题。
|
||
|
||
## 常见漏洞模式
|
||
|
||
### NoSQL注入
|
||
```javascript
|
||
// 危险 - MongoDB查询注入
|
||
app.post('/login', async (req, res) => {
|
||
const user = await User.findOne({
|
||
username: req.body.username,
|
||
password: req.body.password
|
||
});
|
||
// 攻击: {"username": {"$ne": ""}, "password": {"$ne": ""}}
|
||
});
|
||
|
||
// 安全 - 类型验证
|
||
app.post('/login', async (req, res) => {
|
||
const { username, password } = req.body;
|
||
if (typeof username !== 'string' || typeof password !== 'string') {
|
||
return res.status(400).json({ error: 'Invalid input' });
|
||
}
|
||
const user = await User.findOne({ username, password });
|
||
});
|
||
```
|
||
|
||
### 原型污染
|
||
```javascript
|
||
// 危险 - 合并用户输入
|
||
const merge = require('lodash.merge');
|
||
app.post('/config', (req, res) => {
|
||
merge(config, req.body);
|
||
// 攻击: {"__proto__": {"isAdmin": true}}
|
||
});
|
||
|
||
// 安全 - 使用Object.assign或白名单
|
||
app.post('/config', (req, res) => {
|
||
const allowed = ['theme', 'language'];
|
||
allowed.forEach(key => {
|
||
if (req.body[key]) config[key] = req.body[key];
|
||
});
|
||
});
|
||
```
|
||
|
||
### 命令注入
|
||
```javascript
|
||
// 危险
|
||
const { exec } = require('child_process');
|
||
app.get('/ping', (req, res) => {
|
||
exec(`ping ${req.query.host}`, (err, stdout) => {
|
||
res.send(stdout);
|
||
});
|
||
});
|
||
|
||
// 安全 - 使用execFile和参数数组
|
||
const { execFile } = require('child_process');
|
||
app.get('/ping', (req, res) => {
|
||
execFile('ping', ['-c', '4', req.query.host], (err, stdout) => {
|
||
res.send(stdout);
|
||
});
|
||
});
|
||
```
|
||
|
||
### XSS
|
||
```javascript
|
||
// 危险 - 直接输出用户输入
|
||
app.get('/search', (req, res) => {
|
||
res.send(`<h1>Results for: ${req.query.q}</h1>`);
|
||
});
|
||
|
||
// 安全 - 使用模板引擎或转义
|
||
const escape = require('escape-html');
|
||
app.get('/search', (req, res) => {
|
||
res.send(`<h1>Results for: ${escape(req.query.q)}</h1>`);
|
||
});
|
||
```
|
||
|
||
### 路径遍历
|
||
```javascript
|
||
// 危险
|
||
app.get('/files/:name', (req, res) => {
|
||
res.sendFile(`/uploads/${req.params.name}`);
|
||
});
|
||
|
||
// 安全 - 验证路径
|
||
const path = require('path');
|
||
app.get('/files/:name', (req, res) => {
|
||
const safePath = path.join('/uploads', req.params.name);
|
||
if (!safePath.startsWith('/uploads/')) {
|
||
return res.status(400).send('Invalid path');
|
||
}
|
||
res.sendFile(safePath);
|
||
});
|
||
```
|
||
|
||
### 不安全的依赖
|
||
```javascript
|
||
// 危险 - 使用有漏洞的包
|
||
const serialize = require('node-serialize');
|
||
const obj = serialize.unserialize(userInput); // RCE!
|
||
|
||
// 安全 - 使用JSON
|
||
const obj = JSON.parse(userInput);
|
||
```
|
||
|
||
## 安全中间件
|
||
```javascript
|
||
const helmet = require('helmet');
|
||
const rateLimit = require('express-rate-limit');
|
||
|
||
// 安全头
|
||
app.use(helmet());
|
||
|
||
// 速率限制
|
||
app.use(rateLimit({
|
||
windowMs: 15 * 60 * 1000,
|
||
max: 100
|
||
}));
|
||
|
||
// CORS
|
||
const cors = require('cors');
|
||
app.use(cors({
|
||
origin: 'https://example.com',
|
||
credentials: true
|
||
}));
|
||
```
|
||
|
||
## 安全检查清单
|
||
1. 使用helmet设置安全头
|
||
2. 实现速率限制
|
||
3. 验证所有用户输入类型
|
||
4. 使用参数化查询
|
||
5. 定期更新依赖 (npm audit)
|
||
6. 不要在错误中暴露堆栈信息
|
||
""",
|
||
)
|