HIGH insecure direct object referencemongodb

Insecure Direct Object Reference in Mongodb

How Insecure Direct Object Reference Manifests in Mongodb

Insecure Direct Object Reference (IDOR) in Mongodb applications occurs when client-supplied identifiers are used directly to fetch database documents without proper authorization checks. Mongodb's flexible document model and common query patterns create specific vulnerabilities that developers must understand.

The most common Mongodb IDOR pattern involves using URL parameters or request bodies to construct queries that fetch documents by their ObjectId. Consider this vulnerable endpoint:

app.get('/api/users/:userId', async (req, res) => {
  const userId = req.params.userId;
  const user = await User.findById(userId);
  res.json(user);
});

This code directly uses the client-supplied userId to fetch a user document. An attacker can simply change the userId parameter to access any user's data. The vulnerability is compounded by Mongodb's ObjectId format, which is 24-character hexadecimal strings that are predictable when created sequentially.

Another Mongodb-specific IDOR pattern involves array field manipulation. Many applications store related data in arrays within documents:

const user = await User.findOneAndUpdate(
  { _id: userId },
  { $push: { documents: documentId } }
);

If the userId is not validated against the authenticated user, an attacker can add documents to other users' accounts by guessing their ObjectId.

Mongodb's aggregation framework introduces additional IDOR vectors. Consider this vulnerable aggregation pipeline:

const pipeline = [
  { $match: { userId: req.params.userId } },
  { $lookup: { from: 'documents', localField: 'documents', foreignField: '_id', as: 'docDetails' } }
];
const result = await User.aggregate(pipeline);

An attacker can manipulate the userId parameter to access other users' aggregated data, potentially exposing sensitive information through joins that should be restricted.

Mongodb's flexible schema also enables IDOR through dynamic field access. Applications might use dot notation to access nested fields:

const field = req.query.field;
const value = await User.findOne({ _id: userId }, { [field]: 1 });

Without validation of the field parameter, an attacker can access any field in the user document, including sensitive fields like password hashes or API keys.

Time-based IDOR attacks are particularly effective against Mongodb due to its document-oriented nature. Applications often expose endpoints that return time-series data:

app.get('/api/users/:userId/activity', async (req, res) => {
  const { start, end } = req.query;
  const activities = await Activity.find({
    userId: req.params.userId,
    timestamp: { $gte: start, $lte: end }
  });
  res.json(activities);
});

An attacker can enumerate userIds and retrieve comprehensive activity logs by manipulating the date range parameters.

Mongodb's geospatial queries create unique IDOR opportunities. Applications using $near or $geoWithin operators might expose location data:

const nearbyUsers = await User.find({
  location: {
    $near: {
      $geometry: {
        type: 'Point',
        coordinates: [req.query.lon, req.query.lat]
      },
      $maxDistance: 1000
    }
  }
});

Without proper authorization, this endpoint reveals the locations of all users within the specified radius.

Mongodb-Specific Detection

Detecting IDOR vulnerabilities in Mongodb applications requires both static analysis and dynamic testing. Static analysis tools can identify dangerous query patterns, while dynamic scanners can actively test for authorization bypasses.

Static analysis should look for these Mongodb-specific patterns:

// Dangerous patterns to flag
const result = await Model.findById(req.params.id);
const result = await Model.findOne({ _id: req.body.id });
const result = await Model.find({ userId: req.query.userId });
const result = await Model.aggregate([ { $match: { userId: req.params.userId } } ]);
const result = await Model.updateOne({ _id: req.body.id }, updateData);
const result = await Model.deleteOne({ _id: req.body.id });

Dynamic testing with middleBrick specifically targets Mongodb IDOR vulnerabilities by testing unauthenticated endpoints with manipulated identifiers. The scanner tests 12 security categories including BOLA (Broken Object Level Authorization), which covers IDOR.

middleBrick's Mongodb-specific detection includes:

Detection Type What It Tests Impact
ObjectId Enumeration Tests sequential ObjectId manipulation High
Array Field Manipulation Tests adding/removing array elements High
Aggregation Bypass Tests pipeline manipulation Critical
Geospatial Access Tests location data exposure Medium
Time-Series Access Tests activity log enumeration High

The scanner automatically generates test cases based on the observed API structure. For a typical REST endpoint like /api/users/:id, middleBrick will:

  • Test sequential ObjectId manipulation (changing 1 character at a time)
  • Test common ObjectId patterns (all zeros, all ones, reversed)
  • Test array manipulation endpoints with modified identifiers
  • Test aggregation endpoints with manipulated pipeline parameters

middleBrick's black-box approach is particularly effective for Mongodb IDOR because it doesn't require access to the database or source code. It simply sends requests with manipulated parameters and analyzes the responses for unauthorized data access.

For Mongodb applications using Mongoose ODM, middleBrick also analyzes the schema definitions to understand relationships and identify potential IDOR vectors through population and referencing patterns.

Mongodb-Specific Remediation

Remediating Mongodb IDOR vulnerabilities requires implementing proper authorization checks at the application layer. The key principle is never trust client-supplied identifiers.

The most effective approach is to use the authenticated user's ID from the session, not from request parameters:

app.get('/api/users/:userId', async (req, res) => {
  // Get user ID from authenticated session, not URL parameter
  const authenticatedUserId = req.user._id;
  
  // Verify the requested ID matches the authenticated user
  if (req.params.userId !== authenticatedUserId.toString()) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  const user = await User.findById(authenticatedUserId);
  res.json(user);
});

For endpoints that legitimately need to access other users' data (admin functionality), implement role-based access control:

app.get('/api/users/:userId', async (req, res) => {
  const requestedUserId = req.params.userId;
  const authenticatedUser = req.user;
  
  // Allow self-access or admin access
  if (authenticatedUser.role === 'admin' || 
      authenticatedUser._id.equals(requestedUserId)) {
    const user = await User.findById(requestedUserId);
    res.json(user);
  } else {
    return res.status(403).json({ error: 'Forbidden' });
  }
});

Mongodb's aggregation framework requires special attention. Always include authorization checks in aggregation pipelines:

app.get('/api/users/:userId/documents', async (req, res) => {
  const authenticatedUserId = req.user._id;
  const requestedUserId = req.params.userId;
  
  if (!authenticatedUserId.equals(requestedUserId)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  const pipeline = [
    { $match: { _id: authenticatedUserId } },
    { $lookup: { 
      from: 'documents', 
      localField: 'documents', 
      foreignField: '_id', 
      as: 'docDetails' 
    }}
  ];
  
  const result = await User.aggregate(pipeline);
  res.json(result[0] || { docDetails: [] });
});

For array field manipulation, use atomic operations with proper authorization:

app.post('/api/users/:userId/documents', async (req, res) => {
  const authenticatedUserId = req.user._id;
  const requestedUserId = req.params.userId;
  
  if (!authenticatedUserId.equals(requestedUserId)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  const { documentId } = req.body;
  
  const result = await User.findByIdAndUpdate(
    authenticatedUserId,
    { $push: { documents: documentId } },
    { new: true }
  );
  
  res.json(result);
});

Mongodb's middleware system can centralize authorization logic:

UserSchema.pre('find', function(next) {
  if (this.getQuery()._id) {
    // Allow queries by ID (self-access)
    return next();
  }
  
  // Automatically scope queries to authenticated user
  this.where({ userId: this._user._id });
  next();
});

For geospatial queries, implement radius-based authorization:

app.get('/api/nearby-users', async (req, res) => {
  const authenticatedUser = req.user;
  const { lon, lat, maxDistance = 1000 } = req.query;
  
  const nearbyUsers = await User.find({
    location: {
      $near: {
        $geometry: {
          type: 'Point',
          coordinates: [lon, lat]
        },
        $maxDistance: maxDistance
      }
    },
    _id: { $ne: authenticatedUser._id } // Exclude self
  }).limit(50);
  
  res.json(nearbyUsers);
});

Implement comprehensive logging for all data access operations:

UserSchema.post('find', async function(docs, next) {
  const userId = this.getQuery()._id;
  const authenticatedUserId = this._user._id;
  
  // Log access attempts
  await AuditLog.create({
    action: 'user_access',
    userId: authenticatedUserId,
    targetId: userId,
    timestamp: new Date(),
    success: docs.length > 0
  });
  next();
});

Finally, use Mongoose population with authorization checks:

UserSchema.virtual('documents', {
  ref: 'Document',
  localField: '_id',
  foreignField: 'ownerId',
  justOne: false,
  count: false
});

// Middleware to enforce authorization
UserSchema.pre('find', function populateHook(next) {
  if (this._user && this._user.role !== 'admin') {
    this.populate({
      path: 'documents',
      match: { ownerId: this._user._id }
    });
  }
  next();
});

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 for Mongodb IDOR vulnerabilities without access to the source code?
Use middleBrick's black-box scanning approach. It tests endpoints by manipulating URL parameters, request bodies, and headers to attempt unauthorized data access. The scanner automatically generates test cases based on observed API patterns and analyzes responses for unauthorized data exposure. No credentials or database access required.
What's the difference between IDOR and BOLA in Mongodb applications?
BOLA (Broken Object Level Authorization) is the broader category that includes IDOR. In Mongodb, BOLA encompasses all authorization failures at the object level, including IDOR (direct object reference), collection-level access, aggregation pipeline manipulation, and geospatial query bypasses. IDOR is specifically when client-supplied identifiers are used directly to fetch documents.