// VULNERABLE: Multiple mass assignment vulnerabilities
const express = require('express');
const User = require('./models/User');
const app = express();
// PROBLEM: Direct object assignment without filtering
app.put('/api/users/:id', async (req, res) => {
try {
// DANGEROUS: All request body fields are applied
const updatedUser = await User.findByIdAndUpdate(
req.params.id,
req.body, // No field filtering!
{ new: true }
);
res.json(updatedUser);
} catch (error) {
res.status(500).json({ error: 'Update failed' });
}
});
// PROBLEM: Mass assignment in user creation
app.post('/api/register', async (req, res) => {
try {
// DANGEROUS: All fields from request body
const newUser = new User(req.body);
await newUser.save();
res.status(201).json(newUser);
} catch (error) {
res.status(500).json({ error: 'Registration failed' });
}
});
// PROBLEM: Bulk operations without field control
app.post('/api/users/bulk-update', async (req, res) => {
try {
const updates = req.body.users;
for (const update of updates) {
// VULNERABLE: No field filtering in bulk operations
await User.findByIdAndUpdate(update.id, update);
}
res.json({ message: 'Users updated' });
} catch (error) {
res.status(500).json({ error: 'Bulk update failed' });
}
});
// Attack examples:
// PUT /api/users/123
// {
// "username": "victim",
// "role": "admin", // Privilege escalation
// "isAdmin": true, // Admin flag
// "credits": 999999, // Financial manipulation
// "isVerified": true // Bypass verification
// }
// POST /api/register
// {
// "username": "attacker",
// "email": "test@example.com",
// "password": "password123",
// "role": "admin", // Create admin user
// "credits": 1000000 // Start with credits
// }
// SECURE: Comprehensive mass assignment protection
const express = require('express');
const Joi = require('joi');
const User = require('./models/User');
const app = express();
// Field validation schemas
const USER_UPDATE_SCHEMA = Joi.object({
username: Joi.string().alphanum().min(3).max(30),
email: Joi.string().email(),
firstName: Joi.string().max(50),
lastName: Joi.string().max(50),
phone: Joi.string().pattern(/^[\+]?[1-9][\d]{0,14}$/),
bio: Joi.string().max(500),
preferences: Joi.object({
newsletter: Joi.boolean(),
notifications: Joi.boolean(),
theme: Joi.string().valid('light', 'dark')
})
// Explicitly NOT including: role, isAdmin, credits, isVerified
}).options({ stripUnknown: true });
const ADMIN_UPDATE_SCHEMA = Joi.object({
username: Joi.string().alphanum().min(3).max(30),
email: Joi.string().email(),
firstName: Joi.string().max(50),
lastName: Joi.string().max(50),
role: Joi.string().valid('user', 'moderator', 'admin'),
isVerified: Joi.boolean(),
credits: Joi.number().min(0).max(1000000)
// Still not including: isAdmin (separate endpoint)
}).options({ stripUnknown: true });
const USER_CREATION_SCHEMA = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).required(),
firstName: Joi.string().max(50).required(),
lastName: Joi.string().max(50).required(),
acceptTerms: Joi.boolean().valid(true).required()
}).options({ stripUnknown: true });
// Validation middleware
function validateAndFilter(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).json({
error: 'Validation failed',
details: error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}))
});
}
req.body = value;
next();
};
}
// Role-based access control
function requireRole(requiredRole) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
const roleHierarchy = { 'user': 1, 'moderator': 2, 'admin': 3 };
const userLevel = roleHierarchy[req.user.role] || 0;
const requiredLevel = roleHierarchy[requiredRole] || 0;
if (userLevel < requiredLevel) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Secure user update endpoint
app.put('/api/users/:id',
authenticateToken,
validateAndFilter(USER_UPDATE_SCHEMA),
async (req, res) => {
try {
const userId = req.params.id;
// Users can only update their own profile
if (req.user.id !== userId && req.user.role !== 'admin') {
return res.status(403).json({ error: 'Cannot update other users' });
}
const updatedUser = await User.findByIdAndUpdate(
userId,
{ $set: req.body },
{
new: true,
runValidators: true,
select: '-password -__v'
}
);
if (!updatedUser) {
return res.status(404).json({ error: 'User not found' });
}
res.json(updatedUser);
} catch (error) {
console.error('User update error:', error);
res.status(500).json({ error: 'Update failed' });
}
}
);
// Admin endpoint with extended permissions
app.put('/api/admin/users/:id',
authenticateToken,
requireRole('admin'),
validateAndFilter(ADMIN_UPDATE_SCHEMA),
async (req, res) => {
try {
const userId = req.params.id;
const updatedUser = await User.findByIdAndUpdate(
userId,
{ $set: req.body },
{
new: true,
runValidators: true,
select: '-password -__v'
}
);
if (!updatedUser) {
return res.status(404).json({ error: 'User not found' });
}
// Log admin action
console.info('Admin user update:', {
adminId: req.user.id,
targetUserId: userId,
changes: req.body,
timestamp: new Date().toISOString()
});
res.json(updatedUser);
} catch (error) {
console.error('Admin user update error:', error);
res.status(500).json({ error: 'Update failed' });
}
}
);
// Secure registration endpoint
app.post('/api/register',
validateAndFilter(USER_CREATION_SCHEMA),
async (req, res) => {
try {
const userData = {
...req.body,
role: 'user',
isAdmin: false,
credits: 0,
isVerified: false,
isActive: true,
createdAt: new Date()
};
delete userData.acceptTerms;
const newUser = new User(userData);
await newUser.save();
const { password, ...userResponse } = newUser.toObject();
res.status(201).json({
message: 'User created successfully',
user: userResponse
});
} catch (error) {
if (error.code === 11000) {
res.status(409).json({ error: 'Username or email already exists' });
} else {
console.error('Registration error:', error);
res.status(500).json({ error: 'Registration failed' });
}
}
}
);
// Secure bulk update with strict validation
app.post('/api/admin/users/bulk-update',
authenticateToken,
requireRole('admin'),
async (req, res) => {
try {
const { users } = req.body;
if (!Array.isArray(users) || users.length === 0) {
return res.status(400).json({ error: 'Invalid users array' });
}
if (users.length > 100) {
return res.status(400).json({ error: 'Too many users in bulk operation' });
}
const results = [];
const errors = [];
for (const [index, userData] of users.entries()) {
try {
if (!userData.id) {
errors.push({ index, error: 'User ID required' });
continue;
}
// Validate each user's data
const { error, value } = ADMIN_UPDATE_SCHEMA.validate(
userData,
{ stripUnknown: true }
);
if (error) {
errors.push({
index,
userId: userData.id,
error: error.details[0].message
});
continue;
}
const updatedUser = await User.findByIdAndUpdate(
userData.id,
{ $set: value },
{
new: true,
runValidators: true,
select: '-password -__v'
}
);
if (updatedUser) {
results.push(updatedUser);
} else {
errors.push({
index,
userId: userData.id,
error: 'User not found'
});
}
} catch (updateError) {
errors.push({
index,
userId: userData.id,
error: updateError.message
});
}
}
// Log bulk admin action
console.info('Bulk user update:', {
adminId: req.user.id,
updatedCount: results.length,
errorCount: errors.length,
timestamp: new Date().toISOString()
});
res.json({
message: 'Bulk update completed',
updated: results.length,
errors: errors
});
} catch (error) {
console.error('Bulk update error:', error);
res.status(500).json({ error: 'Bulk update failed' });
}
}
);
// Separate endpoint for admin promotion (extra security)
app.post('/api/admin/users/:id/promote',
authenticateToken,
requireRole('admin'),
async (req, res) => {
try {
const userId = req.params.id;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
user.isAdmin = true;
user.role = 'admin';
await user.save();
// Log critical admin action
console.warn('User promoted to admin:', {
adminId: req.user.id,
promotedUserId: userId,
timestamp: new Date().toISOString()
});
res.json({ message: 'User promoted to admin' });
} catch (error) {
console.error('User promotion error:', error);
res.status(500).json({ error: 'Promotion failed' });
}
}
);