README.md
Rendering markdown...
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"}]}`);
});