Showing Posts From

Appsec

API Security Blueprint: Defending Microservices Against BOLA and BFLA Attacks

API Security Blueprint: Defending Microservices Against BOLA and BFLA Attacks

APIs are the connective tissue of modern software. They are also the primary attack surface. The OWASP API Security Top 10 places Broken Object Level Authorization (BOLA) and Broken Function Level Authorization (BFLA) at positions #1 and #5 respectively — and for good reason. These vulnerabilities are endemic, trivially exploitable, and catastrophically impactful. In microservices architectures, where dozens of APIs communicate internally and externally, a single misconfigured authorization layer creates a blast radius that spans entire domains of sensitive data.Understanding BOLA (Broken Object Level Authorization) BOLA occurs when an API exposes object identifiers (IDs) in requests but fails to verify that the authenticated user has permission to access that specific object. Vulnerable pattern: GET /api/v1/invoices/84729 Authorization: Bearer <user_A_token>If the server returns the invoice regardless of whether user_A owns invoice 84729, it is vulnerable to BOLA. An attacker simply iterates IDs: import requestsheaders = {"Authorization": f"Bearer {attacker_token}"} for invoice_id in range(80000, 90000): r = requests.get(f"https://api.example.com/api/v1/invoices/{invoice_id}", headers=headers) if r.status_code == 200: exfiltrate(r.json()) # Every 200 is someone else's dataThis is not a theoretical attack. BOLA was the root cause of the Optus (11M customer records), T-Mobile (2021), and dozens of fintech API breaches.Implementing Robust Object-Level Authorization Option 1: Database-level ownership checks (mandatory baseline) Never trust the client-supplied ID alone. Always enforce ownership in your query: # VULNERABLE def get_invoice(invoice_id: int, current_user: User): return db.query(Invoice).filter(Invoice.id == invoice_id).first()# SECURE — ownership enforced at query level def get_invoice(invoice_id: int, current_user: User): invoice = ( db.query(Invoice) .filter(Invoice.id == invoice_id, Invoice.owner_id == current_user.id) .first() ) if not invoice: raise HTTPException(status_code=404) # Always 404, never 403 — prevents enumeration return invoiceOption 2: Non-sequential, non-guessable identifiers Replace auto-increment integer IDs with UUIDs or ULIDs. This doesn't fix authorization logic, but it eliminates trivial enumeration: import ulidclass Invoice(Base): id: str = Column(String, primary_key=True, default=lambda: str(ulid.new()))Option 3: Relationship-scoped queries In multi-tenant systems, scope all queries to the tenant context extracted from the JWT: def get_invoices(current_user: User, db: Session): return ( db.query(Invoice) .join(Organization) .filter( Organization.id == current_user.organization_id, Invoice.organization_id == current_user.organization_id, ) .all() )Understanding BFLA (Broken Function Level Authorization) BFLA occurs when an API exposes administrative or privileged functions to users who should not have access — typically because authorization checks on function-level (not just data-level) are missing or incorrect. Attack scenario: # Regular user discovers admin endpoints via API spec exposure GET /api/v1/admin/users → 403 Forbidden (good) DELETE /api/v1/users/54321 → 200 OK (should be 403 for non-admins!) POST /api/v1/users/54321/promote → 200 OK (privilege escalation!)Common root causes:Frontend hides admin buttons, but backend doesn't enforce role checks API versioning gaps — /api/v2/ has checks, /api/v1/ endpoints were forgotten HTTP method confusion — GET /invoice/123 is authorized, DELETE /invoice/123 is not checked separatelyImplementing Function-Level Authorization Role-Based Access Control (RBAC) decorator pattern: from functools import wraps from enum import Enumclass Role(Enum): USER = "user" MANAGER = "manager" ADMIN = "admin"ROLE_HIERARCHY = {Role.USER: 0, Role.MANAGER: 1, Role.ADMIN: 2}def require_role(minimum_role: Role): def decorator(func): @wraps(func) async def wrapper(*args, current_user: User, **kwargs): if ROLE_HIERARCHY[current_user.role] < ROLE_HIERARCHY[minimum_role]: raise HTTPException( status_code=403, detail="Insufficient privileges" ) return await func(*args, current_user=current_user, **kwargs) return wrapper return decorator# Usage @router.delete("/users/{user_id}") @require_role(Role.ADMIN) async def delete_user(user_id: str, current_user: User = Depends(get_current_user)): ...@router.post("/users/{user_id}/promote") @require_role(Role.ADMIN) async def promote_user(user_id: str, current_user: User = Depends(get_current_user)): ...Attribute-Based Access Control (ABAC) for fine-grained policies: For complex enterprise systems, RBAC alone is insufficient. Use OPA as a centralized policy engine: # opa/policies/api_authz.rego package api.authzdefault allow = false# Users can only read their own resources allow { input.method == "GET" input.path == ["api", "v1", "invoices", invoice_id] input.user.id == data.invoices[invoice_id].owner_id }# Managers can read any invoice within their organization allow { input.method == "GET" input.path == ["api", "v1", "invoices", _] input.user.role == "manager" input.user.org_id == data.invoices[invoice_id].org_id }# Only admins can delete allow { input.method == "DELETE" input.user.role == "admin" }API Gateway-Level Enforcement Authorization logic must not live only in individual microservices — that creates drift and gaps. Enforce coarse-grained policies at the API gateway layer (Kong, AWS API Gateway, Apigee): # Kong declarative config — rate limit and JWT validation plugins: - name: jwt config: key_claim_name: kid claims_to_verify: - exp - iss - name: rate-limiting config: minute: 60 hour: 1000 policy: redis - name: opa config: server: http://opa-service:8181 policy: api/authz/allowDetection & Anomaly Monitoring Authorization vulnerabilities that reach production require behavioral detection: # Alert on BOLA enumeration patterns rule = { "name": "API Object Enumeration Detected", "condition": ( "same_user_id AND sequential_resource_ids AND " "request_count_1min > 50 AND " "response_mix_200_404" ), "action": ["block_user", "alert_security_team", "create_incident"], }Key signals to monitor:Same user accessing sequential or high-volume distinct object IDs in short windows 403/404 response volume spikes from a single source Unexpected HTTP method usage on resource endpoints (DELETE, PATCH from user-tier tokens)API Security Checklist Object-level authorization enforced at query layer — never trust client-supplied IDs alone Non-sequential identifiers (UUID/ULID) on all externally-exposed resources Function-level authorization enforced server-side via RBAC/ABAC on every endpoint HTTP method authorization verified — GET auth ≠ DELETE auth API spec not publicly exposed (redact or gate OpenAPI/Swagger in production) All API versions (v1, v2) have equivalent authorization coverage API gateway enforcing JWT validation, rate limiting, and OPA policy evaluation Behavioral anomaly detection alerting on enumeration patterns Regular BOLA/BFLA-targeted penetration tests (quarterly minimum)GurkhaX conducts API security audits, penetration testing, and authorization architecture reviews for microservices platforms. Request an API security assessment from our offensive security team.

Read IntelPOST #01
Automated Threat Modeling in CI/CD: Shifting Security Left at Scale

Automated Threat Modeling in CI/CD: Shifting Security Left at Scale

Security teams have historically operated as a gate at the end of the software delivery pipeline — reviewing code after it's written, scanning images after they're built, and auditing infrastructure after it's provisioned. The result: security debt accumulates, vulnerabilities reach production, and fixes are exponentially more expensive than if they had been caught at the design stage. Shifting security left means embedding security controls directly into the development workflow, transforming security from a gate into a continuous, automated guardrail.The DevSecOps Pipeline Architecture Developer Commit │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Pre-Commit Hooks (Gitleaks, detect-secrets, trufflehog) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ CI Pipeline Stage 1 — SAST (Semgrep, CodeQL, Bandit) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ CI Pipeline Stage 2 — SCA (Dependabot, Snyk, OWASP Dependency) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ CI Pipeline Stage 3 — Container Scan (Trivy, Grype, Docker Scout)│ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ CI Pipeline Stage 4 — IaC Security (Checkov, tfsec, Terrascan) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ CD Gate — Policy-as-Code Admission (OPA/Conftest, Kyverno) │ └─────────────────────────────────────────────────────────────────┘ │ ▼ Production1. Pre-Commit: Stop Secrets Before They're Committed Secrets committed to Git are notoriously difficult to fully purge — git history persists, and secret scanning services may have already indexed the exposure within minutes of a push. Install pre-commit with Gitleaks to block secrets at the developer workstation: # .pre-commit-config.yaml repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.18.4 hooks: - id: gitleakspre-commit install pre-commit run --all-filesIn CI, run Gitleaks on every pull request: # GitHub Actions - name: Detect Secrets uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}2. SAST: Code-Level Security Analysis with Semgrep Semgrep provides language-aware static analysis with rules tuned for security vulnerabilities — SQL injection, path traversal, insecure deserialization, and hundreds of framework-specific checks. # .github/workflows/sast.yml - name: Run Semgrep SAST uses: returntocorp/semgrep-action@v1 with: config: > p/owasp-top-ten p/kubernetes p/terraform p/jwt env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}Customize rules for organization-specific patterns — for example, detecting use of deprecated internal auth libraries: # semgrep rule: detect use of deprecated auth module rules: - id: use-of-deprecated-auth-lib patterns: - pattern: import legacy_auth message: "legacy_auth is deprecated and insecure. Use gurkhax_auth v3+ instead." languages: [python] severity: ERROR3. SCA: Dependency Vulnerability Management Third-party libraries account for ~80% of modern application code. Software Composition Analysis (SCA) identifies known CVEs in your dependency tree. Recommended stack:Snyk — real-time CVE monitoring with fix PRs OWASP Dependency-Check — open-source, integrates with Maven, Gradle, npm, pip GitHub Dependabot — automated security PRs, free for public and private repos# dependabot.yml version: 2 updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "daily" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] # Only block on minor+ security updatesGate merges on CVSS score: block PRs introducing CVEs with CVSS ≥ 7.0 in direct dependencies.4. IaC Security: Terraform and Helm Chart Scanning Infrastructure-as-Code misconfigurations are production security incidents waiting to happen — public S3 buckets, unrestricted security groups, unencrypted EBS volumes. Checkov scans Terraform, CloudFormation, Kubernetes manifests, and Helm charts: checkov -d ./terraform --framework terraform \ --check CKV_AWS_20,CKV_AWS_57,CKV_AWS_119 \ --output junitxml > checkov-results.xmlIntegrate as a required CI check: - name: IaC Security Scan run: | pip install checkov checkov -d ./infra --soft-fail-on MEDIUM \ --hard-fail-on HIGH,CRITICAL5. Policy-as-Code: OPA/Conftest Admission Gates Open Policy Agent (OPA) with Conftest enforces security policies on Kubernetes manifests before deployment: # policy/deny-privileged.rego package maindeny[msg] { input.kind == "Pod" container := input.spec.containers[_] container.securityContext.privileged == true msg := sprintf("Container '%v' is running as privileged — deny", [container.name]) }deny[msg] { input.kind == "Pod" not input.spec.securityContext.runAsNonRoot msg := "Pod must enforce runAsNonRoot" }conftest test kubernetes/deployment.yaml --policy policy/Gate deployments in CD pipelines: - name: Enforce Kubernetes Policies run: conftest test k8s/ --policy opa-policies/ --fail-on-warnThreat Model Integration: STRIDE in Sprint Ceremonies Automation handles what's known. Threat modeling handles what's emergent. Run lightweight STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) sessions as part of feature design reviews:Data flow diagrams — map all data paths, trust boundaries, and external integrations STRIDE per element — identify threats against each data store, process, and flow Risk-rated backlog items — convert threats into security stories with CVSS-calibrated priority Automated regression — encode accepted threat mitigations as Semgrep/OPA rulesDevSecOps Maturity Checklist Pre-commit hooks block secrets and obvious vulnerabilities SAST runs on every PR with severity-gated merge blocking SCA monitors all dependency trees with automated fix PRs Container images scanned — no critical CVEs in base images IaC scanning enforced for all Terraform and Helm changes OPA/Conftest policies enforced at CD admission Threat modeling embedded in sprint design ceremonies Security findings tracked in issue tracker with SLAGurkhaX implements end-to-end DevSecOps pipelines and conducts AppSec maturity assessments for engineering organizations. Engage our team to build your security-first delivery pipeline.

Read IntelPOST #02