HIGH bola idorhapi

Bola Idor in Hapi

How Bola Idor Manifests in Hapi

Broken Object Level Authorization (BOLA), also known as Insecure Direct Object Reference (IDOR), occurs when an API fails to properly verify whether a user has permission to access a specific resource. In Hapi applications, this vulnerability manifests through several Hapi-specific patterns that developers should recognize.

The most common BOLA pattern in Hapi involves route handlers that trust client-provided identifiers without validating ownership. Consider a typical Hapi route:

const userProfile = async (request, h) => {
  const userId = request.params.userId;
  const user = await db.getUserById(userId);
  return h.response(user);
};

This handler accepts any userId parameter and returns the corresponding user record. An authenticated user could simply modify the userId parameter to access other users' profiles, as Hapi itself doesn't enforce any authorization checks.

Hapi's plugin architecture can exacerbate BOLA vulnerabilities when authentication plugins don't integrate properly with business logic. For example, using hapi-auth-jwt2 for authentication but failing to validate that the JWT subject matches the requested resource:

server.route({
  method: 'GET',
  path: '/api/users/{id}/posts',
  options: {
    auth: 'jwt',
    handler: async (request, h) => {
      const postId = request.params.id;
      const post = await db.getPostById(postId);
      return h.response(post);
    }
  }
});

The route correctly requires authentication, but the handler doesn't verify that the authenticated user owns or has permission to view the requested post.

Another Hapi-specific manifestation involves dynamic route generation where authorization logic is forgotten. When using server.route() in loops or with configuration objects:

const createRoutes = (userId) => {
  return {
    method: 'GET',
    path: `/api/users/${userId}/messages`,
    handler: async (request, h) => {
      const messages = await db.getMessagesForUser(userId);
      return h.response(messages);
    }
  };
};

If userId comes from an untrusted source or isn't validated against the authenticated user, this creates a BOLA vulnerability. The route assumes the userId parameter is safe without verifying the requesting user's permissions.

Hapi's validation system using Joi can be misused in ways that enable BOLA. While Joi validates data types and formats, it doesn't perform authorization:

const getPostRoute = {
  method: 'GET',
  path: '/posts/{id}',
  options: {
    validate: {
      params: {
        id: Joi.string().uuid().required()
      }
    },
    handler: async (request, h) => {
      const post = await db.getPostById(request.params.id);
      return h.response(post);
    }
  }
};

The Joi validation ensures the ID is a valid UUID format, but doesn't prevent a user from accessing any post ID they can guess or enumerate.

Batch operations in Hapi present another BOLA attack surface. When endpoints accept arrays of IDs without per-item authorization:

const getMultiplePosts = async (request, h) => {
  const postIds = request.query.ids.split(',');
  const posts = await Promise.all(
    postIds.map(id => db.getPostById(id))
  );
  return h.response(posts);
};

An attacker could request posts=[1,2,3,4,5] and receive data for all five posts, even if they only have permission to access one.

Hapi's response toolkit (h.response) doesn't provide built-in authorization features, making it easy to accidentally expose data. Developers must explicitly implement authorization checks before calling h.response().

Hapi-Specific Detection

Detecting BOLA vulnerabilities in Hapi applications requires examining both the code structure and runtime behavior. The key is identifying where Hapi routes accept resource identifiers without proper authorization validation.

Static code analysis for Hapi BOLA focuses on patterns where route handlers access database records using parameters directly from the request. Look for:

// Vulnerable pattern
const userId = request.params.userId;
const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);

Tools like ESLint with custom rules can flag these patterns, but they miss runtime authorization failures.

Dynamic analysis through automated scanning provides more comprehensive BOLA detection. middleBrick's black-box scanning approach tests Hapi APIs without requiring source code access. The scanner identifies endpoints that accept resource IDs and attempts authenticated access to different resources to detect authorization bypasses.

For Hapi applications, middleBrick specifically tests:

  • Route parameters that reference user-specific resources (userId, postId, etc.)
  • Query parameters containing IDs or resource references
  • Request body fields with object identifiers
  • Headers that might contain authorization tokens or user identifiers
  • Batch endpoints accepting multiple IDs

The scanner uses authenticated sessions to access resources the user should legitimately see, then systematically modifies identifiers to test for BOLA. For example, if a Hapi endpoint returns user profile data for user ID 123, the scanner tries user ID 124 to check if authorization is properly enforced.

middleBrick's OpenAPI analysis is particularly effective for Hapi applications since Hapi often uses Swagger/OpenAPI specifications. The scanner cross-references the spec's security definitions with actual runtime behavior, identifying mismatches where authentication is declared but authorization isn't implemented.

Runtime detection also involves monitoring for IDOR-specific patterns in Hapi's request lifecycle. Using request lifecycle extensions, you can instrument Hapi to log parameter usage patterns:

server.ext('onRequest', (request, h) => {
  const params = Object.keys(request.params);
  const auth = request.auth;
  // Log or analyze parameter patterns for BOLA detection
  return h.continue;
});

Commercial BOLA detection tools can integrate with Hapi through plugins that intercept route execution and verify authorization before database access. These plugins compare the authenticated user's permissions against the requested resource.

Network-level detection involves monitoring API calls for sequential ID access patterns that indicate automated BOLA attacks. Hapi applications behind reverse proxies or API gateways can log suspicious access patterns like rapid requests to sequential resource IDs.

middleBrick's continuous monitoring feature for Hapi APIs provides ongoing BOLA detection by regularly scanning endpoints and alerting on new vulnerabilities as they're introduced through code changes.

Hapi-Specific Remediation

Remediating BOLA vulnerabilities in Hapi requires implementing proper authorization checks at the appropriate layers. The most effective approach combines Hapi's built-in features with application-specific authorization logic.

The fundamental remediation pattern involves validating that the authenticated user has permission to access the requested resource before returning any data. In Hapi, this typically means:

const userProfile = async (request, h) => {
  const userId = request.params.userId;
  const authenticatedUserId = request.auth.credentials.userId;
  
  // Verify ownership
  if (userId !== authenticatedUserId) {
    return h.response({ error: 'Access denied' }).code(403);
  }
  
  const user = await db.getUserById(userId);
  return h.response(user);
};

This pattern ensures users can only access their own resources. For more complex authorization scenarios, implement role-based or permission-based checks:

const getPost = async (request, h) => {
  const postId = request.params.id;
  const user = request.auth.credentials;
  
  const post = await db.getPostWithAuthor(postId);
  if (!post) {
    return h.response({ error: 'Not found' }).code(404);
  }
  
  // Check if user is owner or has permission
  const hasAccess = post.authorId === user.id || 
                   user.roles.includes('admin') ||
                   await db.userCanViewPost(user.id, postId);
  
  if (!hasAccess) {
    return h.response({ error: 'Access denied' }).code(403);
  }
  
  return h.response(post);
};

Hapi's pre-handler extension provides a cleaner way to implement authorization checks that run before the main handler:

const getPostRoute = {
  method: 'GET',
  path: '/posts/{id}',
  options: {
    pre: [
      {
        method: (request) => {
          const postId = request.params.id;
          const userId = request.auth.credentials.id;
          return db.verifyPostAccess(userId, postId);
        },
        assign: 'postAccess'
      }
    ],
    handler: async (request, h) => {
      if (!request.pre.postAccess) {
        return h.response({ error: 'Access denied' }).code(403);
      }
      const post = await db.getPostById(request.params.id);
      return h.response(post);
    }
  }
};

This separates authorization logic from business logic, making the code more maintainable and testable.

For applications with complex authorization requirements, implement a centralized authorization service:

class AuthorizationService {
  constructor(db) {
    this.db = db;
  }
  
  async canAccessResource(userId, resourceType, resourceId) {
    switch (resourceType) {
      case 'user':
        return await this.canAccessUser(userId, resourceId);
      case 'post':
        return await this.canAccessPost(userId, resourceId);
      case 'message':
        return await this.canAccessMessage(userId, resourceId);
      default:
        return false;
    }
  }
  
  async canAccessUser(userId, targetUserId) {
    // Users can access their own profile or if admin
    const user = await this.db.getUserById(userId);
    return user.id === targetUserId || user.roles.includes('admin');
  }
  
  async canAccessPost(userId, postId) {
    const post = await this.db.getPostById(postId);
    if (!post) return false;
    
    const user = await this.db.getUserById(userId);
    return post.authorId === userId || user.roles.includes('admin');
  }
}

Then use this service in your Hapi handlers:

const auth = new AuthorizationService(db);

const getPost = async (request, h) => {
  const postId = request.params.id;
  const userId = request.auth.credentials.id;
  
  const canAccess = await auth.canAccessResource(userId, 'post', postId);
  if (!canAccess) {
    return h.response({ error: 'Access denied' }).code(403);
  }
  
  const post = await db.getPostById(postId);
  return h.response(post);
};

Hapi's validation system can be extended to include authorization checks. Create a custom Joi extension that validates ownership:

const ownershipExtension = (joi) => ({
  type: 'ownership',
  base: joi.any(),
  messages: {
    'ownership.fail': 'User does not own this resource'
  },
  validate(value, helpers) {
    const { error } = joi.string().uuid().validate(value);
    if (error) return { value, errors: error };
    
    // This would need access to request context
    // Simplified example - in practice you'd need a different approach
    return { value };
  }
});

For database-level protection, use row-level security (RLS) or always include user ID filters in queries:

const getUserMessages = async (request, h) => {
  const userId = request.auth.credentials.id;
  const messages = await db.query(
    'SELECT * FROM messages WHERE user_id = $1',
    [userId]
  );
  return h.response(messages);
};

This ensures that even if authorization checks in the application layer fail, the database won't return unauthorized data.

Implement comprehensive logging for authorization failures to detect potential BOLA attacks:

const logAuthorizationFailure = (request, resourceType, resourceId) => {
  const logger = request.logger.bind({ 
    auth: request.auth,
    resource: { type: resourceType, id: resourceId }
  });
  logger.error('Authorization failed', {
    userId: request.auth.credentials.id,
    resourceType,
    resourceId,
    timestamp: new Date().toISOString()
  });
};

Finally, use Hapi's route authentication configuration to enforce that sensitive routes require authentication:

server.route({
  method: 'GET',
  path: '/api/users/{id}',
  options: {
    auth: {
      strategy: 'jwt',
      mode: 'required'
    },
    handler: async (request, h) => {
      // Authorization logic here
    }
  }
});

This prevents unauthenticated access entirely, reducing the BOLA attack surface.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

How can I test my Hapi API for BOLA vulnerabilities?
You can test Hapi APIs for BOLA using middleBrick's black-box scanning approach. Simply submit your API URL to middleBrick, which will authenticate as a legitimate user, access resources they should see, then systematically modify resource identifiers to test for authorization bypasses. The scanner tests route parameters, query parameters, request bodies, and batch endpoints to identify where Hapi routes fail to validate user permissions before returning data.
Does Hapi provide any built-in features to prevent BOLA?
Hapi doesn't have built-in BOLA prevention features. While Hapi provides authentication plugins like hapi-auth-jwt2 and validation with Joi, it doesn't enforce authorization checks. Developers must explicitly implement authorization logic in route handlers or use pre-handler extensions. Hapi's architecture allows for custom authorization plugins, but the framework itself assumes developers will handle authorization at the application level.