第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>&lt;script&gt;alert(&#x27;你的cookie被偷了!&#x27;)&lt;/script&gt;</p>
<p>这家店的服务很棒</p>
</div>

💡 这行在干嘛:html.escape()< 变成 &lt;,把 > 变成 &gt;,浏览器就只会显示文字,不会执行脚本了。

配图1 - 配图1


什么是 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 结构。

配图2 - 配图2


实战防护: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>
过滤后:   &lt;script&gt;alert(&#x27;hack!&#x27;)&lt;/script&gt;
--------------------------------------------------
原始输入: 我最爱的食物是 <strong>宫保鸡丁</strong>
过滤后:   我最爱的食物是 &lt;strong&gt;宫保鸡丁&lt;/strong&gt;
--------------------------------------------------
原始输入: 图片链接:<img src='x' onerror='alert(1)'>
过滤后:   图片链接:&lt;img src=&#x27;x&#x27; onerror=&#x27;alert(1)&#x27;&gt;
--------------------------------------------------
原始输入: 带引号的数据:他说"你好"
过滤后:   带引号的数据:他说&quot;你好&quot;
--------------------------------------------------

💡 一句话解释:不管用户输入什么乱七八糟的字符,过滤后都变成纯文本,浏览器只会显示,不会执行。


项目 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 攻击尝试:
保存的内容: &lt;script&gt;alert(&#x27;hack&#x27;)&lt;/script&gt;
(XSS 被转义,浏览器不会执行)

SQL 注入尝试:
保存的内容: 正常评论  DROP TABLE comments 
(危险关键字被移除)

限流测试(同一用户快速发多条):
✅ 第 1 条成功
✅ 第 2 条成功
✅ 第 3 条成功
❌ 第 4 条被拦截: 发评论太频繁了,请 60 秒后再试

最终评论列表:
[1] 小明: 第 1 条评论
[2] 小明: 第 2 条评论
[3] 小明: 第 3 条评论
[4] 黑客: &lt;script&gt;alert(&#x27;hack&#x27;)&lt;/script&gt;
[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)>"
- 预期输出:"&lt;img src=x onerror=alert(1)&gt;"
- 提示: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 个核心点

  1. XSS = 注入脚本到页面 → 用 html.escape() 转义
  2. CSRF = 伪造用户请求 → 用 CSRF Token 验证
  3. SQL 注入 = 注入 SQL 命令 → 用参数化查询

延伸学习资源

  1. OWASP 官方安全指南 — 最权威的 Web 安全资料
  2. MDN Web 安全 — 浏览器安全机制详解
  3. 《白帽子讲 Web 安全》— 吴翰清著,适合深入学习

互动钩子

💬 你在实际开发中遇到过 Web 安全问题吗?被 XSS 攻击过还是差点被 SQL 注入?评论区聊聊,老粉优先回复!


下章预告:

📖 下一章我们要学习 Buffer 二进制数据——你知道为什么上传图片时会偶尔出现"花屏"或"乱码"吗?答案就在下一章!


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。