CSRF:跨站请求伪造
攻击原理
CSRF(Cross-Site Request Forgery,跨站请求伪造)利用的是:浏览器在发起跨站请求时会自动携带目标站点的 Cookie。攻击者诱骗已登录的受害者访问恶意页面,该页面向受害者已登录的网站发起请求,服务器看到合法的 Cookie 便执行了操作——而受害者根本不知道。
CSRF 的前提条件
- 受害者已登录目标站点(有有效的 Session Cookie)
- 目标站点的操作(转账、改密码、发帖)通过可预测的 HTTP 请求触发
- 请求中没有攻击者无法伪造的参数(如随机 CSRF Token)
CSRF 防御方案
方案一:CSRF Token(同步器令牌模式)
服务器为每个会话(或每个表单)生成一个随机、不可预测的 Token,嵌入表单中。提交时验证 Token 是否匹配。攻击者无法获取受害者页面上的 Token(同源策略阻止跨域读取),因此无法伪造含有效 Token 的请求。
# Flask 实现 CSRF Token(flask-wtf 库已内置)
import secrets
from flask import session, request
def generate_csrf_token():
if 'csrf_token' not in session:
session['csrf_token'] = secrets.token_hex(32) # 256-bit 随机
return session['csrf_token']
def validate_csrf_token():
token = request.form.get('csrf_token') or \
request.headers.get('X-CSRF-Token')
if not token or token != session.get('csrf_token'):
abort(403) # Token 不匹配,拒绝请求
<!-- 表单中嵌入 CSRF Token -->
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input name="amount" type="number">
<button type="submit">转账</button>
</form>
// AJAX 请求中通过请求头携带 CSRF Token
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken // 自定义请求头跨站请求无法设置
},
body: JSON.stringify({ amount: 100, to: 'alice' })
});
方案二:SameSite Cookie(最简单有效)
现代浏览器支持的 SameSite Cookie 属性是防御 CSRF 最优雅的方案,无需修改应用逻辑。
# 推荐配置(防 CSRF + 保持可用性)
Set-Cookie: sessionid=abc123; SameSite=Lax; HttpOnly; Secure; Path=/
# SameSite=Strict 防护最强:
# - 从任何外部站点的链接、表单、iframe、AJAX 发起的请求都不携带 Cookie
# - 缺点:用户从外部链接跳入后是未登录状态(体验差)
# SameSite=Lax(推荐默认值):
# - 跨站 POST/iframe/图片/AJAX 不携带 Cookie(阻止 CSRF 攻击)
# - 用户点击链接导航时携带 Cookie(保持正常跳转体验)
# - 现代浏览器的默认值(不指定 SameSite 时)
方案三:Referer / Origin 头验证
检查请求的 Referer 或 Origin 头,确认请求来自同域。注意:
- 某些浏览器/隐私工具会隐去 Referer 头(会误拦截合法请求)
- Origin 头在现代 CORS 请求中更可靠
- Referer 可能泄露敏感 URL,应作为辅助验证而非主要防御
方案四:Double Submit Cookie
无需服务端存储 Token 的无状态防御方案:服务器生成随机值同时写入 Cookie 和表单字段,提交时验证两者相等。攻击者无法读取 Cookie(同源策略),因此无法在请求中附上相同的值。
推荐组合使用:SameSite=Lax(或 Strict) 作为主要防御 + CSRF Token 作为深度防御。SameSite 已被所有现代浏览器支持,在 SameSite Cookie 生效的情况下,大多数 CSRF 攻击已被阻断。保留 CSRF Token 是为了覆盖旧浏览器和某些 SameSite 不生效的边缘场景(如子域名 Cookie)。
SSRF:服务端请求伪造
攻击原理
SSRF(Server-Side Request Forgery,服务端请求伪造)是指攻击者让服务器向攻击者指定的 URL 发起请求。当应用接收用户提供的 URL 并代为请求时(如"截图 URL 预览"、"导入远程文档"、Webhook 测试),如果没有限制目标地址,攻击者可以让服务器访问内网服务或云环境的元数据接口。
SSRF 可以攻击哪些目标
169.254.169.254,GCP: metadata.google.internal,Azure: 169.254.169.254。可获取 IAM 凭证、用户数据、实例信息。Capital One 2019 年泄露事件的根因。file:// 协议,可读取服务器本地文件:file:///etc/passwd、file:///app/config.yaml(含数据库密码)。SSRF 防御方案
方案一:URL 白名单验证
import urllib.parse
import ipaddress
ALLOWED_DOMAINS = {'api.example.com', 'cdn.example.com'}
def validate_url(url: str) -> bool:
try:
parsed = urllib.parse.urlparse(url)
# 1. 只允许 HTTP/HTTPS 协议
if parsed.scheme not in ('http', 'https'):
return False # 禁止 file://, gopher://, dict:// 等
# 2. 域名白名单
if parsed.hostname not in ALLOWED_DOMAINS:
return False
return True
except:
return False
def is_internal_ip(ip_str: str) -> bool:
"""检查 IP 是否是内网地址"""
try:
ip = ipaddress.ip_address(ip_str)
return (
ip.is_private or
ip.is_loopback or
ip.is_link_local or # 169.254.0.0/16 云元数据
ip.is_reserved or
str(ip) == '0.0.0.0'
)
except:
return True # 解析失败视为危险
方案二:DNS 重绑定防护
DNS 重绑定(DNS Rebinding)是 SSRF 的绕过技术:域名首次解析返回合法 IP(通过白名单),之后 TTL 过期,攻击者将该域名解析到内网 IP。防御:在 URL 验证后,实际发起请求前再次解析并检查解析结果。
import socket
import requests
def safe_request(url: str):
parsed = urllib.parse.urlparse(url)
hostname = parsed.hostname
# 解析所有 IP(处理多 A 记录)
try:
addrs = socket.getaddrinfo(hostname, None)
for addr in addrs:
ip = addr[4][0]
if is_internal_ip(ip):
raise ValueError(f"SSRF 防护:{hostname} 解析到内网地址 {ip}")
except socket.gaierror:
raise ValueError("域名解析失败")
# 限制超时,禁止重定向到内网(防 SSRF via 重定向)
response = requests.get(
url,
timeout=5,
allow_redirects=False, # 手动处理重定向并再次校验
headers={'User-Agent': 'MyApp/1.0'}
)
return response
方案三:网络层隔离(最根本的防御)
除了代码层防御,更根本的是通过网络架构减少 SSRF 的危害范围:
# AWS IMDSv2 强制配置(EC2 实例元数据服务 v2)
# 相比 IMDSv1,v2 需要会话令牌(PUT 请求获取),SSRF 无法直接访问
aws ec2 modify-instance-metadata-options \
--instance-id i-xxxxx \
--http-tokens required \ # 强制要求会话令牌
--http-endpoint enabled
# Kubernetes:限制 Pod 的出网规则(NetworkPolicy)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-metadata-access
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.0.0/16 # 禁止访问 AWS 元数据地址
- 10.0.0.0/8 # 禁止访问内网(按实际需求调整)
- IP 进制绕过:
http://2130706433/(127.0.0.1 的十进制)、http://0x7f000001/(十六进制) - 域名绕过:
http://localhost/、http://[::1]/(IPv6 回环) - URL 编码:
%31%36%39%2e%32%35%34%2e%31%36%39%2e%32%35%34 - 重定向链:合法域名 → 301 重定向到内网 IP(需在跟随重定向时再次校验)
- 自建 DNS:DNS 重绑定(解析先返回合法 IP,后返回内网 IP)
防御时需考虑这些绕过手段:解析 IP 后校验,而不只是字符串匹配;使用 DNS 固定(DNS pinning);网络层封锁更可靠。
CSRF 利用浏览器自动携带 Cookie 的特性,让受害者在不知情的情况下触发操作。防御三剑客:SameSite Cookie(现代浏览器默认 Lax)+ CSRF Token(深度防御)+ Origin 验证。SSRF 让服务器成为攻击者的代理,最危险的目标是云元数据接口和内网服务。防御核心:URL 白名单 + DNS 解析后的 IP 校验 + 网络层隔离(最根本)。两种攻击都体现了"信任关系被滥用"的安全思维——攻击者不是直接攻击,而是利用受信任的中间方代为执行。