模块 05第 2 学期 · 安全13 海报条目
应用安全:OWASP Top 10:2025
登录后可在不同设备间同步进度。在此之前,进度只保存在当前浏览器中。
为什么重要
安全是 AI 生成代码最常出问题、代价也最高的地方。根据 Veracode 2026 年的数据,AI 代码的安全通过率只有约 56%。本模块为期两周,是整个课程中最难过的一道关卡。
学习目标
- 把 OWASP Top 10:2025 落实到你自己的应用上。
- 正确实现认证与授权(OAuth/OIDC、会话、JWT 轮换、IAM、RLS)。
- 管好密钥,确保它们永远不会出现在代码、前端 bundle 或日志里。
- 防御注入(SQL、XSS、SSRF)和浏览器端攻击(CSRF、CORS 配置错误)。
- 用 rate limiting(限流)、WAF 和 DDoS 防护守住边界。
- 在 CI 中运行 SAST、DAST、密钥扫描和依赖扫描。
OWASP Top 10:2025
| 编号 | 风险 | AI 代码的典型失误 |
|---|---|---|
| A01 | 访问控制失效(Broken Access Control) | 接口只校验了登录,没校验资源归属;RLS 未开启;顺序 ID 导致 IDOR |
| A02 | 安全配置错误(Security Misconfiguration) | 开着调试模式、默认凭据、宽松的 CORS *、过于详细的错误信息、存储桶公开 |
| A03 | 软件供应链失效(Software Supply Chain Failures) | 幻觉出来的包或未锁定版本的包、被投毒的依赖、未签名的构建 |
| A04 | 加密失效(Cryptographic Failures) | 自己造加密算法、用 MD5/SHA1 存密码、密钥明文存放 |
| A05 | 注入(Injection) | 字符串拼接 SQL、dangerouslySetInnerHTML、把用户输入拼进 shell 命令 |
| A06 | 不安全的设计(Insecure Design) | 没有威胁建模;业务逻辑被滥用(负数数量、优惠券叠加) |
| A07 | 认证失效(Authentication Failures) | 登录没有限流、密码重置流程薄弱、token 有效期过长、没有 MFA |
| A08 | 软件或数据完整性失效(Software or Data Integrity Failures) | webhook 未验签、更新包未签名、不安全的反序列化 |
| A09 | 安全日志与告警失效(Security Logging and Alerting Failures) | 没有审计日志;暴力破解无告警;日志里有密钥 |
| A10 | 异常情况处理不当(Mishandling of Exceptional Conditions) | 出错时 fail-open(默认放行)、异常被吞掉、堆栈信息直接返回给用户 |
2025 版的新变化:供应链单独成为 A03,异常情况处理不当新进入 A10。这两项恰恰是 AI 工具最薄弱的地方。
课程内容
5.1 访问控制(A01, 68)
- 认证(Authentication)是确认你是谁,授权(Authorization)是确认你能做什么。AI 代码通常做了前者,忘了后者。
- 每个请求、在服务端、针对每个对象都要做授权检查(
WHERE id = $1 AND org_id = $currentOrg)。 - 默认拒绝。角色和权限集中在一处管理(策略模块或 RLS),不要散落在各处的
if语句里。 - 基础设施的 IAM: 每个服务有自己的身份,遵循最小权限;不共用管理员密钥;dev、staging、prod 使用不同账号。
- 绝不要把 service-role 或管理员密钥暴露给客户端。在 Supabase 里,anon key 本来就是公开的,所以 RLS 是你唯一的防线。
5.2 认证、OAuth 与 token(69, 70)
- 不要从零自研认证。用成熟的服务商(Supabase Auth、Clerk、Auth0,企业 SSO 可用 WorkOS)。
- OAuth 2.0 / OIDC: 浏览器和移动端使用带 PKCE 的授权码流程(Authorization Code flow)。校验
state、nonce、issuer 和 audience。 - 会话:使用
HttpOnly、Secure、SameSite=Lax或Strict的 cookie。不要把 token 存在 localStorage。 - JWT 轮换: access token 有效期要短(5 到 15 分钟);refresh token 轮换并具备重用检测;签名密钥通过 JWKS 配合 key ID(
kid)轮换;绝不允许alg: none;始终校验签名、过期时间和 audience。 - 企业客户默认期待:MFA、SSO(SAML/OIDC)、SCIM 用户同步、会话吊销。
5.3 密钥管理(67)
- 密钥放在密钥管理服务或平台环境变量里,绝不进 git,也绝不放进
NEXT_PUBLIC_*/VITE_*变量(这些会被打包发到浏览器)。 - pre-commit hook 加 CI 扫描(gitleaks、开启 push protection 的 GitHub secret scanning)。
- 泄露的密钥必须轮换,光删掉没用:git 历史和 fork 会永远保留它。
- 每个环境用不同的密钥;API key 权限尽量收窄(Stripe 受限密钥、限定范围的 token)。
- 每个付费 API key(LLM、短信、地图)都要设消费上限。否则一个泄露的密钥就是一张空白支票。
5.4 密码学与加密(72, A04)
- 密码:通过认证服务商使用 argon2id 或 bcrypt。绝不要自己用 SHA 做哈希。
- 静态加密:开启托管数据库和存储的加密,备份也要加密。高敏感数据(身份证号、银行信息)做字段级加密,密钥放在 KMS 里。
- 使用现成的库(libsodium、平台 KMS),绝不要自创加密算法。
5.5 注入(78, 79, 80)
- SQL 注入: 使用参数化查询或查询构造器/ORM。绝不要把字符串插值进 SQL,包括各种“raw”逃生口。
- XSS: 框架默认会转义;危险区是
dangerouslySetInnerHTML、v-html,以及把 markdown 或 LLM 输出当作 HTML 渲染。用 DOMPurify 做净化,并设置 Content-Security-Policy。 - SSRF: 任何会去抓取用户提供的 URL 的功能(链接预览、webhook、“从 URL 导入”、AI agent 浏览网页)都可能被指向
169.254.169.254(云元数据)或内部服务。对域名做白名单,解析后屏蔽私有 IP 段,禁用重定向或在每次跳转后重新检查,并把抓取程序放在隔离的网络中运行。
5.6 浏览器安全(76, 77)
- CORS 不是服务端的安全边界,它只控制浏览器允许哪些来源读取响应。绝不要在
Access-Control-Allow-Credentials: true的同时原样回显任意Origin。只放行明确列出的来源。 - CSRF: 使用 cookie 认证时,对会改变状态的请求,用
SameSitecookie 加上 CSRF token 或来源校验。 - 安全响应头:CSP、HSTS、
X-Content-Type-Options: nosniff、Referrer-Policy、frame-ancestors。
5.7 边界防护:rate limiting、WAF、DDoS(1, 74, 75)
- 按 IP、用户和 API key 限流;对登录、注册、密码重置、OTP 以及所有花钱的接口(LLM、短信、邮件)设更严格的限制。
- 算法:令牌桶(token bucket)或滑动窗口,状态存在 Redis 或边缘节点。返回
429并带上Retry-After。 - WAF(Cloudflare、AWS WAF)启用托管的 OWASP 规则;注册环节加机器人防护。
- DDoS 防护主要来自 CDN;确保源站不能被直接访问而绕过 CDN。
5.8 异常情况(A10)
- Fail closed(出错即拒绝): 授权检查抛异常,就拒绝。反欺诈检查超时,就挂起交易,而不是放行。
- 绝不向用户展示堆栈信息;带上 request ID 记录到日志里。
- 捕获具体的错误类型;不要吞掉所有异常。
5.9 威胁建模(A06)
每个功能花 30 分钟做 STRIDE:Spoofing(伪造身份)、Tampering(篡改)、Repudiation(抵赖)、Information disclosure(信息泄露)、Denial of service(拒绝服务)、Elevation of privilege(权限提升)。画出信任边界,列出滥用场景(“如果数量是 -1 会怎样?”)。
5.10 流水线中的安全
| 工具类型 | 示例 | 能发现什么 |
|---|---|---|
| 密钥扫描 | gitleaks、GitHub push protection | 代码里的密钥 |
| SAST | Semgrep、CodeQL | 注入、不安全的写法 |
| SCA | Dependabot、Snyk、OSV-Scanner | 有漏洞的依赖 |
| DAST | OWASP ZAP | 运行时配置错误、响应头、XSS |
| 容器/IaC | Trivy、Checkov | 有问题的镜像和基础设施配置 |
实验 5(两周):攻击你自己的应用
第一周,攻破: 准备第二个账号和一个浏览器代理(ZAP 或 Burp Community)。
- 通过修改 ID 尝试读取或修改其他用户的记录(IDOR)。
- 用公开的 anon key 直接调用数据库。
- 在每个文本字段以及 markdown/LLM 输出中注入
<script>。 - 把所有会抓取 URL 的功能指向
http://169.254.169.254/和http://localhost。 - 暴力破解登录和密码重置;猛刷最贵的那个接口。
- 在生产环境的 JS bundle 中搜索密钥。
第二周,修复并证明: 修复每一个发现,为每个发现补一个回归测试,把扫描工具接入 CI。
完成标准(Definition of Done)
AI 提示词包
- “你是一名渗透测试人员。针对这个应用的每条路由,列出已登录用户可能如何访问其他用户的数据。然后为每一种情况编写测试。”
- “找出所有读取密钥或环境变量的地方。标记出任何可能进入客户端 bundle 的。”
- “找出所有抓取由用户输入派生出的 URL 的地方。加上 SSRF 防护,在 DNS 解析之后屏蔽私有、回环和链路本地地址段。”
- “找出所有 catch 块。标记出任何 fail-open 或吞掉错误的。”