Chiranth-Janardhan-moger/sqlguardjs

GitHub: Chiranth-Janardhan-moger/sqlguardjs

SQLGuardJS 是一个 Express 请求验证中间件,通过启发式检测和加权评分拦截 SQL 注入、NoSQL 注入与 XSS 攻击载荷。

Stars: 17 | Forks: 0

# SQLGuardJS [![npm version](https://img.shields.io/npm/v/sqlguardjs.svg)](https://www.npmjs.com/package/sqlguardjs) [![Tests](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/Chiranth-Janardhan-moger/sqlguardjs/actions/workflows/ci.yml) [![Node.js >=18](https://img.shields.io/badge/node-%3E%3D18.0.0-339933.svg)](https://nodejs.org/) [![License: MIT](https://img.shields.io/npm/l/sqlguardjs.svg)](https://github.com/Chiranth-Janardhan-moger/sqlguardjs/blob/main/LICENSE) [![npm downloads](https://img.shields.io/npm/dm/sqlguardjs.svg)](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防御, 数据可视化, 暗色界面, 自定义脚本