Broken Function Level Authorization (BFLA) in APIs: The Complete Guide to Detection and Prevention

Broken Function Level Authorization (BFLA) in APIs: The Complete Guide to Detection and Prevention

Let me tell you about one of the sneakiest API vulnerabilities out there: Broken Function Level Authorization, or BFLA. This is officially listed as API5:2023 in the OWASP API Security Top 10, and I consider it one of the most damaging because it often lets a normal user act like an administrator — just by knowing the right URL.

What Is Broken Function Level Authorization?

Every API has different “functions” — some are meant for regular users, and some are meant only for admins or specific roles. Examples of admin-only functions:

  • Deleting a user account
  • Changing another user’s role
  • Viewing internal analytics
  • Approving or rejecting refunds
  • Accessing an internal debug or export tool

BFLA happens when the API checks that you are logged in (authentication) but forgets to check whether you are allowed to use that specific function (authorization).

I like to explain the difference like this:

  • Authentication = “Who are you?”
  • Authorization = “What are you allowed to do?”

BFLA is when an API answers the first question but skips the second one for certain functions.

How This Is Different From Broken Object Level Authorization (BOLA)

People often confuse BFLA with BOLA (Broken Object Level Authorization), so let me clarify:

  • BOLA is about data — can I access someone else’s object, like User B’s order, User B’s profile, or User B’s invoice, using my own valid session?
  • BFLA is about functionality — can I call an action or endpoint that I should not have permission to call at all, like an admin-only “delete user” or “promote to admin” function?

Both are authorization failures, but BOLA is about whose data, and BFLA is about which actions.

How BFLA Happens in Real APIs

1. Hidden Admin Endpoints That “Rely on Obscurity”

Developers sometimes assume that if an endpoint isn’t shown in the regular app UI, no one will find it. But APIs are discoverable through browser dev tools, mobile app decompilation, JavaScript files, or simply guessing URL patterns like /api/admin/users.

2. Missing Role Checks in Code

The endpoint exists, authentication middleware confirms the user is logged in, but the actual authorization check (“is this user an admin?”) was never written into that specific controller or function.

3. Inconsistent Authorization Across HTTP Methods

An API might correctly protect GET /api/users/{id} but forget to protect DELETE /api/users/{id} — same resource, different HTTP verb, different (missing) protection.

4. Client-Side-Only Restrictions

The mobile app or website simply hides the admin button from regular users, but the backend endpoint itself has no server-side check. Anyone who calls the API directly (bypassing the UI) can trigger the admin action.

5. Role Confusion in Multi-Tenant Systems

In systems with multiple organizations or tenants, a user who is an admin within their own company might accidentally be able to call functions meant for a global super-admin, because the code checks “is admin” without checking “admin of what scope.”

6. Versioned or Legacy Endpoints

An old version of an endpoint (/api/v1/admin/...) might still be live and lack authorization checks that were added later to the newer version (/api/v2/admin/...).

A Realistic Example

Imagine an HR management API. A regular employee logs in and can call:

GET /api/v1/employees/me

This returns their own profile — totally fine. But the API also has:

PUT /api/v1/employees/{id}/salary

If the backend only checks “is this a valid logged-in user token?” and never checks “is this user in the HR/admin role?”, then any employee could send:

PUT /api/v1/employees/104/salary
{ "salary": 500000 }

…and give themselves — or anyone else — a raise, simply because the function-level check was missing.

Another example: a SaaS platform with:

POST /api/v1/admin/export-all-users

If this endpoint doesn’t verify the caller’s role server-side, a regular free-tier user could call it directly and download the entire user database.

The Many Forms BFLA Can Take

Let me expand this into the specific patterns I look for when testing:

Horizontal Function Escalation

A user with one type of role calls a function meant for a different type of role at the same “level” — for example, a support agent calling a billing team’s refund-approval endpoint.

Vertical Function Escalation

A low-privilege user (regular user) calls a function meant for a higher-privilege role (admin) — the classic and most dangerous form.

Method-Based Bypass

The GET request is protected, but POST, PUT, PATCH, or DELETE on the same route is not.

Legacy Endpoint Bypass

Old, undocumented, or “deprecated” endpoints still work but were never updated with the current authorization logic.

Parameter-Based Privilege Change

Some APIs let you pass a role or isAdmin parameter in the request body during profile updates. If the server blindly trusts this (this actually overlaps with Mass Assignment too), a user can just set "role": "admin" themselves.

UI-Hidden but API-Reachable Functions

Functions that exist in the backend and are reachable via API calls, but are simply not shown as buttons in the frontend for regular users.

How to Test for BFLA

Here is my step-by-step testing approach:

  1. Map out every function/endpoint in the API, including ones only visible to admin accounts, using tools like Burp Suite, Postman, or by inspecting mobile app traffic.
  2. Create two test accounts — one low-privilege (regular user) and one high-privilege (admin).
  3. Capture requests made by the admin account for admin-only functions.
  4. Replay those exact requests using the low-privilege user’s token/session instead of the admin’s.
  5. Check the response — if you get a 200 OK and the action actually happens (not just a fake success message), that’s BFLA.
  6. Test every HTTP method on each endpoint (GET, POST, PUT, PATCH, DELETE), not just the one the UI uses.
  7. Try old API versions (/v1/, /v2/) and any endpoints found in JavaScript files, mobile app strings, or API documentation/Swagger files.
  8. Test cross-tenant access if the system has multiple organizations — can an admin from Company A affect Company B?

How to Fix Broken Function Level Authorization

1. Centralize Authorization Logic

Don’t scatter “if user.role == admin” checks across every controller. Use a centralized authorization layer or middleware (like a policy engine, RBAC library, or something like Open Policy Agent) so every endpoint automatically goes through the same check.

2. Deny by Default

Design your system so that access is denied unless explicitly granted, rather than allowed unless explicitly denied. New endpoints should be locked down by default.

3. Enforce Role Checks Server-Side, Always

Never trust the frontend to hide a button as your only line of defense. Every sensitive function must re-verify permissions on the backend, every single time it’s called.

4. Use Role-Based Access Control (RBAC) or Attribute-Based Access Control (ABAC)

  • RBAC — permissions tied to roles (admin, editor, viewer).
  • ABAC — permissions based on attributes (department, tenant, resource ownership), which is more flexible for complex multi-tenant systems.

5. Apply Authorization Checks Consistently Across All HTTP Methods

If GET /resource/{id} is protected, make sure PUT, PATCH, and DELETE on the same resource path go through identical checks.

6. Remove or Properly Secure Legacy Endpoints

Audit old API versions regularly. If they’re no longer needed, retire them. If they must stay, apply the same authorization standards as the current version.

7. Write Automated Authorization Tests

Add tests to your CI/CD pipeline that specifically try to access admin functions with a non-admin token and expect a 403 Forbidden. This catches regressions before they reach production.

8. Log and Monitor Privilege Escalation Attempts

Track failed authorization attempts (repeated 403s on sensitive endpoints) so your security team can spot exploitation attempts early.

Business Impact of BFLA

  • Full account or data compromise — attackers can promote themselves to admin.
  • Financial fraud — unauthorized refunds, discount codes, or salary changes.
  • Regulatory violations — unauthorized access to sensitive data can breach GDPR, HIPAA, or other compliance frameworks.
  • Total system takeover — in the worst cases, BFLA on a “create admin user” endpoint can hand an attacker full control of the platform.

Final Thoughts

BFLA is dangerous precisely because it’s invisible from the outside until someone actually tests it. A well-designed UI can completely hide the problem while the underlying API remains wide open. The fix isn’t complicated in theory — check permissions on every function, every time — but it requires discipline across the entire codebase, especially as APIs grow and more endpoints get added over time.

Total
1
Shares

Leave a Reply

Previous Post
Mass Assignment Vulnerabilities in APIs: How Attackers Exploit Auto-Binding and How to Stop Them

Mass Assignment Vulnerabilities in APIs: How Attackers Exploit Auto-Binding and How to Stop Them

Next Post
# API Rate Limiting Explained: How Lack of Resource Controls Leads to DoS Attacks and Server Overload *A complete, beginner-to-advanced guide on Unrestricted Resource Consumption (OWASP API4:2023)* I want to talk about something that sounds boring on the surface but is one of the easiest ways to take down an entire API: **lack of resources and rate limiting**. This is officially known in the OWASP API Security Top 10 as **API4:2023 – Unrestricted Resource Consumption**, and I have seen it break production systems more times than I can count. If you build, secure, or test APIs, this is a topic you cannot skip. Let me break it down the way I wish someone had explained it to me the first time. ## What "Lack of Resources and Rate Limiting" Actually Means Every request that hits an API costs something. It costs CPU time, memory, database connections, bandwidth, and sometimes money (think of API calls to a paid third-party service like an SMS gateway or an AI model). When an API does not put a limit on how many requests a client can make, or how large a request can be, or how long a request can run, an attacker — or even just a careless user — can consume all of that capacity. The result is simple: the server slows down, runs out of memory, hits its database connection pool limit, or racks up a massive bill. This is called **resource exhaustion**, and when it is done on purpose, it is called a **Denial of Service (DoS)** attack. I like to think of it like a restaurant with no reservation system and no limit on how many dishes one table can order. One customer could order 500 plates of food, tie up the entire kitchen, and every other customer would starve while waiting. ## Why This Happens: The Root Causes Let me walk through the actual reasons this vulnerability shows up in real APIs. ### 1. No Rate Limiting on Endpoints This is the most common cause. A login endpoint, a search endpoint, a password reset endpoint, or a report-generation endpoint accepts unlimited requests per second from a single IP address, user account, or API key. ### 2. No Limit on Payload Size An API accepts a JSON body, a file upload, or an array in a request without checking its size. Someone sends a 2GB JSON file, or an array with 10 million items, and the server tries to parse the whole thing into memory at once. ### 3. No Limit on Response Size or Pagination An endpoint like `/api/users` returns every single user in the database in one response instead of paginating results. If there are 5 million users, that single request can crash the server or the client. ### 4. Expensive Operations Without Throttling Some endpoints are naturally expensive — generating a PDF report, running a complex database query with joins, resizing an image, or calling a third-party AI model. If these are not throttled per user, one person can call them repeatedly and burn through server resources or your budget. ### 5. No Timeout on Long-Running Requests If a request can run forever — for example, a regex that takes exponential time to evaluate (a classic **ReDoS**, Regular Expression Denial of Service) — the server thread or process gets stuck, and enough stuck threads mean the whole server stops responding. ### 6. Missing Limits on Array or Batch Operations Many APIs allow batch operations like `POST /orders/bulk` where you can submit multiple orders in one call. Without a cap on how many items can be in that batch, one request can effectively become thousands of database writes. ### 7. No Cost Controls on Third-Party API Usage If your backend calls an external paid API (like a mapping service, an AI model, or an SMS provider) on behalf of a user, and there's no per-user quota, an attacker can spam your endpoint and drain your budget in minutes. This is sometimes called a **Denial of Wallet** attack. ## Real-World Style Example Imagine a food delivery API with this endpoint: ``` POST /api/v1/restaurants/search { "latitude": 33.6, "longitude": 73.0, "radius_km": 5000 } ``` Nothing stops the client from setting `radius_km` to 5000 instead of 5. Now the query has to scan almost the entire restaurants table. If ten thousand attackers send this same request at once, the database grinds to a halt, and legitimate customers cannot even open the app. Another classic case: a `/api/export-report` endpoint that generates a PDF of a user's entire transaction history. If it has no rate limit, an attacker can hit it 200 times per second, and the server keeps spinning up 200 PDF-generation processes simultaneously, consuming all available memory. ## The Many Faces of Resource Exhaustion Attacks I want to break this vulnerability further into all the sub-attack types that fall under this umbrella, because "rate limiting" is really an umbrella term. ### Volumetric Attacks Simply sending a huge number of requests in a short time to overwhelm the server. This is the most basic form of DoS. ### Slow Request Attacks (Slowloris style) Instead of sending many requests fast, the attacker opens many connections and sends data very slowly, keeping connections open and exhausting the server's connection pool. ### Large Payload Attacks Sending oversized JSON bodies, huge file uploads, or deeply nested JSON objects (which can cause a stack overflow when parsed recursively). ### Algorithmic Complexity Attacks Exploiting an endpoint whose processing time grows exponentially with input size — like a poorly written regex, or a sorting algorithm with a worst-case scenario triggered by crafted input. ### Resource-Intensive Query Attacks Abusing search, filter, or reporting endpoints that allow flexible queries (like GraphQL) to request deeply nested or extremely broad data in one call. ### Brute Force Attacks Rate limiting isn't just about server load — it's also about security. Without rate limiting on a login or OTP verification endpoint, attackers can try thousands of password or OTP combinations per second. ### Distributed Resource Exhaustion Instead of one IP hammering an endpoint, an attacker uses thousands of IPs (a botnet) to spread the requests so IP-based rate limiting doesn't catch it. ## How to Detect This Vulnerability When I test an API for this issue, here's my checklist: 1. **Send rapid repeated requests** to the same endpoint and check if you ever get throttled (HTTP 429 Too Many Requests). 2. **Check response headers** for rate-limit indicators like `X-RateLimit-Limit`, `X-RateLimit-Remaining`, or `Retry-After`. Their absence is often a red flag. 3. **Send an oversized payload** — a huge JSON body or a large file — and see if it's rejected before being processed. 4. **Try pagination bypass** — request a huge `limit` or `page_size` parameter and see if the API just returns everything. 5. **Test batch endpoints** with thousands of items in one array. 6. **Check for timeouts** — send a request designed to take a long time and see if the server ever cuts it off. 7. **Look at authentication endpoints** specifically — login, signup, OTP, and password reset are prime brute-force targets. ## How to Fix It: Practical Prevention Techniques Now let's get into how I would actually fix this in a real system. ### 1. Implement Rate Limiting at Multiple Levels - **Per IP address** — basic but easily bypassed with proxies. - **Per user account / API key** — much more reliable. - **Per endpoint** — sensitive endpoints like login need stricter limits than a public product listing endpoint. Common algorithms: - **Fixed Window** — simple counter reset every X seconds. - **Sliding Window** — smoother, avoids bursts right at window boundaries. - **Token Bucket** — allows small bursts but enforces an average rate over time. - **Leaky Bucket** — similar to token bucket but smooths output more strictly. ### 2. Set Maximum Payload Sizes Configure your web server (Nginx, API gateway, or framework middleware) to reject requests above a certain size before your application code even touches them. ### 3. Enforce Pagination Never allow unbounded `GET` list endpoints. Always cap `limit`/`page_size` to a sane maximum (e.g., 100) regardless of what the client requests. ### 4. Add Timeouts Everywhere Set timeouts on database queries, external API calls, and the overall request lifecycle. A request that takes too long should be killed, not left running forever. ### 5. Limit Batch Operation Sizes If your API supports bulk creation or bulk updates, cap the number of items per request (e.g., max 50 items per batch call). ### 6. Use an API Gateway Tools like Kong, AWS API Gateway, Apigee, or NGINX with rate-limiting modules can enforce limits before traffic even reaches your backend. ### 7. Apply Quotas for Costly Operations For anything that calls a paid third-party service or does heavy computation, track usage per user and enforce a daily/monthly quota, not just a per-second rate limit. ### 8. Protect Against ReDoS Avoid writing regex patterns vulnerable to catastrophic backtracking. Use regex engines with timeout protection, or validate input length before running regex on it. ### 9. Add CAPTCHA or Progressive Delays on Sensitive Endpoints For login and OTP endpoints, after a few failed attempts, introduce increasing delays or require a CAPTCHA. ### 10. Monitor and Alert Set up monitoring for sudden spikes in traffic, error rates, or resource usage so you can react before a full outage happens. ## A Simple Rate Limiting Example (Conceptual) Here's a very simplified idea of a token bucket check in pseudocode, just to show the logic: ``` function isAllowed(userId): bucket = getBucket(userId) refillBucket(bucket) // add tokens based on time passed if bucket.tokens >= 1: bucket.tokens -= 1 return true else: return false // reject with 429 Too Many Requests ``` This kind of logic, combined with a fast in-memory store like Redis, is how most production rate limiters work at scale. ## Business Impact If Ignored - **Downtime** — your whole service becomes unavailable for real users. - **Financial loss** — if you pay per API call to a third-party service, an attacker can generate a massive bill. - **Reputation damage** — customers lose trust in a service that frequently goes down. - **Security risk multiplier** — lack of rate limiting on login/OTP endpoints directly enables account takeover through brute force. ## Final Thoughts Rate limiting and resource control are not optional extras — they are core infrastructure for any API that faces the public internet. I always tell people: assume every endpoint you expose will eventually be hit by an automated script sending thousands of requests per second, because eventually, it will be. Build your defenses before that day comes, not after. I write more deep-dive security breakdowns like this one on my blog at [awjunaid.com](https://awjunaid.com/), and I share practical code and testing scripts on my [GitHub profile](https://github.com/aw-junaid/). Feel free to explore both if you want to go further with hands-on examples.

API Rate Limiting Explained: How Lack of Resource Controls Leads to DoS Attacks and Server Overload

Related Posts