Authorization Controls and Secure File Handling — ASVS 5.0 V8 & V5
    Application Security

    Authorization Controls and Secure File Handling — ASVS 5.0 V8 & V5

    IDOR, forced browsing, privilege escalation, and upload security — what OWASP ASVS 5.0 requires for authorization enforcement and file handling.

    Reverse PolarityApril 25, 202610 min read

    Two chapters in OWASP ASVS 5.0 that are easy to underestimate: V8 (Authorization) and V5 (File and Resource Upload Handling). Neither generates the same volume of conference talks as SQL injection or deserialization, but both produce a disproportionate share of real-world incidents in multi-tenant SaaS applications. This article covers what ASVS 5.0 actually requires in each area, why the requirements are structured the way they are, and where the two chapters intersect.

    Diagram — ASVS 5.0 — Authorization & File Handling
    Diagram

    Authentication vs. Authorization: Why ASVS Keeps Them Separate

    Authentication answers: who are you? Authorization answers: what are you allowed to do? They are distinct problems with distinct failure modes, and ASVS 5.0 treats them in entirely separate chapters — V6 for authentication, V8 for authorization — deliberately.

    The separation matters in practice because the two controls fail independently. A system can have strong authentication (MFA, phishing-resistant credentials) and still be completely broken at the authorization layer if every authenticated user can access every resource. Conversely, a system can have well-scoped authorization rules that are never actually enforced because the enforcement code lives in a client-side JavaScript guard that an attacker simply bypasses.

    ASVS 5.0's control objective for V8 is precise: access must be granted only to permitted consumers, and that permission must be derived from explicitly documented entitlements — not inferred, not assumed from the fact of authentication.


    V8 Authorization: What ASVS Requires

    Documentation First

    Requirement 8.1.1 (Level 1) mandates that authorization rules be documented before implementation can be meaningfully verified. Specifically: rules for restricting function-level and data-specific access based on consumer permissions and resource attributes. Level 2 extends this to field-level access restrictions — including both read and write rules, and noting that rules may depend on other attributes of the data object (e.g., a record's status field may affect who can edit it).

    This documentation-first approach runs throughout ASVS 5.0. The reasoning is practical: a code review cannot confirm that authorization is correctly implemented if no one has written down what "correct" looks like.

    Function-Level, Data-Level, and Field-Level Controls

    ASVS 5.0 breaks authorization into three layers:

    • Function-level (8.2.1, Level 1): Can this user invoke this operation at all? A standard RBAC check — does the user's role include the invoices:write permission?
    • Data-level (8.2.2, Level 1): Can this user access this specific object? This is where IDOR and BOLA (Broken Object Level Authorization) live.
    • Field-level (8.2.3, Level 2): Even if the user can see the record, can they see or modify this specific field? A customer service agent might be permitted to view an order but not the stored payment method details on the same record.

    Each layer is independently bypassable. Enforcing only function-level access leaves data-level wide open; enforcing only data-level access ignores the risk that an API might return fields the consumer should never see.

    Vertical and Horizontal Privilege Escalation

    Vertical escalation is the classic attack: a regular user gaining access to functions or data reserved for a higher-privilege role. A POST /admin/users/promote endpoint that checks authentication but not whether the caller is an admin is a textbook example.

    Horizontal escalation is less visually dramatic but equally damaging in multi-tenant systems: a user accessing another user's data within the same privilege tier. Both map directly to 8.2.2 — the requirement that data-specific access be restricted to consumers with explicit permissions to specific data items.

    A concrete pattern that causes horizontal escalation:

    # Vulnerable: uses user-supplied ID without ownership check
    invoice = Invoice.objects.get(id=request.data["invoice_id"])
    return InvoiceSerializer(invoice).data
    
    # Correct: scopes the query to the authenticated user's tenant
    invoice = Invoice.objects.get(
        id=request.data["invoice_id"],
        tenant_id=request.user.tenant_id
    )
    return InvoiceSerializer(invoice).data
    

    The fix isn't complicated. The failure mode is usually that the check was never written, or was present in one endpoint and not in the endpoint added six months later.

    Forced Browsing and IDOR

    IDOR (Insecure Direct Object Reference) is the specific instantiation of broken data-level authorization where the object reference is directly manipulable by the user — a numeric ID in a URL, a filename in a query parameter, a UUID in a request body. ASVS 5.0 names IDOR and BOLA explicitly in requirement 8.2.2.

    Forced browsing is the broader technique: incrementing IDs, guessing predictable paths, or walking through a known URL pattern without authorization checks. Both attacks succeed when the application conflates "the user knows the identifier" with "the user is permitted to access the resource."

    Switching from sequential integers to UUIDs reduces guessability but does not fix the authorization problem. If the check does this user own this object? is absent, an attacker who learns a UUID — from a shared link, from a logging leak, from another API response — can still access the resource. ASVS 5.0's requirement is about explicit permission enforcement, not about obscuring identifiers.

    Centralized Authorization Enforcement

    Requirement 8.3.1 (Level 1) is blunt: authorization must be enforced at a trusted service layer and must not rely on controls that an untrusted consumer could manipulate, such as client-side JavaScript.

    This rules out patterns like:

    // Client-side "authorization" — not a security control
    if (user.role === 'admin') {
        showDeleteButton();
    }
    

    The delete operation on the server must check the caller's role independently of what the frontend sent. Hiding a button is a UX decision, not an authorization control.

    The deeper architectural requirement is centralization. Scattered per-endpoint authorization checks are harder to audit and easier to miss. ASVS 5.0 pushes toward a pattern where authorization decisions flow through a single trusted layer — a policy engine, an authorization middleware, or a dedicated service — rather than being re-implemented at every controller.

    Requirement 8.3.3 (Level 3) addresses service-to-service scenarios: when service A calls service B on behalf of a user, service B must make authorization decisions using the user's token, not a machine-to-machine token from service A. Without this, service A's broad permissions can silently expand what the user is permitted to do.

    Authorization Changes and Immediate Effect

    Requirement 8.3.2 (Level 3) requires that authorization changes take effect immediately. The scenario: a user's role is revoked, but they hold a valid JWT with a 1-hour lifetime. For the next hour they continue to pass authorization checks because the token still carries the old claims.

    Where immediate revocation is impossible (common with self-contained tokens), ASVS requires mitigating controls: detecting when a consumer performs a post-revocation action and reverting it. The standard notes this alternative does not mitigate information leakage — a briefly valid token can still be used to read data even if write operations are caught and reversed.

    Multi-Tenant Isolation

    Requirement 8.4.1 (Level 2) is specifically relevant for SaaS: multi-tenant applications must enforce cross-tenant controls ensuring that no consumer operation affects tenants with which they do not have permissions to interact. This is a higher-level restatement of data-level authorization applied to the tenant boundary — and it must be verified as a distinct concern, not assumed to follow automatically from other access controls.


    V5 File Handling: Upload and Download Security

    The Documentation Requirement

    Before any upload validation can be meaningfully tested, 5.1.1 (Level 2) requires that the application document permitted file types, expected extensions, maximum sizes (including unpacked size for archives), and the behavior when a malicious file is detected. The unpacked size clause matters — a 1 MB zip file can expand to gigabytes.

    File Type Validation: Extensions Are Not Enough

    Requirement 5.2.2 (Level 1 for security-critical files; Level 2 for all files) mandates validating both the file extension and the file content. The content validation methods named explicitly: checking magic bytes, performing image re-writing, using specialized validation libraries.

    Magic bytes are the first few bytes of a file that identify its format. A file named invoice.pdf with a magic byte signature of FF D8 FF is actually a JPEG. An attacker uploading a PHP script named invoice.pdf with Content-Type: application/pdf passes an extension check trivially — it does not pass a magic byte check.

    import magic
    
    def validate_upload(file_bytes: bytes, claimed_extension: str) -> bool:
        detected_type = magic.from_buffer(file_bytes, mime=True)
        allowed_types = {
            "pdf": "application/pdf",
            "png": "image/png",
            "jpg": "image/jpeg",
        }
        expected = allowed_types.get(claimed_extension.lower())
        return expected is not None and detected_type == expected
    

    Image re-writing is an alternative for image uploads: the application discards the original and reconstructs the image from decoded pixel data. The result cannot contain embedded scripts or polyglot payloads.

    Compressed archives require separate handling (5.2.3, Level 2): check uncompressed size and file count before extraction, not after. Zip bombs — where a small archive decompresses to hundreds of gigabytes — exploit the gap between "accepted the upload" and "processed the contents."

    Storage Location

    Requirement 5.3.1 (Level 1) is simple to state and frequently violated: files uploaded by untrusted users, stored in a public web folder, must not be executable by the server when accessed via HTTP.

    The failure mode: an application stores uploads in public/uploads/ on a PHP server without disabling PHP execution for that directory. An attacker uploads a file containing PHP code and accesses it directly via HTTP — the web server executes it.

    Defenses include:

    • Storing uploads outside the web root entirely
    • Disabling script execution for the upload directory via web server configuration
    • Serving upload content through an application-controlled endpoint rather than directly

    Requirement 5.3.2 (Level 1) addresses path traversal at the storage layer: file paths for storage operations must be constructed from internally generated or trusted data. If user-supplied filenames must be used, strict validation and sanitization are required. The threat model includes both local path traversal (../../etc/passwd) and zip-slip attacks during archive extraction — requirement 5.3.3 (Level 3) specifically requires server-side decompression to ignore user-provided path information embedded in archive entries.

    Serving Uploaded Files Safely

    Requirements 5.4.1 and 5.4.2 (both Level 2) address the download direction. User-supplied filenames in URL parameters or JSON request bodies must be validated or ignored; the server must always specify the filename via the Content-Disposition header. Filenames in response headers must be encoded or sanitized per RFC 6266 to prevent header injection.

    The standard pattern for a file download endpoint:

    Content-Disposition: attachment; filename="report-2026-06.pdf"
    Content-Type: application/pdf
    X-Content-Type-Options: nosniff
    

    Content-Disposition: attachment tells the browser to download the file rather than render it. X-Content-Type-Options: nosniff prevents MIME-type sniffing. Serving user-uploaded content from a separate domain or subdomain provides additional isolation — a script that executes in the context of uploads.example.com cannot access cookies or storage from app.example.com.

    Malware Scanning

    Requirement 5.4.3 (Level 2) requires that files obtained from untrusted sources be scanned by antivirus tooling before being served to users. This is not a Level 3 exotic requirement — it kicks in at Level 2, which ASVS characterizes as standard security practice for most applications.

    "Antivirus scanning" here means integration with a scanning service at upload time, not just at the perimeter. Files that fail should be quarantined rather than deleted — retention supports incident investigation. The behavior on detection should be documented per 5.1.1: silent failure, user-facing error, or alert.

    Per-User Storage Quotas and Zip Bombs

    Requirements 5.2.4 (Level 3) and 5.2.6 (Level 3) address denial-of-service through legitimate-looking uploads. A per-user file size quota and maximum file count prevent a single tenant from exhausting shared storage. Image pixel dimension limits prevent pixel flood attacks — where a small compressed image file expands to an enormous bitmap in memory when decoded.

    These are Level 3 requirements, but in multi-tenant SaaS they often belong in the architecture from the start — retrofitting storage quotas into a schema that doesn't have them is substantially more disruptive than designing for them initially.


    Where V8 and V5 Connect

    Authorization (V8) and file handling (V5) are in separate chapters because they address distinct problems, but in any application that lets users upload and retrieve files, they compose directly.

    Consider a document management feature in a multi-tenant SaaS application:

    1. Can this user upload to this folder? — V8.2.1, function-level authorization.
    2. Is this upload valid? — V5.2.2, content validation.
    3. Where should the file be stored? — V5.3.1, V5.3.2, storage location and path handling.
    4. Can this user download this specific file? — V8.2.2, data-level authorization. The file's identifier in the download URL is a direct object reference; ownership must be checked.
    5. Can this user access this tenant's files at all? — V8.4.1, cross-tenant isolation.
    6. Is the file safe to serve? — V5.4.3, malware scanning; V5.4.1, Content-Disposition header.
    7. What fields in the file metadata can this user read? — V8.2.3, field-level access. Internal metadata fields (storage path, processing status, scan results) may not be appropriate to expose even to the file's owner.

    Each step is independently enforceable and independently bypassable. An application that enforces upload validation (V5) but skips the ownership check on downloads (V8.2.2) has a path traversal-equivalent vulnerability at the authorization layer: a user who knows or guesses a file identifier can retrieve any file in the system. An application that enforces ownership checks but skips content validation accepts malicious files and serves them faithfully to the authorized user — and potentially to anyone that user shares a link with.

    Every file download endpoint is also an authorization endpoint. The V5 requirements define how to handle the file safely; the V8 requirements define who is permitted to see it.


    Summary of Key Requirements by Level

    Req Description Level
    8.2.1 Function-level access enforcement 1
    8.2.2 Data-level access enforcement (IDOR/BOLA) 1
    8.3.1 Authorization enforced at trusted server layer 1
    8.4.1 Cross-tenant isolation in multi-tenant apps 2
    8.2.3 Field-level access enforcement (BOPLA) 2
    5.2.2 Magic bytes + extension validation (security-critical files) 1
    5.3.1 Uploaded files not executable in web root 1
    5.3.2 Path traversal prevention in storage 1
    5.2.3 Zip bomb protection (uncompressed size check before extraction) 2
    5.4.1 Content-Disposition header on file downloads 2
    5.4.3 Antivirus scanning of untrusted files 2
    5.2.4 Per-user storage quotas 3
    8.3.3 Downstream services use user token, not service token 3

    ASVS 5.0 makes Level 2 the target for most production applications. At that level, the file handling requirements — content validation, zip bomb protection, malware scanning, safe download headers — are all active, as are data-level and field-level authorization. When assessing a SaaS application against Level 2, both V5 and V8 require substantial coverage, and the integration points between them are worth treating as a distinct attack surface.

    More Articles