HIGH bola idorexpress

Bola Idor in Express

How Bola Idor Manifests in Express

BOLA/IdOR (Broken Object Level Authorization) in Express applications often stems from how routes handle dynamic parameters and user context. Express's middleware architecture and parameter handling create specific patterns where this vulnerability frequently appears.

The most common Express-specific manifestation occurs in route handlers that use req.params or req.query without validating whether the authenticated user owns the resource being accessed. Consider a typical Express route:

app.get('/api/users/:userId', (req, res) => {
  const userId = req.params.userId;
  db.query('SELECT * FROM users WHERE id = ?', [userId])
    .then(user => res.json(user))
    .catch(err => res.status(500).json({ error: 'Database error' }));
});

This pattern is dangerous because Express automatically parses URL parameters and makes them available on req.params. The route trusts that userId from the URL belongs to the authenticated user, but there's no verification. An attacker can simply change the :userId parameter to access any user's data.

Express middleware also creates specific BOLA/IdOR scenarios. Authentication middleware often attaches user information to req.user, but subsequent route handlers may ignore this context:

function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (token) {
    jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
      if (!err) req.user = user;
      next();
    });
  } else {
    next();
  }
}

app.get('/api/orders/:orderId', authMiddleware, (req, res) => {
  const orderId = req.params.orderId;
  db.query('SELECT * FROM orders WHERE id = ?', [orderId])
    .then(order => res.json(order))
    .catch(err => res.status(500).json({ error: 'Database error' }));
});

Even with authentication middleware, the route handler doesn't check if req.user.id matches the order's owner. Express's permissive routing allows attackers to enumerate resources by simply changing URL parameters.

Another Express-specific pattern involves population in Mongoose queries, common in Express + MongoDB applications:

app.get('/api/users/:userId/posts', authMiddleware, (req, res) => {
  Post.find({ userId: req.params.userId })
    .populate('comments.author', 'name email')
    .exec((err, posts) => {
      if (err) return res.status(500).json({ error: 'Error fetching posts' });
      res.json(posts);
    });
});

Here, an attacker can access any user's posts by changing :userId, and the populate operation may also expose comment authors' PII without proper authorization checks.

Express's flexible routing can also lead to BOLA/IdOR through route overlap. Consider:

app.get('/api/users/:id', getUser);
app.get('/api/users/profile', getProfile);

function getUser(req, res) {
  const id = req.params.id;
  User.findById(id, (err, user) => {
    if (err || !user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  });
}

function getProfile(req, res) {
  const userId = req.user.id;
  User.findById(userId, (err, user) => {
    if (err || !user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  });
}

An attacker can access /api/users/:id with any user ID, bypassing the intended profile-only access pattern.

Express-Specific Detection

Detecting BOLA/IdOR in Express applications requires understanding Express's routing patterns and middleware flow. middleBrick's Express-specific scanning looks for several key indicators.

The scanner analyzes route definitions to identify dynamic parameters that could lead to authorization bypasses. For Express applications, it specifically examines:

  • Route patterns with :param syntax that reference user-owned resources
  • Middleware chains that may bypass authentication for certain paths
  • Database queries that use URL parameters directly without ownership validation
  • Population operations that could expose related user data

When scanning an Express API, middleBrick tests for BOLA/IdOR by manipulating URL parameters and observing responses. For example, given a route like:

app.get('/api/users/:userId/orders', (req, res) => {
  const userId = req.params.userId;
  Order.find({ userId })
    .then(orders => res.json(orders))
    .catch(err => res.status(500).json({ error: 'Error fetching orders' }));
});

The scanner will:

  1. Authenticate as one user and capture their userId
  2. Request /api/users/:userId/orders with the authenticated user's ID (baseline)
  3. Request the same endpoint with different user IDs
  4. Compare response patterns to detect if data from other users is accessible

middleBrick's Express detection also examines error handling patterns. Express applications often return different error messages based on resource existence, which can aid enumeration:

app.get('/api/users/:userId', (req, res) => {
  User.findById(req.params.userId, (err, user) => {
    if (err || !user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  });
});

The scanner tests how the API responds to non-existent vs. inaccessible resources, looking for timing differences or error message variations that could indicate BOLA/IdOR vulnerabilities.

For Express applications using Mongoose, middleBrick analyzes population patterns:

app.get('/api/posts/:postId', (req, res) => {
  Post.findById(req.params.postId)
    .populate('author', 'name email bio')
    .exec((err, post) => {
      if (err || !post) return res.status(404).json({ error: 'Post not found' });
      res.json(post);
    });
});

The scanner checks if the authenticated user can access posts they don't own and whether population exposes author information without proper authorization.

middleBrick's CLI tool makes Express BOLA/IdOR scanning straightforward:

npx middlebrick scan https://api.example.com --output json --tests bola

# Or scan with specific Express patterns
npx middlebrick scan https://api.example.com --output json --tests bola,authentication

The GitHub Action integration allows continuous monitoring of Express APIs in your CI/CD pipeline:

name: API Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run middleBrick Scan
        run: |
          npx middlebrick scan https://staging.example.com/api --fail-on-score-below C

Express-Specific Remediation

Remediating BOLA/IdOR in Express requires implementing proper authorization checks at the route handler level. The key principle: never trust URL parameters or query strings for resource ownership.

The most effective Express-specific pattern uses middleware to enforce resource ownership:

function authorizeResourceOwner(resourceField = 'userId') {
  return (req, res, next) => {
    const resourceId = req.params[resourceField];
    
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }

    // For numeric IDs, compare directly
    if (req.user.id === resourceId) {
      return next();
    }

    // For database lookups, verify ownership
    const model = getModelFromField(resourceField);
    model.findById(resourceId, (err, resource) => {
      if (err || !resource) {
        return res.status(404).json({ error: 'Resource not found' });
      }
      
      if (resource.ownerId.toString() !== req.user.id) {
        return res.status(403).json({ error: 'Access denied' });
      }
      
      next();
    });
  };
}

// Usage in routes
app.get('/api/users/:userId', authMiddleware, authorizeResourceOwner('userId'), (req, res) => {
  User.findById(req.params.userId, (err, user) => {
    if (err || !user) return res.status(404).json({ error: 'User not found' });
    res.json(user);
  });
});

app.get('/api/users/:userId/orders', authMiddleware, authorizeResourceOwner('userId'), (req, res) => {
  Order.find({ userId: req.params.userId }, (err, orders) => {
    if (err) return res.status(500).json({ error: 'Error fetching orders' });
    res.json(orders);
  });
});

This pattern ensures that the authenticated user's ID matches the resource owner ID before allowing access to the main route handler.

For Express applications using Mongoose, leverage pre-hooks for authorization:

const orderSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId, required: true },
  amount: { type: Number, required: true },
  items: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Item' }]
});

orderSchema.pre('find', function(next) {
  if (this.op === 'find' && this.getFilter().userId !== req.user.id) {
    this.getFilter().userId = req.user.id;
  }
  next();
});

orderSchema.pre('save', function(next) {
  if (!this.isNew) {
    return next(new Error('Modifying existing orders is not permitted'));
  }
  this.userId = req.user.id;
  next();
});

This ensures that all queries automatically filter by the authenticated user's ID, preventing BOLA/IdOR at the database level.

Express's error handling can be standardized to prevent information leakage:

app.use((err, req, res, next) => {
  if (err.name === 'CastError' || err.name === 'ValidationError') {
    return res.status(400).json({ error: 'Invalid request' });
  }
  
  if (err.message.includes('not found')) {
    return res.status(404).json({ error: 'Resource not found' });
  }
  
  console.error(err);
  res.status(500).json({ error: 'Internal server error' });
});

This prevents attackers from distinguishing between non-existent resources and authorization failures.

For population operations, implement authorization-aware population:

function populateWithAuth(path, select) {
  return function(next) {
    this.populate({
      path,
      select,
      match: { ownerId: req.user.id }
    });
    next();
  };
}

app.get('/api/posts/:postId', authMiddleware, (req, res) => {
  Post.findById(req.params.postId)
    .populate('author comments.author', 'name email')
    .exec((err, post) => {
      if (err || !post || post.author.id !== req.user.id) {
        return res.status(403).json({ error: 'Access denied' });
      }
      res.json(post);
    });
});

The middleBrick dashboard helps track remediation progress by showing security score improvements over time as you fix BOLA/IdOR issues across your Express API endpoints.

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 does middleBrick detect BOLA/IdOR in Express applications?
middleBrick scans Express APIs by testing dynamic route parameters with authenticated sessions. It manipulates URL parameters like :userId or :orderId and compares responses to detect if users can access resources they don't own. The scanner also analyzes error handling patterns and population operations that might expose related user data without proper authorization checks.
Can middleBrick scan my Express API running locally during development?
Yes, middleBrick's CLI tool can scan any Express API endpoint, including localhost URLs. You can run 'npx middlebrick scan http://localhost:3000/api --output json' to scan your development server. The GitHub Action integration also works with staging environments, allowing you to catch BOLA/IdOR issues before production deployment.