配置文件绝不能存SQL模板或可执行逻辑,只应存储纯数据;SQL必须硬编码或由ORM生成,参数单独传递并校验,环境变量仅限连接信息,动态内容须白名单过滤。

配置文件里拼SQL?这一步就踩进坑了
直接从配置文件读取 SQL 片段并拼接用户输入,等于主动绕过所有防护机制。Node.js 本身不校验配置内容,require('./config.js') 或 JSON.parse(fs.readFileSync('config.json')) 加载的字符串,如果含占位符(如 ${userInput})或模板语法,运行时一拼接就是裸 SQL 注入温床。
- 常见错误:配置中存类似
"query": "SELECT * FROM users WHERE role = '${role}'",再用eval、new Function或模板引擎渲染 - 真实风险:配置可能被部署脚本篡改、CI/CD 环境变量注入、甚至前端传参污染后端配置加载逻辑
- 根本原则:配置文件只该存数据(如 host/port/database),绝不存可执行逻辑或 SQL 模板
mysql2/pg 的参数化查询不认配置里的 ? 占位符
很多人误以为把 SELECT * FROM t WHERE id = ? 放进 config.json 就安全了——错。只要没走 db.execute() 或 db.query() 的参数传递链,占位符只是普通字符串,不会触发预编译。
- 危险写法:
const sql = config.queries.getUser; db.query(sql, [id])—— 如果config.queries.getUser是字符串拼接出来的,db.query根本不识别里面是否含? - 正确路径:SQL 字符串必须硬编码在代码里(或由 ORM 模型生成),参数单独传;配置只提供表名、字段名等元数据(且需白名单校验)
- 例外情况:PostgreSQL 的
pg-promise允许配置化命名查询,但必须用db.any('getUser', {id: 123})调用,底层仍走参数绑定
环境变量 + 配置校验比 JSON 更可靠
用 .env 文件管理数据库连接参数是安全的,但若把业务查询逻辑也塞进去(比如 QUERY_USER_BY_EMAIL=SELECT * FROM users WHERE email = ?),就又回到“配置即代码”的陷阱。
- 推荐做法:环境变量只放连接信息(
DB_HOST,DB_NAME),SQL 逻辑写死在 service 层,用switch或策略模式按环境选择不同语句(开发/测试/生产) - 必须校验配置项:对从配置读出的任何字符串,做白名单过滤(如只允许
['users', 'orders', 'products']),禁止动态表名拼接 - 警惕 YAML/JS 配置:Node.js 加载
.js配置会执行代码,module.exports = { sql: `SELECT * FROM ${process.env.TABLE}` }直接引入 RCE 风险
ORM 的 model 定义不能放配置里
Sequelize 或 TypeORM 的模型定义(sequelize.define('User', {...}))如果从 JSON 动态生成,字段验证、关联关系、默认值等都可能失效,导致 where 条件被绕过。
- 典型漏洞:
const model = sequelize.define(config.modelName, config.schema)—— 若config.schema含hooks或validate函数,执行权交给不可信配置 - 安全边界:模型定义必须是静态代码;配置仅用于开关功能(如
enableAuditLog: true),不参与查询构造 - 替代方案:用配置驱动查询参数(如分页大小、排序字段),但字段名必须经
Object.keys(allowedFields).includes(sortBy)校验
配置不是黑盒,它和代码一样要受信任边界约束。最易被忽略的是:配置加载时机早于请求上下文,无法做运行时输入校验,所以一切从配置流出的数据,都得当敌意输入处理。


















