HIGH privilege escalationhapijavascript

Privilege Escalation in Hapi (Javascript)

Privilege Escalation in Hapi with Javascript

Privilege escalation occurs when a user with limited permissions gains access to functionality or data reserved for higher-privilege users. In Hapi applications written in JavaScript, this often manifests when route handlers fail to validate the caller's identity or when authorization checks are inconsistently applied across endpoints.

Hapi uses a plugin-based architecture where authentication and authorization are typically configured at the server level. A common mistake is to authenticate a request but then forget to enforce role-based access control (RBAC) in the route handler. For example, an endpoint that retrieves user billing details might be protected by an auth strategy, but if the handler assumes the user is always authorized, a malicious actor who bypasses authentication could still access privileged data.

Consider a Hapi route that requires a JWT token but does not verify the token's scope or role claim:

server.route({
  method: 'GET',
  path: '/api/billing',
  options: {
    auth: 'jwt',
    handler: function (request, h) {
      // No role check - assumes caller is authorized
      return h.response({ balance: 1234.56 }).code(200);
    }
  }
});

An attacker who obtains a valid JWT (e.g., via a compromised client) can call /api/billing and retrieve sensitive financial data, even if they are not a billing staff member.

Another scenario involves Insecure Direct Object References (IDOR) combined with insufficient authorization. If a route uses a parameter like :userId to fetch user data without checking whether the authenticated user is permitted to access that resource, an attacker can manipulate the parameter to access another user's data. This is a classic privilege escalation pattern:

server.route({
  method: 'GET',
  path: '/api/users/{userId}',
  options: {
    auth: 'jwt',
    handler: function (request, h) {
      const userId = request.params.userId;
      // Vulnerable: fetches user data without verifying caller's permissions
      const user = db.getUser(userId);
      return h.response(user).code(200);
    }
  }
});

An attacker with a valid session could change the userId parameter to another user's ID and retrieve private information, effectively escalating privileges.

Additionally, Hapi's default behavior does not enforce strict route-level authorization unless explicitly configured. Developers may rely on a single global auth plugin and forget to apply role checks per route, leading to inconsistent security postures. This is particularly dangerous in large APIs where multiple teams manage different endpoints.

To mitigate these risks, developers must ensure that every route that handles privileged functionality includes explicit role validation, often using a combination of route-specific configurations and handler-level checks.

Javascript-Specific Remediation in Hapi

Remediation requires enforcing strict authorization checks that verify the authenticated user has the required role or permission before performing sensitive actions. In Hapi, this can be achieved by configuring route-level authorization strategies or by adding explicit role checks within the handler.

Example of a secure route using role-based access control:

server.route({
  method: 'GET',
  path: '/api/billing',
  options: {
    auth: 'jwt',
    handler: function (request, h) {
      // Extract role from JWT payload
      const role = request.auth.credentials.role || 'guest';
      if (role !== 'billing_staff') {
        return h.response({ error: 'Insufficient permissions' }).code(403);
      }
      // Proceed only if role is authorized
      return h.response({ balance: 1234.56 }).code(200);
    }
  }
});

Alternatively, use Hapi's built-in authorization plugin to define a policy that restricts access based on scope or role:

const billionaire = require('@hapi/bell');

server.register(billionaire);

server.auth.strategy('jwt', 'jwt', {
  key: 'secret',
  validateFunc: (username, token) => {
    // Validate token and return user object
  }
});

// Define a role-based authorization plugin
const privileges = {
  'billing_staff': ['/api/billing'],
  'admin': ['/api/billing', '/api/admin']
};

server.route({
  method: 'GET',
  path: '/api/billing',
  options: {
    auth: 'jwt',
    handler: function (request, h) {
      return h.response({ balance: 1234.56 }).code(200);
    },
    plugins: {
      'hapi-authorize': {
        assign: 'admin' // or 'billing_staff' based on user role
      }
    }
  }
});

Another robust approach is to use a centralized authorization function that checks permissions before route execution:

function authorize(requiredRole) {
  return function (request, h) {
    const userRole = request.auth.credentials.role;
    if (userRole !== requiredRole) {
      return h.response({ error: 'Forbidden' }).code(403);
    }
    return true;
  };
}

server.route({
  method: 'GET',
  path: '/api/users/{userId}',
  options: {
    auth: 'jwt',
    handler: function (request, h) {
      // Handler logic
    },
    config: {
      pre: [authorize('admin')]
    }
  }
});

Additionally, always validate resource identifiers against the authenticated user's scope. Instead of directly using request.params.userId to fetch data, verify that the caller is allowed to access that resource:

function verifyAccess(request, userId) {
  const authenticatedUserId = request.auth.credentials.userId;
  if (authenticatedUserId !== userId) {
    // Only allow access if user has admin role
    const user = db.getUser(userId);
    if (!user || user.role !== 'admin') {
      return false;
    }
  }
  return true;
}

server.route({
  method: 'GET',
  path: '/api/users/{userId}',
  options: {
    auth: 'jwt',
    handler: function (request, h) {
      if (!verifyAccess(request, request.params.userId)) {
        return h.response({ error: 'Unauthorized' }).code(403);
      }
      const user = db.getUser(request.params.userId);
      return h.response(user).code(200);
    }
  }
});

These patterns ensure that privilege escalation is prevented by enforcing strict, role-based authorization at the route level and by validating resource access against the caller's identity and permissions.

Frequently Asked Questions

How can I prevent privilege escalation in my Hapi API?
Always enforce role-based access control on routes that handle sensitive data or actions. Use explicit authorization checks in route handlers or configure route-level plugins that validate the user's role or scope before processing the request.
Is input validation enough to stop privilege escalation?
No. Input validation helps prevent injection or malformed requests, but it does not address authorization flaws. Privilege escalation requires proper authorization checks that verify whether the authenticated user is permitted to perform a specific action or access a specific resource.