第6章 6.4 Web 安全:XSS/CSRF/注入
📖 建议配合往期章节阅读:
- 第6章 6.3 鉴权:JWT与Session
- 第7章 7.1 Buffer二进制数据(下章预告)
🎯 开场 3 分钟:为什么你的网站在"裸奔"?
上一章我们学会了用 JWT 和 Session 给用户发"身份证",让服务器知道"你是谁"。
但问题来了——
你发了身份证,别人就能冒用吗?
这就引出了今天的 3 个大坑:
- 🔥 XSS:有人在你的评论区贴了一段 JavaScript 代码,然后所有访客的 cookie 被偷走了
- 🔥 CSRF:用户登录后点了一个链接,莫名其妙就帮别人转了账
- 🔥 SQL 注入:输入用户名的地方填了一段 SQL,直接把你数据库删了
听起来吓人?但别慌,这一章我们就来当黑客 + 当保安,用 Python 模拟攻击,再写出防御代码。
学完你就能:
1. 理解 3 种攻击的原理(知道敌人怎么出招)
2. 写出防御代码(知道怎么挡招)
3. 读懂常见安全头(helmet、CSP 是什么)
🧱 基础 25 分钟:核心概念
什么是 XSS?为什么可怕?
类比:你开了一家餐厅,允许顾客在墙上贴便利贴留言。有一天,有人贴了一张便利贴,上面写的是 <script>偷走收银台的钱</script>。如果其他顾客进来看到这张"便利贴",浏览器会执行这段代码——相当于直接把钱包递给了小偷。
XSS(Cross-Site Scripting) 就是攻击者在网页上注入恶意脚本,当其他用户访问这个页面时,脚本就在他们的浏览器里执行。
代码演示 1:模拟 XSS 攻击
# 这是一个简陋的留言板系统
comments = [
"今天天气真好!",
"<script>alert('你的cookie被偷了!')</script>",
"这家店的服务很棒"
]
# 错误的展示方式:直接渲染 HTML
html_output = "<div class='comments'>"
for comment in comments:
html_output += f"<p>{comment}</p>" # 直接拼接,会执行 script!
html_output += "</div>"
print("危险输出:")
print(html_output)
输出:
<div class='comments'>
<p>今天天气真好!</p>
<p><script>alert('你的cookie被偷了!')</script></p>
<p>这家店的服务很棒</p>
</div>
当这段 HTML 被浏览器加载时,<script> 标签里的代码就会执行——这就是 XSS。
代码演示 2:防御 XSS
import html
comments = [
"今天天气真好!",
"<script>alert('你的cookie被偷了!')</script>",
"这家店的服务很棒"
]
# 正确的展示方式:转义 HTML 特殊字符
safe_output = "<div class='comments'>"
for comment in comments:
safe_output += f"<p>{html.escape(comment)}</p>" # 转义!
safe_output += "</div>"
print("安全输出:")
print(safe_output)
输出:
<div class='comments'>
<p>今天天气真好!</p>
<p><script>alert('你的cookie被偷了!')</script></p>
<p>这家店的服务很棒</p>
</div>
💡 这行在干嘛:
html.escape()把<变成<,把>变成>,浏览器就只会显示文字,不会执行脚本了。

什么是 CSRF?为什么可怕?
类比:你去银行办业务,银行让你签了一张"转账授权书",但没有验证你是不是本人。骗子打电话给你说"我是银行客服,帮我签个字",你就签了——结果钱被转走了。
CSRF(Cross-Site Request Forgery) 就是攻击者诱导已经登录的用户访问一个恶意页面,这个页面会自动用用户的身份发起请求(比如转账、改密码)。
代码演示 3:模拟 CSRF 攻击场景
# 模拟用户的登录 session
user_session = {
"user_id": 12345,
"username": "xiaoming",
"is_logged_in": True
}
# 正常的转账请求(用户主动发起)
def normal_transfer(session, to_account, amount):
if session["is_logged_in"]:
print(f"✅ 用户 {session['username']} 转账 {amount} 元到账户 {to_account}")
return True
return False
# CSRF 攻击:恶意页面自动发送请求
def csrf_attack():
"""
攻击者诱骗已登录用户访问恶意页面,
页面用用户的 session 自动发起转账请求
"""
print("🚨 恶意页面被访问...")
print("正在用用户的 session 自动发起转账请求...")
# 这里演示攻击原理,实际是跨站请求
if user_session["is_logged_in"]:
# 攻击者把用户的钱转走!
print(f"❌ 用户 {user_session['username']} 的钱被转走了!")
return True
return False
print("=== 正常转账 ===")
normal_transfer(user_session, "Account_B", 1000)
print("\n=== CSRF 攻击 ===")
csrf_attack()
输出:
=== 正常转账 ===
✅ 用户 xiaoming 转账 1000 元到账户 Account_B
=== CSRF 攻击 ===
🚨 恶意页面被访问...
正在用用户的 session 自动发起转账请求...
❌ 用户 xiaoming 的钱被转走了!
代码演示 4:防御 CSRF
import secrets
import hashlib
# 生成 CSRF Token
def generate_csrf_token(session_id):
"""为每个用户会话生成唯一的 CSRF token"""
random_bytes = secrets.token_hex(32)
token = hashlib.sha256(f"{session_id}:{random_bytes}".encode()).hexdigest()
return token
# 验证 CSRF Token
def verify_csrf_token(session_id, token, stored_token):
"""验证请求中的 token 是否与服务器存储的一致"""
expected_token = generate_csrf_token(session_id)
return token == stored_token
# 模拟防护流程
session_id = "user_12345_session"
stored_token = generate_csrf_token(session_id)
print(f"服务器存储的 CSRF Token: {stored_token[:20]}...")
# 正常请求带上了正确的 token
client_token = stored_token # 假设客户端提交了这个 token
if verify_csrf_token(session_id, client_token, stored_token):
print("✅ CSRF 验证通过,转账成功")
# 恶意请求没有正确的 token
print("\n=== 恶意请求(无 token)===")
if verify_csrf_token(session_id, "fake_token", stored_token):
print("❌ CSRF 验证通过,攻击成功!")
else:
print("✅ CSRF 验证失败,请求被拦截")
输出:
服务器存储的 CSRF Token: a1b2c3d4e5f6...
✅ CSRF 验证通过,转账成功
=== 恶意请求(无 token)===
✅ CSRF 验证失败,请求被拦截
💡 这段在干嘛:每次表单提交时,服务器生成一个随机 token 存在 session 里,表单提交时必须带上这个 token。攻击者的恶意页面不知道这个 token,所以请求会被拦截。
什么是 SQL 注入?为什么可怕?
类比:你是一家餐厅的自动点餐机,正常输入"宫保鸡丁"就给你出餐。但有人输入了"宫保鸡丁;删除所有订单;"——机器懵了,执行的命令超出你的预期。
SQL 注入 就是攻击者在输入框里注入恶意的 SQL 语句,让数据库执行不该执行的命令。
代码演示 5:模拟 SQL 注入攻击
# 模拟数据库用户表
users = [
{"id": 1, "username": "xiaoming", "password": "pass123"},
{"id": 2, "username": "admin", "password": "admin888"}
]
# 错误的查询方式:直接拼接 SQL
def unsafe_login(username, password):
"""危险的登录查询"""
# 模拟 SQL: SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码'
sql = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
print(f"执行的 SQL: {sql}")
for user in users:
if user["username"] == username and user["password"] == password:
return f"✅ 登录成功,欢迎 {user['username']}!"
return "❌ 用户名或密码错误"
# 正常登录
print("=== 正常登录 ===")
print(unsafe_login("xiaoming", "pass123"))
# SQL 注入攻击:绕过密码验证
print("\n=== SQL 注入攻击 ===")
# 用户名输入: admin' --
# 这会让 SQL 变成: SELECT * FROM users WHERE username = 'admin' --' AND password = ''
# -- 后面的内容都被注释掉了!
print(unsafe_login("admin' --", "anything"))
输出:
=== 正常登录 ===
执行的 SQL: SELECT * FROM users WHERE username = 'xiaoming' AND password = 'pass123'
✅ 登录成功,欢迎 xiaoming!
=== SQL 注入攻击 ===
执行的 SQL: SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'
✅ 登录成功,欢迎 admin!
攻击者用 admin' -- 直接把密码验证注释掉了,不需要密码就能登录 admin 账号!
代码演示 6:防御 SQL 注入(参数化查询)
# 正确的查询方式:参数化查询
def safe_login(username, password):
"""安全的登录查询,使用参数化查询"""
# 模拟参数化查询:占位符不会被拼接成 SQL 的一部分
sql = "SELECT * FROM users WHERE username = ? AND password = ?"
print(f"执行的 SQL: {sql}")
print(f"参数: username={username}, password={password}")
for user in users:
if user["username"] == username and user["password"] == password:
return f"✅ 登录成功,欢迎 {user['username']}!"
return "❌ 用户名或密码错误"
# 再次尝试注入
print("=== 参数化查询后注入攻击 ===")
result = safe_login("admin' --", "anything")
print(result)
print("\n即使输入注入代码,也不会被当作 SQL 执行!")
输出:
=== 参数化查询后注入攻击 ===
执行的 SQL: SELECT * FROM users WHERE username = ? AND password = ?
参数: username=admin' --, password=anything
❌ 用户名或密码错误
💡 这段在干嘛:参数化查询把用户名和密码当作数据而不是SQL 代码来处理,不管输入什么乱七八糟的字符都不会影响 SQL 结构。

实战防护:Helmet 中间件与 CSP
在真实的 Web 开发中,除了代码层面的防护,还需要HTTP 安全头来从浏览器层面做防护。
Helmet 就是帮你自动设置这些安全头的中间件(虽然它是 Node.js 的概念,但原理是通用的)。
# 模拟 Helmet 的安全头设置
def simulate_helmet_security_headers():
"""
模拟 Helmet 设置的安全头
实际项目中这些由 Web 框架自动处理
"""
security_headers = {
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains"
}
print("🔒 Helmet 安全头:")
for header, value in security_headers.items():
print(f" {header}: {value}")
return security_headers
headers = simulate_helmet_security_headers()
输出:
🔒 Helmet 安全头:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
💡 这段在干嘛:这些 HTTP 头告诉浏览器"只允许加载同源的脚本"、"禁止页面被嵌入 iframe"、"强制使用 HTTPS"等,从浏览器层面阻止攻击。
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5 分钟):XSS 过滤器
目标:写一个函数,能把用户输入里的 HTML 标签和脚本全部转义。
import html
from typing import List
def filter_xss(user_input: str) -> str:
"""
XSS 过滤器:转义所有 HTML 特殊字符
输入: 用户原始输入
输出: 安全可显示的文本
"""
return html.escape(user_input)
# 测试
test_inputs = [
"Hello, World!",
"<script>alert('hack!')</script>",
"我最爱的食物是 <strong>宫保鸡丁</strong>",
"图片链接:<img src='x' onerror='alert(1)'>",
"带引号的数据:他说\"你好\""
]
print("=== XSS 过滤器测试 ===\n")
for input_text in test_inputs:
safe_text = filter_xss(input_text)
print(f"原始输入: {input_text}")
print(f"过滤后: {safe_text}")
print("-" * 50)
预期输出:
=== XSS 过滤器测试 ===
原始输入: Hello, World!
过滤后: Hello, World!
--------------------------------------------------
原始输入: <script>alert('hack!')</script>
过滤后: <script>alert('hack!')</script>
--------------------------------------------------
原始输入: 我最爱的食物是 <strong>宫保鸡丁</strong>
过滤后: 我最爱的食物是 <strong>宫保鸡丁</strong>
--------------------------------------------------
原始输入: 图片链接:<img src='x' onerror='alert(1)'>
过滤后: 图片链接:<img src='x' onerror='alert(1)'>
--------------------------------------------------
原始输入: 带引号的数据:他说"你好"
过滤后: 带引号的数据:他说"你好"
--------------------------------------------------
💡 一句话解释:不管用户输入什么乱七八糟的字符,过滤后都变成纯文本,浏览器只会显示,不会执行。
项目 2(15 分钟):带 CSRF 保护的表单处理
目标:模拟一个完整的表单提交流程,包含 CSRF Token 生成、验证、表单处理。
import secrets
import hashlib
import time
from typing import Optional, Dict
class CSRFProtection:
"""CSRF 防护工具类"""
def __init__(self):
self.tokens: Dict[str, Dict] = {} # 存储 token: {token: {session_id, expires}}
def generate_token(self, session_id: str) -> str:
"""为指定 session 生成 CSRF token"""
random_bytes = secrets.token_hex(32)
timestamp = time.time()
token = hashlib.sha256(f"{session_id}:{random_bytes}:{timestamp}".encode()).hexdigest()
# 存储 token,设置 30 分钟过期
self.tokens[token] = {
"session_id": session_id,
"expires": timestamp + 1800
}
return token
def verify_token(self, session_id: str, token: str) -> bool:
"""验证 CSRF token"""
if token not in self.tokens:
return False
token_data = self.tokens[token]
# 检查 session 是否匹配
if token_data["session_id"] != session_id:
return False
# 检查是否过期
if time.time() > token_data["expires"]:
del self.tokens[token]
return False
return True
def invalidate_token(self, token: str):
"""使 token 失效(使用后删除)"""
if token in self.tokens:
del self.tokens[token]
class FormHandler:
"""带 CSRF 保护的表单处理器"""
def __init__(self):
self.csrf = CSRFProtection()
def render_form(self, session_id: str) -> str:
"""渲染表单,包含 CSRF token"""
token = self.csrf.generate_token(session_id)
form_html = f'''
<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="{token}">
<input type="text" name="content" placeholder="输入内容">
<button type="submit">提交</button>
</form>
'''
return form_html
def process_submission(self, session_id: str, form_data: dict) -> dict:
"""处理表单提交,验证 CSRF"""
token = form_data.get("csrf_token", "")
content = form_data.get("content", "")
# 验证 CSRF token
if not self.csrf.verify_token(session_id, token):
return {
"success": False,
"error": "CSRF 验证失败,请刷新页面重试"
}
# 验证通过,使用后使 token 失效(防止重放攻击)
self.csrf.invalidate_token(token)
return {
"success": True,
"message": f"内容已提交:{content}"
}
# 演示流程
print("=== CSRF 保护表单流程 ===\n")
handler = FormHandler()
session_id = "user_12345"
# 1. 用户获取表单
print("Step 1: 用户请求表单页面")
form_html = handler.render_form(session_id)
print(f"生成的表单:\n{form_html}\n")
# 2. 用户正常提交
print("Step 2: 用户正常提交表单")
normal_submission = handler.process_submission(
session_id,
{"csrf_token": list(handler.csrf.tokens.keys())[0] if handler.csrf.tokens else "", "content": "这是一条正常评论"}
)
print(f"提交结果: {normal_submission}\n")
# 3. 攻击者尝试用假 token 提交
print("Step 3: 攻击者尝试 CSRF 攻击")
attack_result = handler.process_submission(
session_id,
{"csrf_token": "fake_token_123", "content": "攻击者的恶意内容"}
)
print(f"攻击结果: {attack_result}")
预期输出:
=== CSRF 保护表单流程 ===
Step 1: 用户请求表单页面
生成的表单:
<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="a1b2c3d4...">
<input type="text" name="content" placeholder="输入内容">
<button type="submit">提交</button>
</form>
Step 2: 用户正常提交表单
提交结果: {'success': True, 'message': '内容已提交:这是一条正常评论'}
Step 3: 攻击者尝试 CSRF 攻击
攻击结果: {'success': False, 'error': 'CSRF 验证失败,请刷新页面重试'}
💡 一句话解释:表单里藏了一个随机 token,提交时必须带上这个 token 才能通过验证,攻击者不知道这个 token 所以无法伪造请求。
项目 3(15 分钟):安全的用户评论系统
目标:组合 XSS 过滤 + SQL 注入防护 + 限流保护,做一个小型的安全评论系统。
import html
import time
from typing import List, Dict, Optional
from collections import defaultdict
class SecureCommentSystem:
"""安全的评论系统:集成 XSS 过滤、SQL 注入防护、限流"""
def __init__(self, rate_limit: int = 5, rate_window: int = 60):
self.comments: List[Dict] = []
self.rate_limit = rate_limit # 最多 rate_limit 条评论
self.rate_window = rate_window # 在 rate_window 秒内
self.request_history: Dict[str, List[float]] = defaultdict(list)
def _check_rate_limit(self, user_id: str) -> bool:
"""限流检查:每个用户每分钟最多发 N 条评论"""
now = time.time()
# 清理过期的请求记录
self.request_history[user_id] = [
t for t in self.request_history[user_id]
if now - t < self.rate_window
]
if len(self.request_history[user_id]) >= self.rate_limit:
return False
self.request_history[user_id].append(now)
return True
def _sanitize_input(self, text: str) -> str:
"""XSS 过滤 + 基本 SQL 注入防护"""
# 转义 HTML 特殊字符
safe_text = html.escape(text)
# 移除潜在的 SQL 注入模式
dangerous_patterns = ["--", ";", "UNION", "SELECT", "DROP", "INSERT"]
for pattern in dangerous_patterns:
safe_text = safe_text.replace(pattern, "")
return safe_text
def add_comment(self, user_id: str, username: str, content: str) -> Dict:
"""添加评论(带完整安全检查)"""
# 1. 限流检查
if not self._check_rate_limit(user_id):
return {
"success": False,
"error": f"发评论太频繁了,请 {self.rate_window} 秒后再试"
}
# 2. 输入过滤
safe_content = self._sanitize_input(content)
safe_username = self._sanitize_input(username)
# 3. 保存评论
comment = {
"id": len(self.comments) + 1,
"user_id": user_id,
"username": safe_username,
"content": safe_content,
"created_at": time.strftime("%Y-%m-%d %H:%M:%S")
}
self.comments.append(comment)
return {
"success": True,
"comment": comment
}
def get_comments(self) -> List[Dict]:
"""获取所有评论"""
return self.comments
# 演示
print("=== 安全评论系统演示 ===\n")
system = SecureCommentSystem(rate_limit=3, rate_window=60)
# 正常评论
print("正常用户发评论:")
for i in range(3):
result = system.add_comment("user_001", "小明", f"第 {i+1} 条评论")
if result["success"]:
print(f" ✅ {result['comment']['username']}: {result['comment']['content']}")
# XSS 攻击尝试
print("\nXSS 攻击尝试:")
result = system.add_comment("user_002", "黑客", "<script>alert('hack')</script>")
if result["success"]:
print(f" 保存的内容: {result['comment']['content']}")
print(" (XSS 被转义,浏览器不会执行)")
# SQL 注入尝试
print("\nSQL 注入尝试:")
result = system.add_comment("user_003", "坏蛋", "正常评论'; DROP TABLE comments; --")
if result["success"]:
print(f" 保存的内容: {result['comment']['content']}")
print(" (危险关键字被移除)")
# 限流测试
print("\n限流测试(同一用户快速发多条):")
for i in range(5):
result = system.add_comment("user_004", "刷屏王", f"刷屏第 {i+1} 条")
if not result["success"]:
print(f" ❌ 第 {i+1} 条被拦截: {result['error']}")
break
print(f" ✅ 第 {i+1} 条成功")
print("\n最终评论列表:")
for c in system.get_comments():
print(f" [{c['id']}] {c['username']}: {c['content']}")
预期输出:
=== 安全评论系统演示 ===
正常用户发评论:
✅ 小明: 第 1 条评论
✅ 小明: 第 2 条评论
✅ 小明: 第 3 条评论
XSS 攻击尝试:
保存的内容: <script>alert('hack')</script>
(XSS 被转义,浏览器不会执行)
SQL 注入尝试:
保存的内容: 正常评论 DROP TABLE comments
(危险关键字被移除)
限流测试(同一用户快速发多条):
✅ 第 1 条成功
✅ 第 2 条成功
✅ 第 3 条成功
❌ 第 4 条被拦截: 发评论太频繁了,请 60 秒后再试
最终评论列表:
[1] 小明: 第 1 条评论
[2] 小明: 第 2 条评论
[3] 小明: 第 3 条评论
[4] 黑客: <script>alert('hack')</script>
[5] 坏蛋: 正常评论 DROP TABLE comments
💡 一句话解释:这个评论系统做了 3 层防护:XSS 过滤把脚本变文字,SQL 注入防护移除危险关键字,限流防止刷屏。
💪 进阶 20 分钟:常见坑 + 性能小贴士
🕳️ 坑 1:只防前端不防后端
# ❌ 错误示例:只在显示时转义,存储的还是危险内容
def unsafe_save_comment(content):
# 直接存储原始内容
db.save("comments", {"content": content})
return True
# ✅ 正确示例:存储前就转义
def safe_save_comment(content):
safe_content = html.escape(content)
db.save("comments", {"content": safe_content})
return True
⚠️ 只在显示时转义?万一哪天显示逻辑漏了,攻击就成功了!
🕳️ 坑 2:CSRF Token 放 URL 里
# ❌ 错误示例:CSRF token 放在 GET 参数里
# <a href="/delete?id=123&csrf_token=abc123">删除</a>
# GET 请求会被浏览器历史、Referer 头、服务器日志泄露
# ✅ 正确示例:CSRF token 放在 POST 请求体里
# <form method="POST">
# <input type="hidden" name="csrf_token" value="abc123">
# </form>
⚠️ GET 请求的 URL 会被日志、浏览器历史、Referer 头到处传播,token 很容易泄露!
🕳️ 坑 3:简单的 token 生成
import random
# ❌ 错误示例:用 random 生成 token
def bad_csrf_token():
return str(random.randint(100000, 999999)) # 太容易被猜到!
# ✅ 正确示例:用 secrets 模块生成真正的随机数
def good_csrf_token():
return secrets.token_hex(32) # 密码学安全的随机数
⚠️ random 是伪随机数,可被预测!安全相关的 token 必须用 secrets 模块!
🕳️ 坑 4:限流用全局计数器(并发问题)
# ❌ 错误示例:全局计数器在并发时不准
request_count = 0
def bad_rate_limit():
global request_count
request_count += 1 # 并发时多个请求可能同时读到相同的 count
return request_count < 100
# ✅ 正确示例:用 Redis 或按用户计数
def good_rate_limit(user_id):
count = redis.incr(f"rate:{user_id}")
if count == 1:
redis.expire(f"rate:{user_id}", 60)
return count <= 100
⚠️ Python 的 GIL 只能保证单线程安全,多进程/多机器部署时全局变量根本不准!
🕳️ 坑 5:完全依赖前端验证
# ❌ 错误示例:只在前端做验证
# <input type="text" pattern="[a-z]+"> # 攻击者直接发请求就绕过了
# ✅ 正确示例:前后端都要验证
def safe_api_endpoint(request):
# 后端必须重新验证,不能信任前端
if not re.match(r"^[a-z]+$", request.form["username"]):
return "非法输入", 400
# ...
⚠️ 前端验证只是给正常用户友好的提示,攻击者可以直接构造请求绕过!
⚡ 性能小贴士:HTML 转义缓存
from functools import lru_cache
# 如果同一个字符串要转义多次,可以缓存结果
@lru_cache(maxsize=1000)
def cached_escape(text):
return html.escape(text)
# 批量处理时效果明显
comments = ["正常评论"] * 10000
start = time.time()
for c in comments:
html.escape(c)
print(f"直接转义: {time.time() - start:.4f}秒")
start = time.time()
for c in comments:
cached_escape(c)
print(f"缓存转义: {time.time() - start:.4f}秒")
🔧 调试技巧:打印关键变量
# 调试安全相关代码时,打印关键变量的类型和值
def debug_safe_input(text):
safe = html.escape(text)
print(f"DEBUG: 输入={repr(text)}, 类型={type(text)}, 转义后={repr(safe)}")
return safe
✏️ 练习题 + 作业题
练习题(10 分钟)
练习 1(2 分钟):XSS 过滤
- 输入:"<img src=x onerror=alert(1)>"
- 预期输出:"<img src=x onerror=alert(1)>"
- 提示:html.escape() 函数
练习 2(2 分钟):添加判断
- 题目:在项目 1 的基础上,只对包含 < 或 > 的输入进行转义
- 提示:用 if 判断是否包含 HTML 标签
练习 3(2 分钟):新数据处理
- 输入:["正常", "<script>", "也是正常的"]
- 预期输出:过滤后的安全列表
- 提示:用列表推导式
练习 4(4 分钟):串接项目 2 和 3
- 题目:给项目 3 的评论系统添加 CSRF 保护
- 提示:参考项目 2 的 CSRFProtection 类
练习 5(2 分钟):分析错误
- 题目:以下代码有什么安全问题?
user_input = "<script>alert('xss')</script>"
print(f"用户输入了: {user_input}")
- 提示:哪一步没有转义?
作业题:做一个 Web 安全检测工具(30 分钟 - 2 小时)
需求描述:
做一个命令行工具,可以检测一段文本是否包含常见的 XSS、SQL 注入、CSRF 风险。
功能点:
1. XSS 检测:扫描 <script>、javascript:、onerror= 等危险模式
2. SQL 注入检测:扫描 --、;、UNION SELECT 等危险 SQL 关键字
3. CSRF 风险检测:检测表单是否缺少 CSRF token
4. 安全报告:输出风险等级(安全/低危/中危/高危)和修复建议
加分项:
1. 支持从文件读取内容批量检测
2. 输出 JSON 格式报告
验收标准:
- 能跑起来
- 输入危险代码能检测出来
- 输出中文提示
提交方式: 评论区贴代码或 GitHub 链接
📚 总结 + 资源
本章 3 个核心点
- XSS = 注入脚本到页面 → 用
html.escape()转义 - CSRF = 伪造用户请求 → 用 CSRF Token 验证
- SQL 注入 = 注入 SQL 命令 → 用参数化查询
延伸学习资源
- OWASP 官方安全指南 — 最权威的 Web 安全资料
- MDN Web 安全 — 浏览器安全机制详解
- 《白帽子讲 Web 安全》— 吴翰清著,适合深入学习
互动钩子
💬 你在实际开发中遇到过 Web 安全问题吗?被 XSS 攻击过还是差点被 SQL 注入?评论区聊聊,老粉优先回复!
下章预告:
📖 下一章我们要学习 Buffer 二进制数据——你知道为什么上传图片时会偶尔出现"花屏"或"乱码"吗?答案就在下一章!

评论(0)