detecting-broken-object-property-level-authorization

检测和测试OWASP API3:2023对象属性级授权失效(BOPLA)漏洞,包括过度数据暴露和批量赋值攻击。

9 stars

Best use case

detecting-broken-object-property-level-authorization is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

检测和测试OWASP API3:2023对象属性级授权失效(BOPLA)漏洞,包括过度数据暴露和批量赋值攻击。

Teams using detecting-broken-object-property-level-authorization should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/detecting-broken-object-property-level-authorization/SKILL.md --create-dirs "https://raw.githubusercontent.com/killvxk/cybersecurity-skills-zh/main/skills/detecting-broken-object-property-level-authorization/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/detecting-broken-object-property-level-authorization/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How detecting-broken-object-property-level-authorization Compares

Feature / Agentdetecting-broken-object-property-level-authorizationStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

检测和测试OWASP API3:2023对象属性级授权失效(BOPLA)漏洞,包括过度数据暴露和批量赋值攻击。

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 检测对象属性级授权失效

## 概述

对象属性级授权失效(Broken Object Property Level Authorization,BOPLA)被OWASP API安全Top 10归类为API3:2023,结合了两类相关漏洞:过度数据暴露(API返回超出所需的数据)和批量赋值(Mass Assignment,API接受超出预期的数据)。即使API正确地执行了对象级授权,也可能无法控制用户对对象特定属性的读写权限。攻击者利用此漏洞从API响应中读取敏感属性,或向请求体中注入额外属性来修改其无权访问的字段。

## 前置条件

- 目标API需有返回或接受对象数据的端点
- API文档或模式(优先使用OpenAPI规范)
- Burp Suite或Postman用于API请求操控
- 具有不同权限级别的多个用户账户
- Python 3.8+及requests库用于自动化测试
- 已获得执行安全测试的授权

## 漏洞模式

### 过度数据暴露

API返回的对象属性超出客户端所需:

```json
// GET /api/v1/users/123
// 响应包含UI未显示的敏感字段:
{
  "id": 123,
  "username": "john_doe",
  "email": "john@example.com",
  "name": "John Doe",
  "ssn": "123-45-6789",           // 敏感 - UI不需要
  "salary": 95000,                 // 敏感 - UI不需要
  "internal_notes": "VIP client",  // 内部 - 不应暴露
  "password_hash": "$2b$12...",    // 严重 - 永远不应暴露
  "role": "admin",                 // 可能暴露权限信息
  "created_by": "system_admin",   // 内部元数据
  "credit_card_last4": "4242"     // PCI合规违规
}
```

### 批量赋值

API未过滤地将客户端提供的数据绑定到内部对象属性:

```http
// 普通用户更新请求
PUT /api/v1/users/123
Content-Type: application/json

{
  "name": "John Updated",
  "email": "new@example.com",
  "role": "admin",           // 攻击者注入:权限提升
  "is_verified": true,       // 攻击者注入:绕过验证
  "discount_rate": 100,      // 攻击者注入:业务逻辑滥用
  "account_balance": 999999  // 攻击者注入:金融欺诈
}
```

## 测试方法

```python
#!/usr/bin/env python3
"""BOPLA漏洞扫描器

测试API是否存在对象属性级授权失效(BOPLA)漏洞,
包括过度数据暴露和批量赋值。
"""

import requests
import json
import sys
from typing import Dict, List, Optional, Set
from dataclasses import dataclass, field
from copy import deepcopy

@dataclass
class BOPLAFinding:
    endpoint: str
    method: str
    vulnerability_type: str  # "excessive_exposure" 或 "mass_assignment"
    severity: str
    property_name: str
    details: str

class BOPLAScanner:
    SENSITIVE_PROPERTY_PATTERNS = {
        "critical": [
            "password", "password_hash", "secret", "token", "api_key",
            "private_key", "secret_key", "access_token", "refresh_token",
        ],
        "high": [
            "ssn", "social_security", "tax_id", "credit_card", "card_number",
            "cvv", "bank_account", "routing_number",
        ],
        "medium": [
            "salary", "income", "internal_notes", "admin_notes",
            "created_by", "modified_by", "ip_address", "session_id",
            "role", "permissions", "is_admin", "is_superuser", "privilege",
        ],
        "low": [
            "phone", "address", "date_of_birth", "dob", "age",
            "gender", "ethnicity", "religion",
        ]
    }

    MASS_ASSIGNMENT_FIELDS = [
        ("role", "admin"),
        ("is_admin", True),
        ("is_verified", True),
        ("is_active", True),
        ("email_verified", True),
        ("account_type", "premium"),
        ("discount_rate", 100),
        ("credit_limit", 999999),
        ("permissions", ["admin", "write", "delete"]),
        ("account_balance", 999999),
        ("subscription_tier", "enterprise"),
        ("rate_limit", 999999),
    ]

    def __init__(self, base_url: str, auth_headers: Dict[str, str]):
        self.base_url = base_url.rstrip('/')
        self.auth_headers = auth_headers
        self.findings: List[BOPLAFinding] = []

    def test_excessive_data_exposure(self, endpoint: str,
                                      expected_fields: Set[str]) -> List[BOPLAFinding]:
        """测试API响应是否包含超出预期字段的内容。"""
        findings = []
        url = f"{self.base_url}{endpoint}"

        try:
            response = requests.get(url, headers=self.auth_headers, timeout=10)
            if response.status_code != 200:
                return findings

            data = response.json()

            # 处理单对象和列表响应
            objects = data if isinstance(data, list) else [data]
            if isinstance(data, dict) and "data" in data:
                objects = data["data"] if isinstance(data["data"], list) else [data["data"]]

            for obj in objects[:5]:  # 检查前5个对象
                if not isinstance(obj, dict):
                    continue

                response_fields = set(self._flatten_keys(obj))
                unexpected_fields = response_fields - expected_fields

                for field_name in unexpected_fields:
                    severity = self._classify_sensitivity(field_name)
                    if severity:
                        finding = BOPLAFinding(
                            endpoint=endpoint,
                            method="GET",
                            vulnerability_type="excessive_exposure",
                            severity=severity,
                            property_name=field_name,
                            details=f"响应中出现意外的敏感字段 '{field_name}'"
                        )
                        findings.append(finding)
                        self.findings.append(finding)

        except (requests.exceptions.RequestException, json.JSONDecodeError):
            pass

        return findings

    def test_mass_assignment(self, endpoint: str, method: str = "PUT",
                              original_data: Optional[dict] = None) -> List[BOPLAFinding]:
        """测试API是否接受并处理额外注入的属性。"""
        findings = []
        url = f"{self.base_url}{endpoint}"

        # 首先获取当前对象状态
        if original_data is None:
            try:
                response = requests.get(url, headers=self.auth_headers, timeout=10)
                if response.status_code == 200:
                    original_data = response.json()
                else:
                    original_data = {}
            except (requests.exceptions.RequestException, json.JSONDecodeError):
                original_data = {}

        # 测试每个批量赋值字段
        for field_name, injected_value in self.MASS_ASSIGNMENT_FIELDS:
            if field_name in original_data:
                # 字段存在 - 测试是否可以修改
                original_value = original_data[field_name]
                if original_value == injected_value:
                    continue  # 已有该值

            test_data = deepcopy(original_data)
            test_data[field_name] = injected_value

            headers = {**self.auth_headers, "Content-Type": "application/json"}

            try:
                if method == "PUT":
                    response = requests.put(url, json=test_data,
                                          headers=headers, timeout=10)
                elif method == "PATCH":
                    response = requests.patch(url, json={field_name: injected_value},
                                            headers=headers, timeout=10)
                elif method == "POST":
                    response = requests.post(url, json=test_data,
                                           headers=headers, timeout=10)

                if response.status_code in (200, 201, 204):
                    # 验证字段是否实际被修改
                    verify_response = requests.get(url, headers=self.auth_headers, timeout=10)
                    if verify_response.status_code == 200:
                        updated_data = verify_response.json()
                        if updated_data.get(field_name) == injected_value:
                            finding = BOPLAFinding(
                                endpoint=endpoint,
                                method=method,
                                vulnerability_type="mass_assignment",
                                severity="CRITICAL" if field_name in ["role", "is_admin", "permissions"]
                                         else "HIGH",
                                property_name=field_name,
                                details=f"成功注入 '{field_name}={injected_value}'"
                            )
                            findings.append(finding)
                            self.findings.append(finding)

                            # 如可能则恢复原始值
                            if field_name in original_data:
                                restore_data = {field_name: original_data[field_name]}
                                requests.patch(url, json=restore_data,
                                             headers=headers, timeout=10)

            except requests.exceptions.RequestException:
                continue

        return findings

    def test_graphql_property_exposure(self, graphql_endpoint: str,
                                        query: str) -> List[BOPLAFinding]:
        """测试GraphQL API是否存在属性级授权问题。"""
        findings = []
        url = f"{self.base_url}{graphql_endpoint}"

        # 用于发现可用字段的自省查询
        introspection = """
        {
          __schema {
            types {
              name
              fields {
                name
                type { name kind }
              }
            }
          }
        }
        """

        try:
            response = requests.post(
                url,
                json={"query": introspection},
                headers=self.auth_headers,
                timeout=10
            )

            if response.status_code == 200:
                data = response.json()
                if "errors" not in data:
                    finding = BOPLAFinding(
                        endpoint=graphql_endpoint,
                        method="POST",
                        vulnerability_type="excessive_exposure",
                        severity="MEDIUM",
                        property_name="__schema",
                        details="GraphQL自省已启用 - 完整模式已暴露"
                    )
                    findings.append(finding)
                    self.findings.append(finding)

        except requests.exceptions.RequestException:
            pass

        return findings

    def _flatten_keys(self, obj: dict, prefix: str = "") -> List[str]:
        """递归展开嵌套字典的键。"""
        keys = []
        for key, value in obj.items():
            full_key = f"{prefix}.{key}" if prefix else key
            keys.append(full_key)
            if isinstance(value, dict):
                keys.extend(self._flatten_keys(value, full_key))
        return keys

    def _classify_sensitivity(self, field_name: str) -> Optional[str]:
        """对字段名进行敏感度分类。"""
        lower_name = field_name.lower().split('.')[-1]
        for severity, patterns in self.SENSITIVE_PROPERTY_PATTERNS.items():
            for pattern in patterns:
                if pattern in lower_name:
                    return severity.upper()
        return None

    def generate_report(self) -> dict:
        return {
            "total_findings": len(self.findings),
            "by_type": {
                "excessive_exposure": len([f for f in self.findings
                                          if f.vulnerability_type == "excessive_exposure"]),
                "mass_assignment": len([f for f in self.findings
                                       if f.vulnerability_type == "mass_assignment"]),
            },
            "by_severity": {
                "CRITICAL": len([f for f in self.findings if f.severity == "CRITICAL"]),
                "HIGH": len([f for f in self.findings if f.severity == "HIGH"]),
                "MEDIUM": len([f for f in self.findings if f.severity == "MEDIUM"]),
                "LOW": len([f for f in self.findings if f.severity == "LOW"]),
            },
            "findings": [
                {
                    "endpoint": f.endpoint,
                    "method": f.method,
                    "type": f.vulnerability_type,
                    "severity": f.severity,
                    "property": f.property_name,
                    "details": f.details,
                }
                for f in self.findings
            ]
        }
```

## 修复措施

```python
# 服务端:显式属性白名单
class UserSerializer:
    # 只暴露这些字段 - 永远不要使用to_json()或to_dict()
    PUBLIC_FIELDS = ['id', 'username', 'name', 'avatar_url']
    OWNER_FIELDS = PUBLIC_FIELDS + ['email', 'phone', 'preferences']
    ADMIN_FIELDS = OWNER_FIELDS + ['role', 'created_at', 'last_login']

    def serialize(self, user, requesting_user):
        if requesting_user.is_admin:
            fields = self.ADMIN_FIELDS
        elif requesting_user.id == user.id:
            fields = self.OWNER_FIELDS
        else:
            fields = self.PUBLIC_FIELDS

        return {field: getattr(user, field) for field in fields}

# 批量赋值保护 - 可写字段的显式白名单
WRITABLE_FIELDS = {'name', 'email', 'phone', 'avatar_url', 'preferences'}

def update_user(user_id, request_data, requesting_user):
    # 过滤掉不在白名单中的字段
    safe_data = {k: v for k, v in request_data.items() if k in WRITABLE_FIELDS}
    # 仅使用安全数据应用更新
    User.objects.filter(id=user_id).update(**safe_data)
```

## 参考资料

- OWASP API3:2023: https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- Salt Security BOPLA Analysis: https://salt.security/blog/api3-2023-broken-object-property-level-authorization
- Wallarm BOPLA Guide: https://lab.wallarm.com/api32023-broken-object-property-level-authorization/
- API Security News BOPLA: https://apisecurity.io/owasp-api-security-top-10/api3-2023-broken-object-property-level-authorization/
- CloudDefense BOPLA: https://www.clouddefense.ai/owasp/2023/3

Related Skills

testing-for-broken-access-control

9
from killvxk/cybersecurity-skills-zh

系统性测试 Web 应用程序中的访问控制缺陷,包括权限提升、缺失的功能级检查以及不安全的直接对象引用。

testing-api-for-broken-object-level-authorization

9
from killvxk/cybersecurity-skills-zh

测试REST和GraphQL API中的越权对象访问(BOLA/IDOR)漏洞,即已认证用户通过操纵API请求中的对象标识符 来访问或修改属于其他用户的资源。测试人员拦截API调用,识别对象ID参数(数字ID、UUID、slug), 并系统性地替换为其他用户的ID,以确定服务器是否执行了对象级授权。 对应OWASP API安全Top 10 2023 API1(越权对象访问)。

exploiting-broken-link-hijacking

9
from killvxk/cybersecurity-skills-zh

通过识别对过期域名、已停用云资源和失效外部服务的引用,发现并利用断链劫持(Broken Link Hijacking)漏洞,攻击者可申领这些资源。

exploiting-broken-function-level-authorization

9
from killvxk/cybersecurity-skills-zh

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

detecting-wmi-persistence

9
from killvxk/cybersecurity-skills-zh

通过分析 Sysmon 事件 ID 19、20 和 21 中的恶意 EventFilter、EventConsumer 和 FilterToConsumerBinding 创建,检测 WMI 事件订阅持久化。

detecting-t1548-abuse-elevation-control-mechanism

9
from killvxk/cybersecurity-skills-zh

通过监控注册表修改、进程提升标志和异常的父子进程关系,检测提升控制机制滥用,包括 UAC 绕过、sudo 利用和 setuid/setgid 操纵。

detecting-t1055-process-injection-with-sysmon

9
from killvxk/cybersecurity-skills-zh

通过分析 Sysmon 事件中的跨进程内存操作、远程线程创建和异常 DLL 加载模式,检测进程注入技术(T1055),包括经典 DLL 注入、进程镂空和 APC 注入。

detecting-t1003-credential-dumping-with-edr

9
from killvxk/cybersecurity-skills-zh

利用 EDR 遥测数据、Sysmon 进程访问监控和 Windows 安全事件关联,检测针对 LSASS 内存、SAM 数据库、NTDS.dit 和缓存凭据的 OS 凭据转储技术。

detecting-suspicious-powershell-execution

9
from killvxk/cybersecurity-skills-zh

检测可疑的 PowerShell 执行模式,包括编码命令、下载器(download cradles)、AMSI 绕过尝试以及受限语言模式规避。

detecting-suspicious-oauth-application-consent

9
from killvxk/cybersecurity-skills-zh

使用 Microsoft Graph API、审计日志和权限分析,检测 Azure AD / Microsoft Entra ID 中的高风险 OAuth 应用授权同意,识别非法同意授权攻击。

detecting-supply-chain-attacks-in-ci-cd

9
from killvxk/cybersecurity-skills-zh

扫描 GitHub Actions 工作流和 CI/CD 流水线配置,检测供应链攻击(Supply Chain Attack)向量, 包括未固定的 Action 版本、通过表达式的脚本注入、依赖混淆(Dependency Confusion)和密钥泄露。 使用 PyGithub 和 YAML 解析进行自动化审计。适用于加固 CI/CD 流水线或调查被攻击的构建系统。

detecting-stuxnet-style-attacks

9
from killvxk/cybersecurity-skills-zh

本技能涵盖检测遵循Stuxnet攻击模式的复杂网络物理攻击——在修改PLC逻辑的同时欺骗传感器读数以向操作员隐藏操控行为。内容涉及PLC逻辑完整性监控、基于物理的过程异常检测、工程师工作站入侵指标、USB传播攻击向量,以及从IT到OT横向移动直至过程操控的多阶段攻击链检测。