IDOR:不安全直接对象引用
IDOR(Insecure Direct Object Reference)是最常见的访问控制漏洞。当应用直接使用用户可控的参数(如 ID、文件名)来引用资源,而没有验证当前用户是否有权访问该资源时,攻击者只需修改参数值即可访问其他用户的数据。
典型 IDOR 场景
# 正常请求:用户查看自己的订单
GET /api/orders/10042
Authorization: Bearer user_alice_token
# 响应:{ "order_id": 10042, "user": "alice", "items": [...] }
# IDOR 攻击:将 ID 改为其他用户的订单
GET /api/orders/10041
Authorization: Bearer user_alice_token
# 如果服务器只校验了"用户是否登录"而没有校验"订单是否属于该用户"
# 响应会返回 Bob 的订单数据!
有漏洞的代码 vs 安全代码
# 危险:只根据 ID 查询,没有校验归属
@app.route('/api/orders/<int:order_id>')
@require_login
def get_order(order_id):
order = Order.query.get(order_id) # 直接查,不验证归属
if not order:
return {"error": "not found"}, 404
return order.to_dict() # 任何登录用户都能访问任意订单!
# 安全:在查询中绑定当前用户
@app.route('/api/orders/<int:order_id>')
@require_login
def get_order_safe(order_id):
current_user_id = get_current_user_id() # 从 JWT/Session 获取用户 ID
order = Order.query.filter_by(
id=order_id,
user_id=current_user_id # 同时校验 ID 和 user_id,一个条件不满足则无结果
).first()
if not order:
return {"error": "not found"}, 404
# 注意:不要说"无权访问"——这会暴露资源存在!
return order.to_dict()
IDOR 不只出现在数字 ID 上:文件下载(/download?file=invoice_2024_alice.pdf)、用户名(/profile/alice)、邮箱地址、UUID(虽然不可猜测,但如果被暴露在日志或 URL 中就能被枚举)。防御规则不变:不管 ID 是什么格式,服务端必须验证资源归属。
水平越权 vs 垂直越权
相同权限级别的用户之间的越权。Alice 可以访问 Bob 的数据,但两人都是普通用户。IDOR 是最典型的水平越权。
例: 用户 A 修改了用户 B 的头像;用户 A 读取了用户 B 的私信记录。
低权限用户访问了高权限功能。普通用户执行了管理员操作。危害通常更大。
例: 普通用户访问 /admin/delete_user;修改请求中的 role=user 为 role=admin。
# 垂直越权漏洞:依赖客户端传递的角色
@app.route('/api/admin/delete-user', methods=['DELETE'])
def delete_user():
role = request.json.get('role') # 危险!从请求体获取角色
if role == 'admin': # 攻击者可以在请求中改 role 为 admin
do_delete(request.json['user_id'])
...
# 安全:从服务端的认证信息获取角色,不信任客户端
@app.route('/api/admin/delete-user', methods=['DELETE'])
@require_role('admin') # 装饰器从 JWT/Session 中验证角色
def delete_user_safe():
# 执行到这里说明装饰器已验证是管理员
do_delete(request.json['user_id'])
def require_role(role):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
user = get_current_user() # 从 JWT 中获取,不从请求体获取
if not user or user.role != role:
return {"error": "Forbidden"}, 403
return f(*args, **kwargs)
return wrapper
return decorator
RBAC:基于角色的访问控制
RBAC(Role-Based Access Control)是最常用的权限模型。用户被分配到角色,角色拥有权限。不直接给用户分配权限,而是通过角色管理。
# 简单 RBAC 实现
PERMISSIONS = {
'viewer': {'order:read', 'product:read'},
'editor': {'order:read', 'order:write', 'product:read', 'product:write'},
'admin': {'order:read', 'order:write', 'order:delete',
'user:read', 'user:manage', 'product:read',
'product:write', 'product:delete'},
}
def has_permission(user, permission: str) -> bool:
role = user.role
return permission in PERMISSIONS.get(role, set())
# 在视图中使用
@app.route('/api/orders/<int:id>', methods=['DELETE'])
@require_login
def delete_order(id):
if not has_permission(current_user, 'order:delete'):
return {"error": "权限不足"}, 403
# 还要验证这个 order 属于当前用户或管理员有权删除
order = get_order_or_403(id, current_user)
order.delete()
目录遍历攻击
目录遍历(Directory Traversal / Path Traversal)攻击通过在文件路径参数中注入 ../(相对路径跳转),读取服务器上任意文件,如 /etc/passwd、应用配置文件等。
# 正常请求
GET /download?file=invoice.pdf
# 目录遍历攻击(读取 Linux 密码文件)
GET /download?file=../../../../../../etc/passwd
# URL 编码绕过
GET /download?file=..%2F..%2F..%2Fetc%2Fpasswd
# 双重编码绕过(某些框架会解码两次)
GET /download?file=..%252F..%252Fetc%252Fpasswd
import os
from pathlib import Path
# 安全的文件访问
BASE_DIR = Path("/app/uploads").resolve()
def safe_file_path(filename: str) -> Path:
# 1. 拼接到基础目录
requested = (BASE_DIR / filename).resolve()
# 2. 验证最终路径是否仍在允许目录下
if not str(requested).startswith(str(BASE_DIR)):
raise ValueError("路径遍历攻击被阻止")
# 3. 白名单文件扩展名(可选,额外防护)
ALLOWED_EXTENSIONS = {'.pdf', '.jpg', '.png'}
if requested.suffix.lower() not in ALLOWED_EXTENSIONS:
raise ValueError("不支持的文件类型")
return requested
@app.route('/download')
@require_login
def download_file():
try:
path = safe_file_path(request.args['file'])
return send_file(path)
except ValueError:
return {"error": "文件不存在"}, 404
最小权限原则实践
最小权限原则(Principle of Least Privilege,PoLP):每个用户、进程、服务只应拥有完成其工作所必需的最小权限。这是防御深度(Defense in Depth)策略的核心——即使某一环节被突破,受限的权限也能限制爆炸半径。
| 场景 | 不良实践 | 最小权限实践 |
|---|---|---|
| 数据库账号 | 应用使用 root 账号连接数据库 | 只授予 SELECT/INSERT/UPDATE/DELETE,不授予 DROP/CREATE/FILE |
| 文件系统 | Web 服务以 root 运行 | 使用独立的低权限用户(www-data),只读取必要目录 |
| 云 IAM | Lambda 函数有 AdministratorAccess 策略 | 只授予 s3:GetObject 到特定 bucket 的精确权限 |
| API 密钥 | 全功能 API Key 直接写在代码里 | 只读 Key 用于只读操作,密钥通过环境变量/Secret Manager 注入 |
| 容器 | 容器以 root 身份运行 | Dockerfile 添加 USER 1000,只挂载必要目录(只读) |
访问控制是 OWASP 2021 的头号安全风险。核心要点:① IDOR 是最普遍的漏洞——每次查询都要绑定当前用户,不能只根据 ID 查询;② 服务端鉴权是铁律——角色和权限从服务端认证信息获取,永远不要信任客户端传递的权限参数;③ RBAC 提供清晰的权限管理模型,权限命名用 资源:操作 格式;④ 目录遍历通过 Path 规范化+白名单防御;⑤ 最小权限原则限制爆炸半径,是纵深防御的核心实践。