Node.js 服务常见安全风险有哪些?
简化版
Node 服务常见安全风险包括输入未校验、SQL/NoSQL 注入、XSS、CSRF、依赖漏洞、敏感信息泄露、文件上传风险、路径穿越、弱鉴权和限流缺失。核心原则是不要信任任何外部输入。
详细版
常见防护:
- 参数校验:使用 schema 校验请求体和查询参数。
- 注入防护:SQL 参数化查询,Mongo 查询过滤危险操作符。
- XSS:输出转义、CSP、避免直接拼 HTML。
- CSRF:SameSite Cookie、CSRF Token。
- 文件上传:限制类型、大小、扩展名,隔离存储。
- 依赖安全:定期审计 npm 包。
- 敏感配置:环境变量和密钥管理,不提交仓库。
安全不是某个库能一次性解决,而是贯穿输入、处理、输出、部署。
完整版教学
一、输入校验是第一道门
接口参数来自用户、浏览器、第三方系统,都不能默认可信。类型、长度、格式、枚举值、嵌套结构都要校验。
没有校验的数据进入数据库查询、文件路径、命令行、模板渲染,就可能引发注入或越权。
二、注入风险不只 SQL
Node 项目也常见 NoSQL 注入:
{ "username": { "$ne": null } }
如果把用户输入直接塞进 Mongo 查询,可能绕过条件。命令执行、模板渲染、路径拼接也都有注入风险。
三、依赖供应链风险
Node 项目依赖多,npm 包可能有漏洞、恶意代码或维护风险。要使用 lockfile、审计依赖、限制安装脚本风险,关键系统还要做依赖准入。
不要随便把小功能交给冷门包,依赖越多,攻击面越大。
四、面试追问与工程落地
常见追问是“文件上传如何防护”。不能只看扩展名,要限制大小、校验 MIME/文件头、重命名文件、存到非执行目录,图片可做二次处理。下载时还要防路径穿越。
工程中安全要配合日志和告警。登录失败、权限拒绝、上传异常、接口频率异常都应该可观测,否则攻击发生时很难发现。
五、信任边界与注入防护
Schema 校验负责确认类型、长度和允许字段,但它不能替代“在危险解释器边界使用安全 API”。进入 SQL 时使用参数化查询,进入 Mongo 查询时限制操作符,调用子进程时优先使用不经过 shell 的参数数组,拼接文件路径时校验解析后的目标仍在允许根目录内。
例如上传根目录是 /srv/uploads,用户给出 ../../etc/passwd 时,简单字符串拼接会越界。应先取受控文件名或执行 path.resolve(root, input),再验证结果是否位于规范化后的 root 之下;只删除 ../ 字符串无法覆盖绝对路径、编码和平台分隔符等变体。
| 攻击面 | 主要控制 | 仍需注意 |
|---|---|---|
| SQL / NoSQL 注入 | 参数化、操作符白名单 | 动态表名和排序字段也要白名单 |
| 命令注入 | 避免 shell、固定可执行文件与参数 | 参数本身仍需业务校验 |
| 路径穿越 | 规范化并限制根目录 | 符号链接和上传后的存储位置 |
| XSS / 模板注入 | 按输出上下文编码、CSP | HTML、属性、URL 的编码规则不同 |
鉴权也位于信任边界:客户端传来的用户 id、角色和价格都只能作为输入,资源归属和权限必须由服务端根据可信身份重新查询。通过了身份认证不等于拥有任意对象的访问权限。
六、资源滥用、供应链与密钥
攻击者不一定绕过权限,也可能用合法请求耗尽资源。请求体大小、上传大小、头部和读写超时、并发数、速率以及正则复杂度都要设上限;访问外部 URL 的功能还要防 SSRF,不能只按域名字符串判断,应校验协议、解析后的地址和重定向目标。
限流数字要由容量与业务决定。例如某登录接口可先设置“单账号每分钟 100 次、允许 20 次短时突发”作为示例策略,再结合 IP、设备和失败次数调整;它不是通用安全常数,也不能替代验证码、锁定和异常检测。
供应链侧应提交 lockfile、审查新增依赖与安装脚本、持续扫描已知漏洞,并以最小操作系统权限运行服务。密钥不能进入仓库、镜像或普通日志,应由密钥系统注入、限制读取范围并支持轮换;泄露后仅删除 Git 中的字符串并不能让旧密钥失效,必须立即撤销或轮换。
安全控制需要落在输入、解释器、权限、资源和部署多个边界上;某个安全中间件无法覆盖整条链路。
上线前的最小安全清单
一条新接口至少应核对:
- 请求体、查询参数、路径参数和请求头是否有 schema 与大小上限。
- 认证、功能权限、资源归属和租户隔离是否分别验证。
- 数据是否进入 SQL、NoSQL、shell、模板、路径或 URL 等解释器边界。
- 响应是否按上下文编码,Cookie 是否配置合适的
HttpOnly、Secure、SameSite。 - 外部调用是否有连接/响应超时、重试上限和并发隔离。
- 日志是否足够审计,同时避免记录令牌、密码和隐私字段。
- 依赖、运行账户、网络访问和密钥权限是否遵循最小权限。
这份清单不能替代威胁建模,但能把常见遗漏变成可审核项。涉及资金、账号、文件解析或外部 URL 的高风险功能,还应单独做滥用场景和失败路径设计。
七、常见误区与追问
- 误区:做过 schema 参数校验就不会发生注入。 校验输入结构只是第一层,进入 SQL、shell、模板和路径解释器时仍要使用对应的安全 API。
- 误区:JWT 验签通过就可以访问请求中的任意资源。 验签只确认令牌可信,服务端还必须检查权限、租户和资源归属。
- 误区:文件扩展名和客户端 MIME 正确就说明上传安全。 两者都可伪造,还要限制大小、检查文件头、重命名、隔离存储,必要时重新编码。
- 追问:为什么参数化查询不能保护动态表名? 参数占位符通常只绑定值,表名、列名和排序方向需要从服务端白名单选择。
- 追问:如何防止路径穿越? 使用固定根目录解析规范化绝对路径,验证结果仍在根目录内,并处理符号链接与平台路径差异。
- 追问:限流应放在哪里? 网关做全局粗粒度保护,应用按账号、租户或业务动作精细限制,两层都要考虑代理后的真实来源。
- 追问:发现依赖漏洞就必须立刻删除包吗? 先确认版本、调用路径和可利用性,再升级、替换或临时缓解;高风险可利用漏洞应优先处置并持续验证。
八、加强记忆
Node 安全记住“输入不可信、解释器要隔离、权限由服务端判断、资源必须有上限、依赖与密钥可治理”。每次外部数据进入 SQL、shell、模板、文件系统或下游网络前,都重新识别信任边界。安全是贯穿开发和部署的链路,不是安装一个中间件就结束。