mcpField
R
reviewing-security

reviewing-security

github.com/osmontero安全16149 次安装今天更新
代码仓库

关于 reviewing-security

面向开发者的安全审查技能,强调追踪攻击者可控输入到实际影响的代码路径,而非只列漏洞类别。涵盖攻击面、授权、数据流、依赖项及基于证据的报告。

主要功能

  1. 01追踪攻击输入至实际影响
  2. 02梳理入口点与运行权限
  3. 03检查授权与数据暴露
  4. 04验证扫描结果是否可达
  5. 05按可利用性评级并报告

适用场景

  1. 01审查代码库中的可利用漏洞
  2. 02检查对象与租户访问授权
  3. 03报告前验证扫描器告警

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/osmontero/opencode-skills --skill reviewing-security

安装后在 Claude Code 中输入/reviewing-security确认已加载。

附带文件references/vulnerability-patterns.md

详细介绍

审查安全性

概述

大多数安全审查结果都毫无价值,因为它们只列出类别,而不是路径。“验证所有用户输入”适用于以往编写的每个程序,毫无作用。有用的发现是一条路径:攻击者控制的输入、传递该输入的代码、它到达的操作,以及攻击者能获得什么。

**核心原则:**追踪数据,而不是照着清单逐项检查。漏洞存在于值进入系统的位置与它发挥作用的位置之间。

**配套技能:**通用审查流程请参阅 requesting-code-review;确认攻击路径时请参阅 systematic-debugging。

铁律

NO FINDING WITHOUT A PATH FROM ATTACKER-CONTROLLED INPUT TO IMPACT

如果你说不清输入是什么、它经过什么路径,以及它会给操作人员造成什么代价,那这就是代码异味——应将其归入“观察项”,而不是“发现项”。用类别性建议充斥报告,会掩盖那条确实存在的路径。

**推论:**报告问题前,先在实际代码中追踪路径。grep 命中只是线索,不是发现项。

检查清单

  1. 梳理攻击面——入口点、哪些人能访问、它们以什么权限运行
  2. 追踪输入到接收点——各种形式的注入
  3. 授权——收益最高的检查环节
  4. 密钥与加密
  5. 进程与插件边界
  6. 依赖项
  7. 数据暴露——日志、错误、响应
  8. 运行扫描器——然后确认可达性,再报告
  9. 报告——按可利用性评定严重程度,每项都要附上路径

第 1 步:梳理攻击面

在阅读实现代码前先完成这一步。你要列出陌生人能够触及的位置。

入口点谁能访问运行时使用的权限
HTTP 路由、gRPC 方法匿名用户 / 已认证用户 / 管理员 / 仅限内部进程、数据库角色、文件系统
队列或事件使用者能发布消息的任何人通常拥有完整进程权限
文件上传、导入任何已认证用户解析器在进程内运行
Webhook 接收器互联网用户,直到签名得到验证
CLI 标志、环境变量、配置本地操作人员
LLM / 工具输出任何能影响模型上下文的人工具能执行的任何操作
# Enumerate handlers and consumers — adapt the pattern to the framework
grep -rnE '\.(Get|Post|Put|Patch|Delete|Handle|HandleFunc)\(' --include='*.go' --include='*.ts'
grep -rnE 'func \(.*\) [A-Z][A-Za-z]*\(ctx context.Context' --include='*.go'   # gRPC impls

最后一行并不新奇。**模型输出是不可信输入。**如果文档、网页或先前的工具结果能引导模型调用某个工具,那么该工具能触及的每个接收点都可被攻击者触及。审查工具定义时,应像审查 HTTP 处理程序一样保持警惕。

步骤 2:从输入追踪到接收点

对于每个入口点,追踪值的流向。接收点是指任何行为会因该值具有恶意而发生变化的操作。

接收点危险之处安全形式
SQL / 查询构建器字符串拼接或插值参数化查询;标识符无法参数化,需使用允许列表
Shell / 进程启动Shell 解释器,或 argv[0] 中包含用户数据使用 argv 数组执行,不调用 Shell,二进制文件路径固定
文件系统路径..、绝对路径、符号链接、NUL拼接路径,然后调用 filepath.EvalSymlinks,再确认结果仍位于根目录下
出站 HTTP用户提供的 URL → SSRF对主机使用允许列表;阻止私有、链路本地和元数据地址段;重定向后重新检查
模板渲染关闭自动转义,或使用 dangerouslySetInnerHTML默认转义;使用真正的清理器清理 HTML
反序列化多态或携带类型的格式使用固定架构,解码为具体类型,并限制大小和深度
归档解压zip-slip、符号链接、解压炸弹验证每个条目的路径,限制总字节数和条目数量
重定向目标开放重定向 → 网络钓鱼、令牌窃取对路径使用允许列表,或仅允许同源
对用户输入执行正则表达式灾难性回溯限制输入长度,避免嵌套量词
eval / 反射 / 动态导入任意代码没有安全形式——移除它

跨边界追踪值。 常见的疏漏是:在边界处验证值、将其存储起来,之后又读取并信任它。存储不等于清理。如果之后会渲染或执行该值,它仍然是不可信的。

每行对应的具体易受攻击 / 修复示例:参见 references/vulnerability-patterns.md。

步骤 3:授权

真正的漏洞就在这里。 现代库已能很好地防御注入;但对象级授权缺陷并非如此,因为框架无法修复这类问题——这需要知道哪些行属于谁。

  • 每个接受 ID 的处理程序都会验证调用者是否有权访问 该 对象,而不只是验证其是否已登录
  • 检查必须位于数据实际经过的路径上;路由层的检查若可被服务层绕过,就不算检查
  • 租户 / 组织范围限制应写在查询中(WHERE tenant_id = ?),而不是在获取结果后再应用
  • 不信任请求正文中的角色、租户、价格或用户 ID——批量赋值只是多绕几步的授权绕过
  • 默认拒绝:没有显式策略的新路由应无法访问,而非公开可用
  • 会改变状态的操作不能通过 GET 访问
  • 顺序 ID 不能成为用户访问他人记录的唯一屏障
  • 管理员界面的访问控制不能只依赖 URL 未公开
  • 多步骤流程的每一步都要重新检查授权,而不只是在开始时检查
# Handlers that take an identifier — each one needs an ownership check
grep -rnE 'Param\("id"\)|params\.id|req\.body\.(userId|tenantId|role)' --include='*.go' --include='*.ts'

步骤 4:密钥与加密

  • 源代码、提交到 git 的配置、日志、错误消息、URL 或客户端可见的资源包中均不包含密钥
  • git 历史记录中也不包含密钥——轮换任何曾提交过的密钥;之后在提交中删除也于事无补
  • 使用 argon2id/scrypt/bcrypt 对密码进行哈希——绝不能直接使用 SHA,也绝不能不加盐
  • 使用恒定时间函数比较令牌和签名
  • 令牌、ID 和盐的随机数必须来自 CSPRNG(crypto/rand、crypto.randomUUID)——绝不能使用 math/rand
  • JWT:验证签名,固定算法(拒绝 none 和算法混淆),检查 exp/aud/iss
  • 绝不禁用 TLS 验证——不使用 InsecureSkipVerify: true,也不设置 NODE_TLS_REJECT_UNAUTHORIZED=0
  • 权限变更时轮换会话令牌;注销时在服务器端使其失效
  • Cookie:有意设置 HttpOnly、Secure、SameSite
  • 不手写加密原语或自定义“加密”方式
gitleaks detect --no-banner          # or: trufflehog git file://.
git log -p -S'BEGIN PRIVATE KEY' --oneline | head

第 5 步:进程与插件边界

对于会生成子进程、加载插件或代表模型执行工具的任何操作:

  • 子进程只继承所需的环境变量,而不是包含所有凭据的整个父进程环境
  • 工作目录、打开的文件描述符和网络访问均经过明确设置,而非默认继承
  • 子进程输出会被解析为数据,绝不会重新作为受信任的指令输入
  • 存在资源限制:超时时间、内存和输出大小。没有限制的子进程是拒绝服务的手段
  • 失败时采取默认拒绝策略——插件崩溃或无响应时,应拒绝操作,而不是跳过检查
  • 工具本身就是安全边界:接受路径、URL 或命令的工具就是相应的危险操作点,会暴露给任何能影响模型的人

第 6 步:依赖项

  • 已提交锁文件,并在构建中严格遵循(npm ci、go mod verify)
  • 漏洞公告扫描无问题;或者每项例外都有书面说明,并论证其可达性
  • 对新的直接依赖项,检查其发布时间、维护情况、下载量,以及名称是否可能是仿冒包
  • 审查任何新依赖项的安装脚本/postinstall 脚本
  • CI 操作固定到 SHA,而不是会变动的标签
govulncheck ./...          # Go — reports only vulnerabilities you actually reach
npm audit --omit=dev       # Node
pip-audit                  # Python

govulncheck 比通用扫描器更有价值,正是因为它会进行可达性分析。优先使用能告知你易受攻击的函数是否会被调用的工具。

第 7 步:数据暴露

  • 返回给客户端的错误不包含堆栈跟踪、SQL、文件路径或内部主机名
  • 日志不包含密码、令牌、会话 ID、完整卡号,也不包含超出必要范围的个人身份信息(PII)
  • 生产环境中已禁用或限制访问调试端点(/debug/pprof、GraphQL 内省、管理控制台)
  • CORS:不得同时使用 Access-Control-Allow-Origin: * 和凭据;明确列出允许的来源
  • 经过身份验证的响应不可由共享缓存缓存
  • 对象存储桶及其列表默认设为私有
  • 时间差异和错误消息差异不会泄露某个账户是否存在

第 8 步:运行扫描器,然后验证

semgrep --config=auto .

扫描器的输出是一份线索清单,而非报告。报告任何扫描器发现之前,请先确认它在此代码库中可达——检查危险操作点是否确实使用了受污染的值。直接转发工具的原始输出,只会让审查变成被忽视的噪音,真正的问题也会因此被埋没。

严重程度

根据可利用性评级,而不是根据类别名称。

严重程度定义
严重未经身份验证的攻击者可执行代码、获取凭据或批量访问其他用户的数据。无需满足特殊前提条件。
高经过身份验证的攻击者可提升权限、读取或写入其他租户的数据,或窃取机密。
中需要特殊前提条件(特定竞态、漏洞链或用户操作),或只能获取有限数据。
低加固建议。存在真实弱点,但目前尚未证明可利用路径——缺少纵深防御层,可能无法遏制未来出现的漏洞。

不要夸大严重程度。如果一份报告中的所有问题都是严重级别,就不会有人采取行动。

报告格式

## Scope
[What was reviewed — diff range, paths, or "full repo". What was NOT reviewed.]
[Scanners run and their versions.]

## Critical
**Tenant isolation missing on document fetch**
`internal/api/documents.go:88` — the handler loads by primary key only:
`db.First(&doc, c.Param("id"))`. There is no `tenant_id` predicate and no
post-fetch ownership check.
**Path:** any authenticated user → `GET /api/documents/{id}` → sequential
integer IDs → full document body of any tenant.
**Impact:** cross-tenant read of all documents; IDs are enumerable.
**Fix:** scope the query — `db.Where("tenant_id = ?", claims.TenantID).First(&doc, id)`
— and add the predicate at the repository layer so new callers inherit it.

## High
[same shape]

## Medium / Low
[same shape]

## Observations (not findings)
[Weaknesses with no demonstrated path. Say so plainly rather than promoting them.]

## Verified Clean
[What you checked and found sound. Silence reads as "not checked".]

## Assessment
**Ship?** Yes / No / After the Critical and High items
**Reasoning:** [1-2 sentences]

不应报告的内容

不要报告原因
没有危险操作点,却建议“添加输入验证”这是笼统建议,不是发现
没有指出可被滥用的端点,却指出缺少速率限制请指出端点和代价,否则就删除
不提供浏览器客户端的服务缺少浏览器安全标头该威胁不适用
测试、测试夹具或示例代码中的发现攻击者无法触及
未确认可达性的扫描器输出这种噪音会掩盖真正的问题
没有威胁背景的密码学偏好吹毛求疵“使用 AES-256,而不是 AES-128”不算发现
任何你会称为“理论上”的内容如果确实可利用,你就应该能指出具体路径

常见错误

审查清单,而不是审查路径。 按顺序逐项检查 OWASP Top 10,会发现框架已经处理的漏洞,却会漏掉该领域特有的授权漏洞。

只检查入口点。 即使入口处做了验证,如果值被存储起来,之后又被拼接到查询中,这些验证也毫无意义。

盲目信任框架。 ORM 有原始查询的绕过接口,模板有原始输出的绕过接口,而且两者都会被使用。请专门搜索这些接口。

把已通过身份验证视为已获授权。 这是现实中最常见的发现,也最容易因为只看路由表、不看查询而被漏掉。

认为模型可信。 如果工具调用会受到模型读取内容的影响,其参数就由攻击者控制。

夸大严重性以显得全面。 这会让读者忽视真正重要的发现。

评论

支持 Markdown