关于 vibe-security
vibe-security 是面向 Web 应用开发与审查的安全编码指南。它按访问控制、XSS、CSRF 和 SSRF 等攻击面组织检查项,并提供验证步骤与防护实践。
主要功能
- 01识别相关 Web 攻击面
- 02审查访问控制与授权
- 03检查 XSS 和 CSRF 防护
- 04采用安全重定向与密码实践
- 05验证响应头并测试安全修复
适用场景
- 01审查接收用户输入的 API 路由
- 02实现身份验证与会话控制
- 03评估文件上传和重定向处理
- 04检查安全响应头和 CSP 策略
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ nickcrew/ claude-cortex --skill vibe-security安装后在 Claude Code 中输入/vibe-security确认已加载。
详细介绍
Web 应用安全编码指南
Web 应用的全面安全编码实践。从漏洞猎手的视角审视代码,在尽可能确保应用安全的同时,不影响功能。
适用场景
- 编写新的 Web 应用端点或 API 路由
- 审查涉及用户输入、身份验证或文件上传的 PR
- 实现身份验证、授权或会话管理
- 处理文件上传、重定向或基于 URL 的功能
- 添加安全标头或 CSP 策略
- 避免用于基础设施/网络安全——请改用
defense-in-depth
工作流
步骤 1:识别攻击面
确定待审查代码涉及哪些安全领域:
| 领域 | 触发条件 |
|---|---|
| 访问控制 | 任何需要身份验证的端点、多租户数据 |
| XSS | 在 HTML、JavaScript 或 CSS 中渲染用户输入 |
| CSRF | 会更改状态的端点(POST、PUT、DELETE) |
| SSRF | 服务端向用户提供的 URL 发出请求 |
| SQL 注入 | 动态数据库查询 |
| 文件上传 | 任何文件上传功能 |
| 路径遍历 | 文件路径中的用户输入 |
步骤 2:应用特定领域的检查
使用下方相关章节作为各个已识别领域的检查清单。
步骤 3:验证安全标头
确保所有响应都包含必需的标头(参见安全标头检查清单)。
步骤 4:审查并测试
- 验证修复不会破坏功能
- 使用各章节列出的绕过技术进行测试
- 如有可用的自动化安全扫描工具,请运行扫描
核心原则:
- 深度防御:切勿依赖单一安全控制
- 安全失败:发生故障时应默认拒绝访问
- 最小权限:仅授予所需的最低权限
- 输入验证:切勿信任用户输入,务必在服务端验证所有输入
- 输出编码:根据数据渲染的上下文进行适当编码
访问控制问题
当用户能够访问超出其预期权限的资源或执行相应操作时,就会产生访问控制漏洞。
核心要求
对于每个需要身份验证的数据项和操作:
-
用户级授权
- 每个用户只能访问或修改自己的数据
- 任何用户都不应访问其他用户或组织的数据
- 始终在数据层验证所有权,而不只是路由层
-
使用 UUID 代替连续 ID
- 使用 UUIDv4 或类似的不可猜测标识符
- 例外:只有在用户明确要求时,才使用连续 ID
-
账号生命周期处理
- 当用户被移出组织时:立即撤销所有访问令牌和会话
- 当账号被删除/停用时:使所有活动会话和 API 密钥失效
- 实现令牌撤销列表,或采用带刷新机制的短期令牌
授权检查清单
- 每次请求都验证用户是否拥有该资源(不要信任客户端数据)
- 检查多租户应用中的组织成员资格
- 针对基于角色的操作,验证角色权限
- 权限发生任何变化后,重新验证权限
- 检查父资源所有权(例如,访问评论时,验证用户是否拥有其所属的帖子)
应避免的常见陷阱
- IDOR(不安全的直接对象引用):始终验证请求用户是否有权访问请求中的资源 ID
- 权限提升:在服务端验证角色变更;切勿信任客户端提供的角色信息
- 水平越权:权限级别相同的用户 A 访问用户 B 的资源
- 垂直越权:普通用户访问管理员功能
- 批量赋值:筛选用户可更新的字段;不要盲目接受请求正文中的所有字段
实现模式
# Pseudocode for secure resource access
function getResource(resourceId, currentUser):
resource = database.find(resourceId)
if resource is null:
return 404 # Don't reveal if resource exists
if resource.ownerId != currentUser.id:
if not currentUser.hasOrgAccess(resource.orgId):
return 404 # Return 404, not 403, to prevent enumeration
return resource
客户端漏洞
跨站脚本攻击(XSS)
所有用户可直接或间接控制的输入都必须经过净化,以防止 XSS。
需要防护的输入来源
直接输入:
- 表单字段(电子邮件、姓名、简介、评论等)
- 搜索查询
- 上传时的文件名
- 富文本编辑器 / 所见即所得内容
间接输入:
- URL 参数和查询字符串
- URL 片段(哈希值)
- 应用中使用的 HTTP 标头(如果会显示 Referer、User-Agent)
- 向用户显示的第三方 API 数据
- WebSocket 消息
- 来自 iframe 的 postMessage 数据
- 如果会渲染 LocalStorage/SessionStorage 值
经常被忽略的输入:
- 会反映用户输入的错误消息
- 接受 HTML 的 PDF / 文档生成器
- 包含用户数据的电子邮件模板
- 管理面板中的日志查看器
- 渲染为 HTML 的 JSON 响应
- SVG 文件上传(可能包含 JavaScript)
- Markdown 渲染(如果允许 HTML)
防护策略
-
输出编码(针对具体上下文)
- HTML 上下文:对 HTML 实体进行编码(
<→<) - JavaScript 上下文:进行 JavaScript 转义
- URL 上下文:进行 URL 编码
- CSS 上下文:进行 CSS 转义
- 使用框架内置的转义功能(React 的 JSX、Vue 的 {{ }} 等)
- HTML 上下文:对 HTML 实体进行编码(
-
内容安全策略(CSP)
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.yourdomain.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';- 避免在脚本中使用
'unsafe-inline'和'unsafe-eval' - 必要时,为内联脚本使用 nonce 或哈希
- 报告违规行为:
report-uri /csp-report
- 避免在脚本中使用
-
输入净化
- 使用成熟的库(HTML 可使用 DOMPurify)
- 为富文本设置允许的标签 / 属性白名单
- 移除或编码危险模式
-
其他标头
X-Content-Type-Options: nosniffX-Frame-Options: DENY(或使用 CSP frame-ancestors)
跨站请求伪造(CSRF)
所有会更改状态的端点都必须防范 CSRF 攻击。
需要 CSRF 防护的端点
已验证身份的操作:
- 所有 POST、PUT、PATCH、DELETE 请求
- 任何会更改状态的 GET 请求(请改用正确的 HTTP 方法)
- 文件上传
- 设置更改
- 支付 / 交易端点
身份验证前的操作:
- 登录端点(防止登录 CSRF)
- 注册端点
- 密码重置请求端点
- 密码更改端点
- 电子邮件 / 电话验证端点
- OAuth 回调端点
防护机制
-
CSRF 令牌
- 生成经过加密的随机令牌
- 将令牌与用户会话绑定
- 在每个会更改状态的请求中验证
- 登录后重新生成(避免与会话固定攻击叠加)
-
SameSite Cookie
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnlyStrict:Cookie 从不随跨站请求发送(安全性最高)Lax:Cookie 会随顶级导航发送(安全性与便利性的良好平衡)- 始终与 CSRF 令牌结合使用,实现纵深防御
-
双重提交 Cookie 模式
- 在 Cookie 和请求正文 / 标头中都发送 CSRF 令牌
- 由服务端验证二者是否匹配
边缘情况和常见错误
- 令牌存在性检查:CSRF 验证绝不能取决于令牌是否存在,必须始终要求提供令牌
- 每个表单使用单独的令牌:对于敏感操作,可考虑为每个表单使用唯一令牌
- JSON API:不要假定 JSON 内容类型可以防止 CSRF;应验证 Origin/Referer 标头并使用令牌
- CORS 配置错误:过于宽松的 CORS 配置可能绕过 SameSite Cookie
- 子域名:应限定 CSRF 令牌的作用域,因为子域名接管可能导致 CSRF
- Flash / PDF 上传:旧版浏览器插件可能绕过 SameSite
- 带有副作用的 GET 请求:绝不要通过 GET 执行状态更改
- 令牌泄露:不要在 URL 中包含 CSRF 令牌
- URL 中的令牌与标头中的令牌:优先使用自定义标头(X-CSRF-Token),而不是 URL 参数
验证清单
- 令牌具有密码学安全的随机性(使用安全随机生成器)
- 令牌与用户会话绑定
- 所有更改状态的请求都在服务端验证令牌
- 缺少令牌 = 拒绝请求
- 身份验证状态发生变化时重新生成令牌
- 设置 SameSite Cookie 属性
- 会话 Cookie 设置 Secure 和 HttpOnly 标志
密钥和敏感数据泄露
客户端代码不应访问任何密钥或敏感信息。
切勿在客户端代码中暴露
API 密钥和机密信息:
- 第三方 API 密钥(Stripe、AWS 等)
- 数据库连接字符串
- JWT 签名密钥
- 加密密钥
- OAuth 客户端机密
- 内部服务 URL/凭据
敏感用户数据:
- 完整信用卡号
- 社会安全号码
- 密码(即使经过哈希处理)
- 安全问题/答案
- 完整电话号码(应屏蔽:--1234)
- 显示时不需要的敏感个人身份信息(PII)
基础设施详情:
- 内部 IP 地址
- 数据库架构
- 调试信息
- 生产环境中的堆栈跟踪
- 服务器软件版本
密钥可能隐藏的位置(请检查!)
- JavaScript 包(包括源映射)
- HTML 注释
- 隐藏表单字段
- 数据属性
- LocalStorage/SessionStorage
- SSR 应用中的初始状态/水合数据
- 通过构建工具暴露的环境变量(NEXT_PUBLIC_、REACT_APP_)
最佳实践
- 环境变量:将密钥存储在
.env文件中 - 仅限服务端:仅从后端发起需要使用密钥的 API 调用
开放重定向
任何接受 URL 并据此进行重定向的端点,都必须防范开放重定向攻击。
防护策略
-
允许列表验证
allowed_domains = ['yourdomain.com', 'app.yourdomain.com'] function isValidRedirect(url): parsed = parseUrl(url) return parsed.hostname in allowed_domains -
仅允许相对 URL
- 仅接受路径(例如
/dashboard),不接受完整 URL - 验证路径以
/开头且不包含//
- 仅接受路径(例如
-
间接引用
- 使用映射替代原始 URL:
?redirect=dashboard→ 查找对应的/dashboard
- 使用映射替代原始 URL:
需要拦截的绕过手法
| 手法 | 示例 | 生效原因 |
|---|---|---|
| @ 符号 | https://[email protected] | 浏览器会导航至 evil.com,并将 legit.com 作为用户名 |
| 滥用子域名 | https://legit.com.evil.com | 子域名由 evil.com 所有 |
| 协议技巧 | javascript:alert(1) | 通过重定向实施 XSS |
| 双重 URL 编码 | %252f%252fevil.com | 双重解码后变为 //evil.com |
| 反斜杠 | https://legit.com\@evil.com | 某些解析器会将 \ 规范化为 / |
| 空字节 | https://legit.com%00.evil.com | 某些解析器会在空字节处截断 |
| 制表符/换行符 | https://legit.com%09.evil.com | 空白字符混淆 |
| Unicode 规范化 | https://legіt.com(西里尔字母 і) | IDN 同形异义字攻击 |
| Data URL | data:text/html,<script>... | 直接执行载荷 |
| 协议相对 URL | //evil.com | 使用当前页面的协议 |
| 滥用片段 | https://legit.com#@evil.com | 不同库的解析方式不同 |
IDN 同形异义字攻击防护
- 验证前将 URL 转换为 Punycode
- 对敏感重定向,可考虑完全屏蔽非 ASCII 域名
密码安全
密码要求
- 至少 8 个字符(建议 12 个或更多)
- 不设最大长度(或设置很高的上限,例如 128 个字符)
- 允许所有字符,包括特殊字符
- 不强制要求使用特定类型的字符(让用户自行选择强密码)
存储
- 使用 Argon2id、bcrypt 或 scrypt
- 切勿使用 MD5、SHA1 或纯 SHA256
服务端漏洞
服务端请求伪造(SSRF)
任何由服务器向用户提供或影响的 URL 发起请求的功能,都必须受到保护。
可能存在漏洞的功能
- Webhook(用户提供回调 URL)
- URL 预览
- 根据 URL 生成 PDF
- 从 URL 获取图片/文件
- 从 URL 导入的功能
- RSS/Feed 阅读器
- 使用用户提供端点的 API 集成
- 代理功能
- HTML 转 PDF/图片转换器
防护策略
-
允许列表方案(首选)
- 仅允许向预先批准的域名发送请求
- 为集成维护严格的允许列表
-
网络隔离
- 在隔离网络中运行获取 URL 的服务
- 阻止访问内部网络和云元数据
需要阻止的 IP 和 DNS 绕过技术
| 技术 | 示例 | 描述 |
|---|---|---|
| 十进制 IP | http://2130706433 | 将 127.0.0.1 表示为十进制 |
| 八进制 IP | http://0177.0.0.1 | 八进制表示 |
| 十六进制 IP | http://0x7f.0x0.0x0.0x1 | 十六进制表示 |
| IPv6 本地主机 | http://[::1] | IPv6 环回地址 |
| IPv4 映射的 IPv6 | http://[::ffff:127.0.0.1] | IPv4 映射的 IPv6 |
| 缩写 IPv6 | http://[::] | 全零地址 |
| DNS 重绑定 | 攻击者的 DNS 返回内部 IP | 第一次请求解析为外部 IP,第二次解析为内部 IP |
| 指向内部地址的 CNAME | 攻击者的域名将 CNAME 指向内部地址 | DNS 指向内部主机名 |
| URL 解析器混淆 | http://attacker.com#@internal | 不同的解析行为 |
| 重定向链 | 外部 URL 重定向到内部地址 | 谨慎跟随重定向 |
| IPv6 作用域 ID | http://[fe80::1%25eth0] | 接口作用域 IPv6 |
| 少见的 IP 格式 | http://127.1 | 缩写 IP 表示法 |
防止 DNS 重绑定
- 发起请求前解析 DNS
- 验证解析后的 IP 不是内部地址
- 在请求期间固定解析后的 IP(不要重新解析)
- 或者:延迟后解析两次,确保两次都解析到同一个外部 IP
云元数据防护
阻止访问云元数据端点:
- AWS:
169.254.169.254 - GCP:
metadata.google.internal、169.254.169.254、http://metadata - Azure:
169.254.169.254 - DigitalOcean:
169.254.169.254
实施检查清单
- 验证 URL scheme 仅为 HTTP/HTTPS
- 解析 DNS 并验证 IP 不是私有/内部地址
- 明确阻止云元数据 IP
- 限制或禁用跟随重定向
- 如果跟随重定向,则验证每一跳
- 为请求设置超时
- 限制响应大小
- 尽可能使用网络隔离
不安全的文件上传
文件上传必须验证类型、内容和大小,以防止各种攻击。
验证要求
1. 文件类型验证
- 根据允许列表检查文件扩展名
- 验证魔数/文件签名是否与预期类型相符
- 切勿只依赖单项检查
2. 文件内容验证
- 读取并验证魔数
- 对于图片:尝试使用图像库处理(以检测格式错误的文件)
- 对于文档:扫描宏和嵌入对象
- 检查多态文件(可被视为多种类型的有效文件)
3. 文件大小限制
- 在服务端设置最大文件大小
- 同时配置 Web 服务器/代理的限制
- 考虑按文件类型设置限制(图片的限制应小于视频)
常见绕过方式和攻击
| 攻击 | 描述 | 防护措施 |
|---|---|---|
| 扩展名绕过 | shell.php.jpg | 检查完整扩展名,使用允许列表 |
| 空字节 | shell.php%00.jpg | 清理文件名,检查是否包含空字节 |
| 双扩展名 | shell.jpg.php | 仅允许单个扩展名 |
| MIME 类型伪造 | 将 Content-Type 设置为 image/jpeg | 验证魔数 |
| 魔数注入 | 在恶意文件前添加有效魔数 | 检查整个文件结构,而不只是文件头 |
| 多格式文件 | 文件既是有效的 JPEG,也是有效的 JavaScript | 按预期类型解析文件;如果无效则拒绝 |
| 含 JavaScript 的 SVG | <svg onload="alert(1)"> | 清理 SVG,或完全禁止 |
| 通过文件上传进行 XXE 攻击 | 恶意 DOCX、XLSX(它们基于 XML) | 在解析器中禁用外部实体 |
| ZIP 路径穿越 | 压缩包中的 ../../../etc/passwd | 验证解压后的路径 |
| ImageMagick 漏洞利用 | 特制图像 | 保持 ImageMagick 为最新版本,并使用 policy.xml |
| 文件名注入 | 文件名中的 ; rm -rf / | 清理文件名,使用随机名称 |
| 内容类型混淆 | 浏览器 MIME 嗅探 | 设置 X-Content-Type-Options: nosniff |
魔数字节参考
| 类型 | 魔数字节(十六进制) |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | 47 49 46 38 |
25 50 44 46 | |
| ZIP | 50 4B 03 04 |
| DOCX/XLSX | 50 4B 03 04(基于 ZIP) |
安全处理上传文件
- 重命名文件:使用随机 UUID 名称,丢弃原始文件名
- 存储在 webroot 之外:或使用单独的域名存储上传文件
- 使用正确的响应头提供文件:
Content-Disposition: attachment(强制下载)X-Content-Type-Options: nosniffContent-Type与实际文件类型匹配
- 使用 CDN/单独的域名:将上传内容与主应用隔离
- 设置严格的权限:上传的文件不应具有可执行权限
SQL 注入
当用户输入未经妥善处理就被纳入 SQL 查询时,就会发生 SQL 注入。
防护方法
1. 参数化查询(预处理语句) — 主要防御措施
-- VULNERABLE
query = "SELECT * FROM users WHERE id = " + userId
-- SECURE
query = "SELECT * FROM users WHERE id = ?"
execute(query, [userId])
2. 使用 ORM
- 使用会自动进行参数化处理的 ORM 方法
- 谨慎使用 ORM 中的原始查询方法
- 留意 ORM 特有的注入点
3. 输入验证
- 验证数据类型(整数应为整数)
- 在适用时,将允许的值列入白名单
- 这是纵深防御措施,而非主要防御措施
需要留意的注入点
- WHERE 子句
- ORDER BY 子句(常被忽视——不能使用参数,必须使用白名单)
- LIMIT/OFFSET 值
- 表名和列名(不能参数化——必须使用白名单)
- INSERT 值
- UPDATE SET 值
- 包含动态列表的 IN 子句
- LIKE 模式(还要转义通配符:%、_)
其他防御措施
- 最小权限原则:数据库用户应仅拥有所需的最低权限
- 禁用危险函数:例如 SQL Server 中的
xp_cmdshell - 错误处理:绝不向用户暴露 SQL 错误
XML 外部实体(XXE)
当 XML 解析器处理用户提供的 XML 中的外部实体引用时,就会产生 XXE 漏洞。
易受攻击的场景
直接输入 XML:
- SOAP API
- XML-RPC
- XML 文件上传
- 配置文件解析
- RSS/Atom 订阅源处理
间接处理 XML:
- 在服务器端将 JSON/其他格式转换为 XML
- Office 文档(DOCX、XLSX、PPTX 是包含 XML 的 ZIP 文件)
- SVG 文件(基于 XML)
- SAML 断言
- 含 XFA 表单的 PDF
按语言/解析器进行防护
Java:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setExpandEntityReferences(false);
Python(lxml):
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
# Or use defusedxml library
PHP:
libxml_disable_entity_loader(true);
// Or use XMLReader with proper settings
Node.js:
// Use libraries that disable DTD processing by default
// If using libxmljs, set { noent: false, dtdload: false }
.NET:
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
XXE 防护检查清单
- 如果可能,完全禁用 DTD 处理
- 禁用外部实体解析
- 禁用外部 DTD 加载
- 禁用 XInclude 处理
- 使用最新的已修补 XML 解析器版本
- 如果需要 DTD,在解析前验证/清理 XML
- 尽可能考虑使用 JSON 替代 XML
路径遍历
当用户输入控制文件路径时,可能会发生路径遍历漏洞,从而访问预期目录之外的文件。
易受攻击的模式
# VULNERABLE
file_path = "/uploads/" + user_input
file_path = base_dir + request.params['file']
template = "templates/" + user_provided_template
防护策略
1. 避免在路径中使用用户输入
# Instead of using user input directly
# Use indirect references
files = {'report': '/reports/q1.pdf', 'invoice': '/invoices/2024.pdf'}
file_path = files.get(user_input) # Returns None if invalid
2. 规范化和验证
import os
def safe_join(base_directory, user_path):
# Ensure base is absolute and normalized
base = os.path.abspath(os.path.realpath(base_directory))
# Join and then resolve the result
target = os.path.abspath(os.path.realpath(os.path.join(base, user_path)))
# Ensure the commonpath is the base directory
if os.path.commonpath([base, target]) != base:
raise ValueError("Error!")
return target
3. 输入净化
- 移除或拒绝
..序列 - 移除或拒绝绝对路径标识符(
/、C:) - 将允许的字符列入白名单(字母数字字符、连字符、下划线)
- 如适用,验证文件扩展名
路径遍历检查清单
- 切勿在文件路径中直接使用用户输入
- 规范化路径,并验证其是否位于基准目录内
- 如适用,限制文件扩展名
- 使用各种编码和绕过技术进行测试
安全标头检查清单
在所有响应中包含以下标头:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: [see XSS section]
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-store (for sensitive pages)
JWT 安全性
JWT 配置错误可能导致完全绕过身份验证和伪造令牌。
漏洞
| 漏洞 | 防护措施 |
|---|---|
alg: none 攻击 | 始终在服务端验证算法,并拒绝 none |
| 算法混淆 | 明确指定预期算法,绝不从令牌推导算法 |
| 弱 HMAC 密钥 | 使用 256 位以上的密码学安全随机密钥 |
| 缺少过期时间 | 始终设置 exp 声明 |
| 令牌存储在 localStorage 中 | 将令牌存储在 httpOnly、Secure、SameSite=Strict Cookie 中,绝不存储在 localStorage 中 |
安全实现
// 1. SIGNING
// Always use environment variables for secrets
const secret = process.env.JWT_SECRET;
const token = jwt.sign({
sub: userId,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (15 * 60), // 15 mins (Short-lived)
jti: crypto.randomUUID() // Unique ID for revocation/blacklisting
}, secret, {
algorithm: 'HS256'
});
// 2. SENDING (Cookie Best Practices)
// Protect against XSS and CSRF
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
// 3. VERIFYING
// CRITICAL: Whitelist the allowed algorithm
jwt.verify(token, secret, { algorithms: ['HS256'] }, (err, decoded) => {
if (err) {
// Handle invalid token
}
// Trust the payload
});
JWT 检查清单
- 验证时明确指定算法(绝不信任令牌标头)
- 拒绝
alg: none - 密钥由 256 位以上的随机数据组成(不是密码或短语)
- 始终设置并验证
exp声明 - 将令牌存储在 httpOnly Cookie 中(而非 localStorage/sessionStorage)
- 实现刷新令牌轮换(旧刷新令牌在使用后失效)
API 安全性
批量赋值
接受未经筛选的请求正文可能导致权限提升。
// VULNERABLE — user can set { role: "admin" } in request body
User.update(req.body)
// SECURE — whitelist allowed fields
const allowed = ['name', 'email', 'avatar']
const updates = pick(req.body, allowed)
User.update(updates)
这适用于任何 ORM/框架——始终明确指定请求可以修改哪些字段。
GraphQL
| 漏洞 | 防护措施 |
|---|---|
| 在生产环境中启用内省 | 在生产环境中禁用内省。 |
| 查询深度攻击 | 实施查询深度限制(例如,最多 10 层)。 |
| 查询复杂度攻击 | 计算并强制执行严格的查询开销限制。 |
| 批处理攻击 | 限制单个请求中允许的操作数量。 |
const server = new ApolloServer({
introspection: process.env.NODE_ENV !== 'production',
validationRules: [
depthLimit(10),
costAnalysis({ maximumCost: 1000 })
]
})
通用安全原则
生成代码时,始终:
- 在服务端验证所有输入 — 切勿只信任客户端验证
- 使用参数化查询 — 切勿将用户输入拼接到查询中
- 根据上下文对输出进行编码 — HTML、JS、URL、CSS 上下文需要使用不同的编码方式
- 执行身份验证检查 — 在每个端点上执行,而不仅仅是在路由层
- 执行授权检查 — 验证用户是否有权访问特定资源
- 使用安全的默认设置
- 安全地处理错误 — 不要向用户泄露堆栈跟踪或内部详细信息
- 保持依赖项更新 — 使用工具跟踪存在漏洞的依赖项
不确定时,选择限制更严格、更安全的选项,并在注释中记录相关安全考量。