Auth Bypass in Rest APIs
How Auth Bypass Manifests in REST
In REST APIs, authentication bypass typically manifests as Broken Object Level Authorization (BOLA), formerly known as IDOR. This occurs when an API endpoint uses client-supplied identifiers (like path parameters, query strings, or request body fields) to access objects without verifying that the authenticated user has permission to access that specific object. REST's resource-oriented design, where each resource is uniquely addressable via a URI (e.g., /api/users/{userId}), creates a direct mapping that, if unprotected, allows horizontal privilege escalation.
Common REST-specific attack patterns include:
- Parameter Tampering: An attacker modifies a predictable identifier (e.g., incrementing a numeric ID, guessing a UUID pattern) to access another user's data. For example, changing
/api/orders/1001to/api/orders/1002. - Method-Based Bypass: Exploiting differing authorization logic across HTTP methods. An API might enforce strict checks on
POSTbut neglect them onPUTorPATCH, allowing an attacker to update another user's resource by sending aPUT /api/users/{targetUserId}with their own valid session. - Location-Based Bypass: Using alternate parameter locations. An endpoint might check authorization only on a path parameter (
/api/users/{userId}/profile) but not on a query parameter (/api/profile?userId={targetUserId}). - Batch Endpoint Abuse: APIs that accept arrays of object IDs (e.g.,
/api/batch?ids=1,2,3) may process each without individual authorization checks, enabling mass data exfiltration.
A vulnerable REST endpoint in Node.js/Express might look like this:
// VULNERABLE: No ownership check
app.get('/api/users/:userId/profile', authenticate, (req, res) => {
const profile = db.getUserProfile(req.params.userId);
res.json(profile);
});
// ATTACK: Authenticated user with userId=5 calls
// GET /api/users/6/profile → receives user 6's dataHere, the authenticate middleware validates the session but does not confirm that req.params.userId matches the authenticated user's ID. The resource identifier is trusted entirely from the request.
REST-Specific Detection
Detecting BOLA in REST requires testing each endpoint's handling of object identifiers across all HTTP methods and parameter locations (path, query, body, headers). A scanner must simulate authenticated sessions (if available) and systematically mutate identifiers to probe for unauthorized access.
middleBrick's approach: During a black-box scan, middleBrick's BOLA/IDOR check performs the following:
- Identifies all endpoints and their parameterized paths from the OpenAPI spec (if provided) or by crawling.
- For each endpoint that accepts an object identifier, it sends sequential requests with altered identifiers (e.g., numeric increment, UUID format changes, known test values) while maintaining the same authentication context.
- Compares responses for differences in status codes, response bodies, or error messages that indicate access to a different object. A
200 OKwith different data, versus a403/404for the same endpoint with the original identifier, signals a potential bypass. - Checks for inconsistent authorization across HTTP methods on the same resource path.
The scanner does not require credentials but can optionally use them to test the authenticated attack surface. Without credentials, it focuses on unauthenticated BOLA (e.g., endpoints that leak data without any auth).
Example scan output snippet from middleBrick's report:
| Endpoint | Parameter | Original ID | Test ID | Status | Finding |
|---|---|---|---|---|---|
GET /api/orders/{orderId} | path | 1001 | 1002 | 200 | Unauthorized access to order 1002 |
PATCH /api/users/{userId} | path | 5 | 6 | 200 | Method-based BOLA: PATCH allows updating other users |
These findings are scored based on the sensitivity of the exposed data (e.g., PII vs. non-sensitive data) and the ease of exploitation (predictable IDs). The report includes severity ratings (Critical/High/Medium/Low) and maps to OWASP API Top 10:2023 BOLA:2023.
REST-Specific Remediation
Remediation must occur at the application layer, enforcing authorization on every request that accesses a specific object. REST's uniform interface constraint means security cannot rely on the client; the server must validate access for each resource request.
1. Implement Object-Level Authorization Checks: After authentication, verify that the requesting user owns or has a role-based permission to access the target object. This check must happen before any data retrieval or modification.
Example fix in Express.js using a policy-based approach:
const authorizeResource = (resourceModel, userIdParam) => {
return async (req, res, next) => {
const resourceId = req.params[userIdParam] || req.body[userIdParam];
const resource = await resourceModel.findById(resourceId);
if (!resource || resource.ownerId !== req.user.id) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
// Secured endpoint
app.get('/api/users/:userId/profile',
authenticate,
authorizeResource(UserProfile, 'userId'),
(req, res) => {
res.json(req.resource); // resource attached by middleware
}
);2. Use Indirect References: Replace direct database IDs with opaque, unpredictable references (e.g., UUIDs, hashids) that do not allow sequential guessing. While not a complete fix, it increases the attack complexity.
3. Validate All Parameter Locations: Ensure authorization logic runs regardless of where the object identifier is provided (path, query, body, headers). Centralize this check in a middleware that inspects all relevant sources.
4. Enforce Least Privilege per HTTP Method: For each method on a resource path, define and enforce the required permission. A user with read:own should only GET their own resources; a PUT might require write:own.
5. Leverage Framework Security Features: Many REST frameworks offer built-in authorization mechanisms. For example, in Spring Boot (Java), use method security with @PreAuthorize:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PreAuthorize("@userService.canAccessUser(#userId)")
@GetMapping("/{userId}/profile")
public Profile getProfile(@PathVariable String userId) {
return userService.getProfile(userId);
}
}The @userService.canAccessUser custom permission evaluator would check if the current authenticated user matches the userId or has an admin role.
6. Continuous Verification: Integrate automated security scanning into CI/CD pipelines. Using middleBrick's GitHub Action, you can fail a pull request if a new BOLA finding is introduced or if the security score drops below a threshold:
# .github/workflows/api-security.yml
name: API Security Scan
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: middlebrick/github-action@v1
with:
api_url: 'https://api.example.com/openapi.yaml'
fail_below_score: 'B'This ensures BOLA vulnerabilities are caught before deployment. The middleBrick CLI can also be used locally for quick validation: middlebrick scan https://api.example.com.