Chiranth-Janardhan-moger/sqlguardjs
GitHub: Chiranth-Janardhan-moger/sqlguardjs
SQLGuardJS 是一个 Express 请求验证中间件,通过启发式检测和加权评分拦截 SQL 注入、NoSQL 注入与 XSS 攻击载荷。
Stars: 17 | Forks: 0
# SQLGuardJS
[](https://www.npmjs.com/package/sqlguardjs)
[](https://github.com/Chiranth-Janardhan-moger/sqlguardjs/actions/workflows/ci.yml)
[](https://nodejs.org/)
[](https://github.com/Chiranth-Janardhan-moger/sqlguardjs/blob/main/LICENSE)
[](https://www.npmjs.com/package/sqlguardjs)
在不到一分钟内保护您的 Express 应用免受 SQL 注入、XSS 和 NoSQL 注入攻击。
SQLGuardJS 是一个 Express 请求验证层,用于处理常见的 SQL 注入、NoSQL 注入和跨站脚本 (XSS) payload。它提供进程内标准化、结构化 token 分析、加权信号、安全的路由器 API、命令行扫描器、结构化管理员日志、感知 schema 的路由检查、安全的学习事件以及请求大小安全限制。
SQLGuardJS 是一种纵深防御控制措施。它不能替代参数化 SQL 查询、安全的 ORM 使用方式、上下文感知的输出编码、HTML 消毒、CSP、最小权限数据库账户或应用安全测试。
## 30 秒快速开始
安装:
```
npm install sqlguardjs
```
使用:
```
const express = require('express');
const { sqlguardjs } = require('sqlguardjs');
const app = express();
const guard = sqlguardjs();
app.use(express.json());
app.use(guard.global());
app.post('/login', guard.route(), (req, res) => {
res.json({ ok: true });
});
app.listen(3000);
```
测试被阻止的请求:
```
curl "http://localhost:3000/login?id=1%20UNION%20SELECT%20password%20FROM%20users--"
```
对于路由 schema:
```
app.post('/login', guard.route({
schema: {
body: {
allowed: ['email', 'password'],
required: ['email', 'password']
},
query: []
}
}), loginHandler);
```
## 使用前后对比
没有 SQLGuardJS:
```
Attacker
-> Express route
-> Application logic
-> Database or HTML rendering
```
使用 SQLGuardJS:
```
Attacker
-> SQLGuardJS
-> Blocked with 403 if malicious, otherwise passed to the Express route.
```
## 为什么 `global()` 和 `route()` 同时存在
Express 在路由匹配之前不会填充 `req.params`。因此,SQLGuardJS 提供了两个防护点:
- `guard.global()` 在路由处理器运行之前扫描请求体、查询字符串、headers 和 cookies。
- `guard.route()` 扫描尚未被扫描的请求源,然后在 Express 解析路由后检查 `req.params` 和可选的路由 schema。
当您希望在路由逻辑运行之前检查所有常见的请求输入时,请同时使用两者。
## 性能
SQLGuardJS 会在内存中扫描已解码的请求数据。它不会调用数据库或外部服务。实际延迟取决于 payload 大小、嵌套深度、是否启用日志记录以及 schema 检查。
默认限制(例如 `maxPayloadLength`、`maxDepth` 和 `maxFields`)旨在确保最坏情况下的请求处理保持在受限范围内。
对于高吞吐量的批量导入 endpoint,请设置特定于路由的 schema,并降低 `maxFields` 或 `maxPayloadLength`,以匹配您的 endpoint 应当接受的最大有效请求。如果某个 endpoint 有意接受大型文件或原始技术内容,请单独验证该上传路径,并在该路由上为扫描器使用 `skip`。
## 功能
- 检测 SQL 注入模式,包括布尔盲注、`UNION SELECT`、堆叠查询、破坏性 DDL、时间延迟探测和元数据枚举。
- 对布尔谓词、堆叠语句和元数据枚举使用结构化 SQL 片段分析,而不是仅仅依赖于精确的正则表达式 payload 字符串。
- 处理 SQL 注释规避,包括 `UNION/**/SELECT` 和 `UN/**/ION SEL/**/ECT`。
- 检测 NoSQL 操作符探测,例如 `$where`、`$ne`、`$gt`、`$regex`、`$or` 和 `$and`。
- 检测 XSS payload,包括 script 标签、事件处理程序属性、JavaScript 执行 URL、危险的 HTML 容器、`srcdoc` 和 `data:text/html`。
- 对 JavaScript 伪协议主体进行 token 化处理,以捕获构造函数链和全局对象执行路径。
- 标准化重复的 URL 编码、`%uXXXX`、HTML 实体、可打印的 Base64、加号分隔的查询字符串、Unicode 空格、零宽字符和控制字符分割。
- 扫描 `req.query`、`req.body`、`req.headers`、`req.params`、`req.cookies`、嵌套对象、数组、对象键、字符串和 Buffer。
- 支持安全的路由器 API,包括 `sqlguardjs().global()`、`sqlguardjs().route()` 和 `secureRouter()`。
- 支持加权置信度评分、可疑请求升级、感知 schema 的路由检查、安全学习事件、试运行部署、路由跳过、结构化管理员日志和请求大小安全限制。
- 包含 TypeScript 定义和真实的 Express 集成测试。
需要 Node.js 18 或更高版本。
## Express 用法
在 body 解析器之后、受保护的路由之前注册 SQLGuardJS。SQLGuardJS 会扫描 Express 在 `req.query`、`req.body`、`req.headers`、`req.params`、`req.cookies` 和 `req.rawBody` 上公开的数据,包括普通对象、数组、buffer、`URLSearchParams`、`Map` 和 `Set` 容器。它无法检查上游解析器或捕获中间件未附加到请求中的字节。
```
const express = require('express');
const { sqlguardjs } = require('sqlguardjs');
const app = express();
const guard = sqlguardjs({
threshold: 0.5,
suspiciousThreshold: 0.2,
logAttacks: true
});
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true, limit: '1mb' }));
app.use(express.text({ type: ['text/plain', 'application/*+json'], limit: '1mb' }));
// Global scanner checks body, query, headers, and cookies.
app.use(guard.global({ scanParams: false }));
// Route verifier checks any unscanned sources plus params after Express resolves them.
app.get('/users/:id', guard.route(), (req, res) => {
res.json({ id: req.params.id });
});
app.post('/login', guard.route({
schema: {
body: {
allowed: ['email', 'password'],
required: ['email', 'password']
},
query: []
}
}), (req, res) => {
res.json({ ok: true });
});
app.listen(3000);
```
对于稍后解析原始流的 webhook 或自定义解析器 endpoint,请在 SQLGuardJS 之前捕获原始主体,并将其作为 `req.rawBody` 公开:
```
app.use((req, res, next) => {
let rawBody = '';
req.setEncoding('utf8');
req.on('data', chunk => { rawBody += chunk; });
req.on('end', () => {
req.rawBody = rawBody;
next();
});
req.on('error', next);
});
app.use(guard.global());
```
对于多部分 (multipart) endpoint,如果您需要检查表单字段,请在 SQLGuardJS 之前运行多部分解析器或原始主体捕获。文件内容通常应由特定于文件的控制措施来验证,而不是由此请求扫描器验证。
当您希望路由器自动处理全局请求扫描和路由级参数/schema 检查时,您还可以使用 `secureRouter()`:
```
const express = require('express');
const { secureRouter } = require('sqlguardjs');
const app = express();
const router = secureRouter({
threshold: 0.5,
suspiciousThreshold: 0.2
});
app.use(express.json({ limit: '1mb' }));
router.post('/login', {
schema: {
body: {
allowed: ['email', 'password'],
required: ['email', 'password']
},
query: []
}
}, (req, res) => {
res.json({ ok: true });
});
app.use('/api', router);
```
`secureRouter()` 会自动包装直接注册 HTTP 方法(如 `get`、`post`、`put` 和 `all`)。对于路径作用域内的 `router.use()` 或链式 `router.route()` 声明,请显式传入 `guard.route()`。
## 生产环境部署
首先在真实流量上使用 `dryRun: true`。这会记录检测结果,但不会阻止请求。
在试运行模式下,SQLGuardJS 会在首次检测后继续扫描整个请求。首次检测结果可通过 `req.sqlguardjs` 获取,该请求的所有检测结果可通过 `req.sqlguardjsDetections` 获取。
如果您的应用位于 nginx、Cloudflare、负载均衡器或平台代理之后,请在依赖基于 IP 的可疑请求升级之前,配置 Express 的 `trust proxy`。对于已认证的 API,建议使用带有 `rateLimitKey` 的稳定应用身份标识(例如用户 ID 加 IP)。
```
app.set('trust proxy', 1);
app.use(guard.global({
dryRun: true,
rateLimitKey: req => req.user?.id ? `${req.user.id}:${req.ip}` : req.ip,
logAttacks: event => {
// Admins see these events in the application logs or wherever this function sends them.
console.warn(JSON.stringify(event));
},
logFormat: 'json',
onThreat(event, req) {
console.warn({
label: event.label,
confidence: event.confidence,
path: event.path,
matchedSignalIds: event.matchedSignalIds,
requestId: event.requestId,
ip: req.ip
});
},
onCallbackError(error, context) {
console.error('SQLGuardJS callback failed', context);
}
}));
```
如果您的应用运行在 Render、Railway、Fly.io、AWS、Azure、GCP、Docker、PM2 或 VPS 上,这些事件将显示在您的常规 Node 日志输出的任何位置。如需仪表板展示,请传入 `onThreat` 并将事件存储在您的数据库、日志平台、SIEM 或告警系统中。
在引入生产日志时,请使用 `logFormat: 'json'`。文本日志会转义回车符和换行符,但结构化 JSON 对于面向行的日志解析器和 SIEM pipeline 来说更安全。
`logAttacks`、`onThreat` 和 `onLearningEvent` 抛出的故障会与请求处理隔离。请使用 `onCallbackError` 来监控日志记录、告警或审查队列的故障。
在查看日志并调整路由排除项之后,启用阻止模式。
```
app.use(guard.global({
dryRun: false,
threshold: 0.5,
skip: req => req.path === '/health'
}));
```
## 最终 SQL 查询防护
在您的应用存储某个值并随后将其拼接 到 SQL 字符串中之后,请求中间件无法再看到该值。对于这种二阶注入类别,请在数据库边界处检查最终的 SQL 字符串:
```
const { assertSafeSqlQuery } = require('sqlguardjs');
async function checkedQuery(db, sql, params) {
assertSafeSqlQuery(sql);
return db.query(sql, params);
}
```
这仍然是一道护栏,不能替代参数化查询。如果 unsafe 的动态 SQL 到达接收端 (sink),请使用它来实现失败即关闭 (fail closed)。
## 感知 Schema 的路由检查
Schema 允许您定义路由所需的字段。意外字段和缺失的必填字段将被报告为 `schema_violation`。
当规则使用 `required` 而没有使用 `allowed` 时,SQLGuardJS 会将必填字段视为允许的字段。仅当有意接受额外字段时,才设置 `allowUnknown: true`。
```
router.post('/login', {
schema: {
body: {
allowed: ['email', 'password'],
required: ['email', 'password']
},
query: []
}
}, handler);
```
路由 schema 键也可以进行全局配置:
```
const schemaGuard = sqlguardjs({
schemas: {
'POST /login': {
body: ['email', 'password'],
query: []
}
}
});
app.use(schemaGuard.global());
```
## 安全学习模式
学习模式会记录那些因为低于阻止阈值而被允许的可疑 payload。它不会自动重新训练或更改规则。
```
app.use(guard.global({
threshold: 0.9,
learning: true,
onLearningEvent(event) {
console.info(event.clusterKey, event.label, event.payloadPreview);
}
}));
```
使用学习事件进行审查、聚类和回归测试创建。不要基于实时流量自动进行训练,因为攻击者可能会投毒 (poison) 自学习系统。
学习模式仅记录至少匹配一个信号且低于阻止阈值的 payload。零信号 payload 无法被自动学习;请为任何确认的绕过添加对抗性测试。
## 流量调优
在收紧阈值之前,针对带有标签的类似生产的样本使用 `evaluatePayloads()`:
```
const { evaluatePayloads } = require('sqlguardjs');
const report = evaluatePayloads([
{ payload: 'hello world', label: 'benign' },
{ payload: "admin' OR 2>1", label: 'sqli' }
]);
console.log(report.summary.falsePositiveRate);
```
可接受的安全标签包括 `benign`、`safe`、`normal` 和 `clean`。诸如 `sqli`、`xss`、`nosql`、`malicious` 和 `attack` 之类的攻击标签,在跟踪假阴性 时会被视为恶意标签。
## 阈值
`threshold` 是阻止请求时的置信度分数。它是一个加权启发式分数,而不是统计概率。
- `0`:没有匹配到信号。
- `0.2` 到低于 `threshold`:可疑。默认情况下允许该请求通过,但来自同一 IP 的重复可疑请求可能会被升级处理。
- 大于等于 `threshold`:除非启用了 `dryRun`,否则将被阻止。
默认值:
```
guard.global({
threshold: 0.5,
suspiciousThreshold: 0.2
});
```
调优指南:
- 对于典型的 API 防护,保持 `threshold: 0.5`。
- 如果您的应用接受代码片段、HTML、SQL 文本或其他技术内容,请提高阈值,例如设为 `0.7`。
- 仅在阻止可疑输入可接受的高风险 endpoint 上降低阈值。
- 在生产环境中更改阈值之前,请使用 `dryRun: true`。
## 检测器 API
```
const { Detector } = require('sqlguardjs');
const detector = new Detector();
console.log(detector.detect('1 UNION/**/SELECT password FROM users--'));
console.log(detector.detect('{"password"="abc123"}'));
```
结果示例:
```
{
"label": "sqli",
"confidence": 1,
"scores": {
"sqli": 3,
"xss": 0
},
"matches": [
{
"id": "union-select",
"label": "sqli",
"confidence": 0.8
}
]
}
```
`matches` 对调试和测试很有用。请将其视为诊断输出,而不是长期使用的策略 API。
## 中间件选项
| 选项 | 默认值 | 描述 |
| --- | --- | --- |
| `threshold` | `0.5` | 当 `result.confidence >= threshold` 时阻止请求。 |
| `suspiciousThreshold` | `0.2` | 开始记录学习事件并跟踪低于 `threshold` 的非良性结果的重复探测。 |
| `rateLimitWindowMs` | `300000` | 每个 IP 重复可疑探测的滑动窗口时间。 |
| `maxSuspiciousRequests` | `3` | 在升级阻止之前,每个 `rateLimitKey` 允许的可疑请求数。 |
| `maxRateLimitCapacity` | `10000` | 内存限制器中存储的最大 IP 条目数。 |
| `maxRateLimitEventsPerKey` | `1000` | 为一个 `rateLimitKey` 保留的最大时间戳数;绝不会低于 `maxSuspiciousRequests`。 |
| `rateLimitKey` | `req.ip` | 函数 `(req) => string`;选择用于可疑请求升级的身份标识。在依赖代理之后的 `req.ip` 之前,请配置 Express 的 `trust proxy`。 |
| `dryRun` | `false` | 记录整个请求过程中的检测,并调用 `next()` 而不是阻止请求。 |
| `logAttacks` | `false` | 设为 `true` 时记录到 `console.warn`;如果传入函数,则该函数接收格式化后的日志消息。 |
| `logFormat` | `text` | 使用 `json` 将结构化事件发送给 `logAttacks`;推荐用于生产环境日志管道。 |
| `onThreat` | `undefined` | 接收检测结果 `(event, req)` 的回调函数。 |
| `onCallbackError` | `undefined` | 当用户 hook 失败时接收 `(error, context)` 的回调函数。Hook 失败将在报告后被吞掉 (swallowed)。 |
| `learning` | `false` | 记录被允许的可疑 payload,但不更改检测器行为。 |
| `onLearningEvent` | `undefined` | 接收学习候选对象以供人工审查的回调函数。 |
| `blockStatus` | `403` | 用于阻止请求的 HTTP 状态码。 |
| `skip` | `undefined` | 函数 `(req) => boolean`;返回 `true` 即跳过扫描。 |
| `schema` | `undefined` | 用于预期查询、body、params、headers 和 cookies 的路由 schema。 |
| `schemas` | `undefined` | 将 `POST /login` 等路由键映射到 schema 的对象。 |
| `scanHeaders` | `true` | 扫描 `req.headers`。 |
| `scanCookies` | `true` | 存在时扫描 `req.cookies`。 |
| `scanParams` | `true` | 扫描 `req.params`。 |
| `scanQuery` | `true` 扫描 `req.query`。 |
| `scanBody` | `true` | 扫描 `req.body`。 |
| `scanRawBody` | `true` | 当上游解析器或捕获中间件提供时,扫描 `req.rawBody`。 |
| `scanKeys` | `true` | 同时扫描对象键及其值。 |
| `maxDepth` | `20` | 在请求被视为 DoS 探测之前,允许的最大对象嵌套深度。 |
| `maxFields` | `1000` | 在请求被视为 DoS 探测之前,每个请求扫描的最大对象字段数。 |
| `maxPayloadLength` | `50000` | 每个字符串解码和扫描的最大字符数。 |
| `maxDecodeIterations` | `8` | 针对嵌套编码的 payload 进行重复 URL/实体标准化处理的最大次数。 |
| `detector` | new `Detector()` | 可选的预配置检测器实例。 |
## 被阻止的响应
```
{
"error": "Forbidden",
"message": "Malicious payload detected by SQLGuardJS",
"details": {
"label": "sqli"
}
}
```
常见标签:
- `sqli`
- `xss`
- `schema_violation`
- `rate_limit_escalation`
- `dos`
## 命令行
```
sqlguardjs scan ""
sqlguardjs scan "1 UNION/**/SELECT password FROM users--"
sqlguardjs scan-file payloads.txt --format csv
```
默认输出格式为 JSON。CSV 输出包含 `payload,label,confidence`。
CSV 输出会转义导致换行的换行符,并为以电子表格公式开头的单元格添加前缀,从而防止攻击者 payload 伪造报告行或在电子表格工具中打开时执行。
## 安全指南
为防止 SQL 注入,请使用参数化查询,在适当的地方使用安全的存储过程,对动态标识符使用白名单验证,并使用最小权限的数据库账户。
为防止 XSS,请在实际可行的情况下使用框架转义、上下文感知的输出编码、针对富文本内容的 HTML 消毒、安全的 DOM sink、CSP 以及 Trusted Types。
参考:
- OWASP SQL 注入防护备忘录:https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
- OWASP 跨站脚本 (XSS) 防护备忘录:https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
## 测试
```
cd npm
npm test
```
Node 测试套件涵盖了检测器、中间件、生成的对抗性语料库、header/raw body 扫描、速率限制、CLI 行为、包元数据以及良性流量的假阳性 检查。
它还包括使用 `supertest` 进行真实的 Express 集成测试,涵盖查询、body、params、schema 检查、结构化日志和学习事件。
当前结果:
```
Test Suites: 10 passed, 10 total
Tests: 156 passed, 156 total
```
贡献者在更改检测器规则之前,应将新的绕过方法或假阳性用例添加为测试。
## 仓库布局
```
npm/ Node package, Express middleware, CLI, and Jest tests
npm/examples/ Minimal and production-style Express examples
python/ Python reference detector and model-training scripts
test_integration/ Local Express integration example
.github/workflows/ CI configuration
```
## 许可证
MIT。详见 [LICENSE](LICENSE)。
标签:AppImage, CISA项目, DOE合作, Express中间件, GNU通用公共许可证, MITM代理, Node.js, NoSQL注入防御, SQL注入防御, Syscall, Web应用防火墙, Web开发, XSS防御, 数据可视化, 暗色界面, 自定义脚本