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: 'admin@example.com', isAdmin: true, password: 'secretpassword123' }); // Create regular user const user1 = await User.create({ username: 'user1', email: 'user1@example.com', 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=`); debug(`Setup endpoint: POST /setup`); debug(`\nExample exploit:`); debug(`GET /posts?authorMatch={"$and":[{"$where":"this.isAdmin"}]}`); });