Insecure Deserialization in Hapi (Javascript)
Insecure Deserialization in Hapi with Javascript — how this specific combination creates or exposes the vulnerability
Insecure deserialization occurs when an application processes untrusted serialized data and reconstructs objects without sufficient validation. In a Hapi application using JavaScript, this typically arises through route handlers that accept and deserialize JSON payloads, form-encoded bodies, or custom formats via plugins. Because JavaScript is dynamically typed, deserializing data (e.g., via JSON.parse, custom revivers, or libraries such as qs or busboy) can lead to prototype pollution or execution of unintended constructors if the input is not strictly validated. Attackers may craft serialized payloads that inject properties into Object.prototype or other shared objects, leading to unexpected behavior when the application later uses those objects. Hapi does not inherently sanitize incoming payloads; it relies on developers to validate and sanitize data before deserialization. If developers use generic deserialization routines on user-controlled input, such as accepting raw JSON in a handler without schema checks, the attack surface expands. For example, an endpoint that accepts POST data and directly deserializes it with JSON.parse can be targeted via prototype pollution, where special keys like __proto__ or constructor are abused to modify object behavior. In Hapi, routes that accept payloads with payload parsing strategies (e.g., payload: { parse: true }) can inadvertently expose these risks if the resulting objects are merged into application logic or reused across requests. Additionally, if the server caches or shares deserialized objects between requests, tainted objects may persist across the application lifecycle. The vulnerability is compounded when the deserialized data is used in sensitive operations such as authentication checks, object updates, or access control decisions. Because Hapi is often used to build APIs that integrate with databases or microservices, insecure deserialization can lead to privilege escalation, data tampering, or unauthorized access. Attack patterns include sending deeply nested structures that cause excessive resource consumption or injecting functions via custom parsers, leading to unexpected code execution paths. The risk is especially pronounced when the application uses third-party plugins that introduce their own deserialization logic without enforcing strict schemas.
Javascript-Specific Remediation in Hapi — concrete code fixes
To mitigate insecure deserialization in Hapi with JavaScript, enforce strict schema validation and avoid generic deserialization of untrusted data. Use Joi or another schema validation library to validate incoming payloads before any deserialization logic. Hapi's payload parsing can be combined with explicit validation to ensure only expected shapes are accepted. Below are concrete, secure patterns.
Secure route definition with Joi validation
Define a route that validates payloads using Joi. This ensures the deserialized object conforms to expected types and rejects unexpected properties, including prototype pollution keys.
const Hapi = require('@hapi/hapi');
const Joi = require('joi');
const init = async () => {
const server = Hapi.server({ port: 4000, host: 'localhost' });
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
age: Joi.number().integer().min(0).max(120),
email: Joi.string().email().required()
}).unknown(false) // reject extra keys, including __proto__
},
handler: (request, h) => {
// request.payload is already validated and safe to use
const { username, age, email } = request.payload;
// Proceed with safe operations, e.g., store in database
return { status: 'ok', username, email };
}
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
init().catch(err => {
console.error(err);
});
Avoiding dangerous custom revivers and deserialization
If you must parse non-JSON formats, avoid custom revivers that use eval or Function constructors. Prefer built-in JSON.parse without a reviver, or use a safe library. Never directly merge deserialized objects into the prototype chain.
// Unsafe: custom reviver that can be abused
// const obj = JSON.parse(userInput, (key, value) => {
// if (key === 'constructor' && typeof value === 'function') {
// return value; // dangerous: may execute arbitrary code
// }
// return value;
// });
// Safe: strict JSON parsing with no reviver and additional checks
const safeParse = (input) => {
let parsed;
try {
parsed = JSON.parse(input);
} catch (err) {
throw new Error('Invalid JSON');
}
// Ensure no prototype pollution keys
if (parsed && typeof parsed === 'object') {
if (parsed.__proto__ !== undefined || parsed.constructor !== undefined) {
throw new Error('Potential prototype pollution detected');
}
}
return parsed;
};
// Example usage in a handler
server.route({
method: 'POST',
path '/data',
options: {
payload: {
parse: false // handle manually after validation
},
handler: (request, h) => {
const raw = request.payload.toString('utf8');
const data = safeParse(raw);
// use data safely
return { received: Object.keys(data) };
}
}
});
General secure practices in Hapi
- Set
payload: { parse: true }and rely on Joi to reject unexpected keys. - Do not use
Object.assignor spread operators to merge user input into prototypes or global objects. - Keep dependencies updated; audit third-party plugins that may introduce custom deserialization.
- Use
unknown(false)in Joi schemas to ensure extra properties are rejected, mitigating prototype pollution attempts.