Insecure Deserialization in Hapi with Mongodb
Insecure Deserialization in Hapi with Mongodb — how this specific combination creates or exposes the vulnerability
Insecure deserialization occurs when an application processes untrusted data into an object without proper validation or type checks. In a Hapi application using MongoDB as a data store, the risk arises when server-side code deserializes user-controlled input—such as cookies, query parameters, or request payloads—into complex JavaScript objects before using them in MongoDB operations. If an attacker can manipulate the structure or contents of the deserialized data, they may inject malicious objects that alter query behavior or escalate privileges.
Hapi does not enforce strict schema validation on request payloads by default. If a route relies on generic JavaScript object parsing (for example, via JSON.parse or framework-assisted transformations) and then passes that object directly to MongoDB driver methods like find, updateOne, or aggregate, attacker-controlled keys can change query semantics. For instance, an attacker might embed a $where or $eval operator in a nested object if the application does not sanitize or whitelist fields. Although MongoDB server-side JavaScript evaluation features like $where are often disabled in hardened deployments, reliance on deserialized input to construct query filters or update pipelines can still lead to NoSQL injection or unexpected data access patterns.
Another vector specific to Hapi with MongoDB involves session or authentication token handling. If your Hapi application stores session data or user claims in cookies and deserializes them into objects for authorization checks, an attacker who can tamper with the cookie content may forge roles or permissions. When those claims are later used to build MongoDB queries—such as filtering documents by a userId field derived from deserialized claims—a malicious user could modify the deserialized structure to reference other users’ data, leading to Insecure Direct Object References (IDOR) or Broken Object Level Authorization (BOLA).
Even when using higher-level ODM libraries that sit atop the MongoDB driver, deserialization risks persist if the library’s parsing logic is influenced by raw input. For example, passing a user-supplied object to an update method that uses dot notation or replacement semantics can inadvertently modify fields the attacker did not intend to touch. The OWASP API Top 10 lists injection-related API vulnerabilities broadly, and insecure deserialization is a common root cause for injection in API endpoints, including those implemented in Hapi backed by MongoDB.
Mongodb-Specific Remediation in Hapi — concrete code fixes
To mitigate deserialization and injection risks in a Hapi application using MongoDB, prefer strict schema validation, explicit field selection, and avoiding direct use of deserialized objects in MongoDB operations. Below are concrete, safe patterns with working code examples.
1. Validate and sanitize input with a strict schema
Use a validation library (such as Join, which is built into Hapi) to define an exact shape for incoming data and reject any extraneous fields that could be abused for injection.
const Hapi = require('@hapi/hapi');
const Joi = require('joi');
const userSchema = Joi.object({
userId: Joi.string().pattern(/^[a-f0-9]{24}$/).required(),
email: Joi.string().email().required(),
// explicitly deny any additional keys
}).unknown(false);
const server = Hapi.server({ port: 4000 });
server.route({
method: 'POST',
path: '/users/{id}',
options: {
validate: {
params: Joi.object({ id: Joi.string().pattern(/^[a-f0-9]{24}$/) }),
payload: userSchema
}
},
handler: async (request, h) => {
const { userId, email } = request.payload;
const { id } = request.params;
// Ensure the payload userId matches the URL parameter to prevent IDOR
if (userId !== id) {
throw Boom.forbidden('Cannot modify another user');
}
const { MongoClient } = require('mongodb');
const uri = process.env.MONGODB_URI;
const client = new MongoClient(uri);
await client.connect();
const db = client.db('appdb');
// Use the validated, typed values only
const result = await db.collection('users').updateOne(
{ _id: new ObjectId(id) },
{ $set: { email } }
);
await client.close();
if (result.matchedCount === 0) {
throw Boom.notFound('User not found');
}
return { status: 'ok' };
}
});
2. Avoid passing deserialized objects directly to update operations
Never forward an entire request body as an update document. Instead, explicitly map only the allowed fields.
server.route({
method: 'PATCH',
path: '/users/{id}/profile',
options: {
validate: {
params: Joi.object({ id: Joi.string().pattern(/^[a-f0-9]{24}$/) }),
payload: Joi.object({
displayName: Joi.string().max(100),
bio: Joi.string().max(500)
// other fields omitted — no $set, $inc, etc. from client
}).unknown(false)
}
},
handler: async (request, h) => {
const { id } = request.params;
const { displayName, bio } = request.payload;
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
const db = client.db('appdb');
// Explicit field mapping prevents injection via nested operators
const result = await db.collection('users').updateOne(
{ _id: new ObjectId(id) },
{ $set: { displayName, bio } }
);
await client.close();
if (result.matchedCount === 0) {
throw Boom.notFound('Profile not found');
}
return { status: 'updated' };
}
});
3. Do not construct query filters from raw deserialized objects
Avoid patterns where an object from a cookie or body is merged into a MongoDB filter. Instead, extract only known-safe values.
server.route({
method: 'GET',
path: '/users/me',
options: {
auth: 'session'
},
handler: async (request, h) => {
// request.auth.credentials should already be validated by your auth strategy
const userId = request.auth.credentials.userId;
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
const db = client.db('appdb');
// Use a trusted scalar value, not a deserialized object
const user = await db.collection('users').findOne({ _id: new ObjectId(userId) });
await client.close();
if (!user) {
throw Boom.notFound('User not found');
}
return user;
}
});
4. Disable server-side JavaScript evaluation in MongoDB when possible
Ensure operators like $where, $eval, and $map with function expressions are not used. If you must use them, validate and sanitize any inputs that feed into those expressions rigorously.
5. Use MongoDB driver options that reduce risk
Configure the MongoDB driver to disallow evaluation of JavaScript expressions where feasible, and keep your driver and server versions up to date to benefit from security patches related to query parsing and execution.