Bola Idor with Basic Auth
How Bola Idor Manifests in Basic Auth
Basic Authentication's simplicity creates a perfect storm for BOLA/IdOR vulnerabilities. The stateless nature of Basic Auth means credentials are validated once per request, then the server trusts that the authenticated user should have access to whatever resource they request. This trust becomes dangerous when authorization checks are missing or improperly implemented.
Consider a REST API endpoint that retrieves user data:
app.get('/api/users/:id', authenticateBasic, (req, res) => {
const userId = req.params.id;
const user = await db.getUserById(userId);
res.json(user);
});The vulnerability here is subtle but critical: the endpoint authenticates the request using Basic Auth credentials, but never verifies whether the authenticated user actually owns or should access the requested user ID. An attacker can simply change the ID parameter in the URL to access any user's data.
Another common pattern involves collection endpoints that return filtered results based on query parameters:
app.get('/api/users', authenticateBasic, (req, res) => {
const userId = req.query.userId;
const users = await db.getUsers({ ownerId: userId });
res.json(users);
});Here, an attacker can manipulate the userId query parameter to access data belonging to other users, even though they're properly authenticated with Basic Auth.
Resource ownership becomes particularly problematic with Basic Auth because the protocol itself provides no context about user relationships or permissions. The server only knows the username and password are valid, not what the user should be allowed to access.
Multi-tenant applications are especially vulnerable. Consider a SaaS platform where each tenant has their own data:
app.get('/api/tenants/:tenantId/data', authenticateBasic, (req, res) => {
const tenantId = req.params.tenantId;
const data = await db.getTenantData(tenantId);
res.json(data);
});Without proper authorization checks, any authenticated user can access any tenant's data by simply changing the tenantId parameter.
The problem compounds when Basic Auth is used with client-side applications that can easily modify request parameters. Since Basic Auth credentials are sent with every request, there's no additional server-side session context to validate against.
Database queries that use unvalidated parameters are particularly dangerous:
app.get('/api/orders/:orderId', authenticateBasic, async (req, res) => {
const orderId = req.params.orderId;
const order = await db.query(`
SELECT * FROM orders WHERE id = ${orderId}
`);
res.json(order);
});This code authenticates the user but doesn't verify they own the order they're trying to access. An attacker can enumerate order IDs to access any customer's orders.
Even seemingly secure patterns can fail when authorization is assumed rather than explicitly checked. For example:
app.get('/api/profile', authenticateBasic, (req, res) => {
const username = req.user.username;
const profile = await db.getUserProfile(username);
res.json(profile);
});While this appears safer, it still fails if the application allows users to view other profiles through different endpoints or if the username parameter can be manipulated in related API calls.
The fundamental issue is that Basic Auth provides authentication without authorization. The protocol tells you who the user is, but nothing about what they should be allowed to do. This separation of concerns is precisely where BOLA/IdOR vulnerabilities emerge.
Basic Auth-Specific Detection
Detecting BOLA/IdOR vulnerabilities in Basic Auth implementations requires both automated scanning and manual code review. The stateless nature of Basic Auth means traditional session-based detection methods don't apply, requiring specialized approaches.
Automated scanning with middleBrick specifically targets Basic Auth endpoints by testing parameter manipulation across authenticated requests. The scanner systematically modifies resource identifiers in URLs, query parameters, and request bodies to see if unauthorized data access is possible.
For example, when scanning an endpoint like /api/users/123, middleBrick will:
- Test sequential ID values (122, 124, 125) to identify pattern-based access
- Try common ID formats (UUIDs, emails, usernames) if the original ID is one format
- Modify query parameters that might control data filtering or ownership
- Check for inconsistent authorization across similar endpoints
- Test for IDOR in related resources (user orders, settings, preferences)
- Validate that authentication headers are properly scoped to the requested resource
The scanner also examines OpenAPI specifications to identify endpoints that accept user-identifying parameters without proper authorization checks. This spec analysis catches vulnerabilities that might not be triggered during active scanning due to business logic constraints.
Manual detection requires examining code paths where Basic Auth credentials are validated but resource access isn't properly scoped. Key indicators include:
function authenticateBasic(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(401).send('Missing auth');
const [scheme, credentials] = authHeader.split(' ');
if (scheme.toLowerCase() !== 'basic') return res.status(401).send('Invalid scheme');
const decoded = Buffer.from(credentials, 'base64').toString();
const [username, password] = decoded.split(':');
const user = db.getUserByUsername(username);
if (!user || user.password !== password) return res.status(401).send('Invalid credentials');
req.user = user;
next();
}The critical vulnerability in this middleware is that it only validates credentials but doesn't establish any authorization context for subsequent requests.
Testing should also examine how Basic Auth is implemented across different environments. Development, staging, and production often have different user datasets, potentially masking IDOR vulnerabilities that only appear with specific data combinations.
Parameter pollution attacks are particularly relevant for Basic Auth endpoints. Testers should try:
/api/users?id=123&id=456 // Parameter pollution
/api/users/123?format=json&format=xml // Multiple parameters
/api/users/123?debug=true&admin=true // Privilege escalation attemptsRate limiting and authentication retry mechanisms can also reveal authorization weaknesses. If an endpoint allows unlimited authentication attempts but restricts access based on user roles, attackers might use credential stuffing to bypass authorization.
Cross-tenant data access testing is essential for multi-tenant applications using Basic Auth. Verify that tenant boundaries are properly enforced by attempting to access data across different tenant contexts with the same credentials.
Finally, examine how Basic Auth interacts with other security controls. WAF rules, API gateways, and CDN configurations might provide additional authorization layers that aren't visible in the application code but are critical for preventing BOLA/IdOR attacks.
Basic Auth-Specific Remediation
Remediating BOLA/IdOR vulnerabilities in Basic Auth implementations requires a systematic approach to authorization that goes beyond simple authentication. The solution involves implementing proper access controls and validating resource ownership at every access point.
The foundation of remediation is establishing a clear authorization model. For Basic Auth, this typically means:
- Mapping authenticated users to their authorized resources
- Implementing ownership verification for all data access
- Using parameterized queries with proper scoping
- Adding authorization middleware that validates access rights
Here's a secure implementation pattern for Basic Auth endpoints:
function authorizeResourceAccess(req, res, next) {
const resourceId = req.params.id;
const userId = req.user.id;
// Verify the authenticated user owns this resource
const isOwner = db.verifyResourceOwnership(userId, resourceId);
if (!isOwner) {
return res.status(403).json({
error: 'Access denied',
message: 'You do not have permission to access this resource'
});
}
next();
}This middleware should be added to any endpoint that accesses user-specific resources:
app.get('/api/users/:id', authenticateBasic, authorizeResourceAccess, async (req, res) => {
const userId = req.params.id;
const user = await db.getUserById(userId);
res.json(user);
});For collection endpoints that return filtered results, implement proper scoping:
app.get('/api/users', authenticateBasic, async (req, res) => {
const userId = req.user.id;
const users = await db.getUsers({ ownerId: userId });
res.json(users);
});Database queries should use parameterized statements with proper ownership validation:
app.get('/api/orders/:orderId', authenticateBasic, async (req, res) => {
const orderId = req.params.orderId;
const userId = req.user.id;
const order = await db.query(
'SELECT * FROM orders WHERE id = $1 AND user_id = $2',
[orderId, userId]
);
if (!order) {
return res.status(404).json({ error: 'Order not found or access denied' });
}
res.json(order);
});For multi-tenant applications, implement tenant-scoped authorization:
app.get('/api/tenants/:tenantId/data', authenticateBasic, async (req, res) => {
const tenantId = req.params.tenantId;
const userId = req.user.id;
// Verify user belongs to this tenant
const userTenant = await db.getUserTenant(userId);
if (userTenant.id !== tenantId) {
return res.status(403).json({ error: 'Access denied' });
}
const data = await db.getTenantData(tenantId);
res.json(data);
});Implement role-based access control (RBAC) for more granular permissions:
const roles = {
admin: ['users', 'orders', 'settings'],
user: ['own-orders', 'profile'],
viewer: ['public-data']
};
function checkAuthorization(requiredPermission) {
return function(req, res, next) {
const userPermissions = roles[req.user.role] || [];
if (!userPermissions.includes(requiredPermission)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}Apply this to endpoints:
app.get('/api/admin/users', authenticateBasic, checkAuthorization('users'), async (req, res) => {
const users = await db.getAllUsers();
res.json(users);
});For APIs that accept complex filtering parameters, validate that filters don't allow cross-user access:
app.get('/api/search', authenticateBasic, async (req, res) => {
const userId = req.user.id;
const { search, category } = req.query;
// Ensure search results are scoped to the authenticated user
const results = await db.search(
{ search, category, userId },
{ limit: 50 }
);
res.json(results);
});Implement comprehensive logging for authorization failures to detect potential attacks:
function logAuthorizationFailure(userId, resourceId, action) {
console.log(`Authorization failed: user ${userId} attempted ${action} on resource ${resourceId}`);
// Send to monitoring system
}Finally, implement automated testing for authorization controls:
describe('Authorization tests', () => {
test('User cannot access other users data', async () => {
const user1 = await createUser();
const user2 = await createUser();
// User1 creates data
const data = await api.post('/api/data', {
auth: basicAuth(user1.username, user1.password),
body: { content: 'test' }
});
// User2 tries to access it
const response = await api.get(`/api/data/${data.id}`, {
auth: basicAuth(user2.username, user2.password)
});
expect(response.status).toBe(403);
});
});These remediation patterns ensure that Basic Auth authentication is properly coupled with authorization checks, preventing BOLA/IdOR vulnerabilities while maintaining the protocol's simplicity and statelessness.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |