Overview
CVE-2026-40291 documents an insecure direct object modification vulnerability in Chamilo LMS where an authenticated user with ROLE_STUDENT could escalate to ROLE_ADMIN by altering the roles field on their own user record via PUT /api/users/{id}. The underlying issue was that the API Platform security check is_granted('EDIT', object) only verified ownership, and the writable serialization group included roles, enabling arbitrary role changes. This is a canonical example of Broken Object Level Authorization (BOLA) and maps to a similar class of issues in Node.js/Express where a handler trusts client-supplied object changes rather than enforcing strict authorization. The CVE highlights CWE-269 (Incorrect Access Control) and CWE-863 (Incorrect Authorization). In Node.js/Express, a parallel vulnerability occurs when an endpoint updates a user document directly from req.body without proper permission checks, allowing privilege escalation through writable fields like roles.
Code Fix Example
Node.js (Express) API Security Remediation
/* Vulnerable */
const express = require('express');
const app = express();
app.use(express.json());
// Imagine a Mongoose-like User model
// const User = require('./models/User');
app.put('/api/users/:id', async (req, res) => {
// No explicit authorization check
const user = await User.findById(req.params.id);
if (!user) return res.status(404).send('Not found');
// Vulnerable: directly applying client-provided updates, including roles
Object.assign(user, req.body);
await user.save();
res.json(user);
});
/* Fixed */
const hasRole = (reqUser, role) => Array.isArray(reqUser?.roles) && reqUser.roles.includes(role);
app.put('/api/users/:id', async (req, res) => {
// Assumes req.user is populated by authentication middleware
const user = await User.findById(req.params.id);
if (!user) return res.status(404).send('Not found');
// Authorization: allow admins to update any user, or users to update non-sensitive fields only
if (req.user?.id !== user.id && !hasRole(req.user, 'ROLE_ADMIN')) {
return res.status(403).send('Forbidden');
}
// Whitelist safe fields; do not allow role changes by non-admins
const { roles, password, ...safeUpdates } = req.body;
Object.assign(user, safeUpdates);
// If an admin is performing the update and explicitly provides roles, apply them
if (hasRole(req.user, 'ROLE_ADMIN') && typeof roles !== 'undefined') {
user.roles = roles;
}
await user.save();
res.json(user);
});