HIGH insecure deserializationhapi

Insecure Deserialization in Hapi

How Insecure Deserialization Manifests in Hapi

Insecure deserialization in Hapi applications typically occurs when untrusted data is deserialized without proper validation or when using unsafe deserialization methods. Hapi's plugin architecture and request handling can create multiple attack surfaces for deserialization vulnerabilities.

The most common manifestation is through request payloads that contain serialized objects. Hapi's payload processing can automatically parse JSON, form data, and other formats, but developers sometimes extend this to handle custom serialized formats or use unsafe deserialization libraries.

A typical vulnerable pattern in Hapi looks like this:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  server.route({
    method: 'POST',
    path: '/deserialize',
    handler: async (request, h) => {
      const unsafeData = request.payload.data;
      
      // UNSAFE: Directly deserializing untrusted input
      const obj = JSON.parse(unsafeData);
      
      return obj.process();
    }
  });

  await server.start();
};
init();

This pattern is dangerous because JSON.parse() can execute arbitrary code if the input contains malicious objects with overridden prototypes or getter/setter methods that trigger side effects.

Another Hapi-specific vulnerability occurs when using plugins that handle serialization. For example, the hapi-auth-jwt2 plugin can be misconfigured to accept untrusted JWT tokens without proper signature verification:

const Hapi = require('@hapi/hapi');
const jwt = require('jsonwebtoken');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  server.route({
    method: 'POST',
    path: '/verify-jwt',
    handler: async (request, h) => {
      const token = request.payload.token;
      
      // UNSAFE: Verifying without proper secret/key
      const decoded = jwt.verify(token, 'weak-secret');
      
      return decoded;
    }
  });

  await server.start();
};
init();

Attackers can exploit this by crafting JWT tokens with manipulated payloads that, when deserialized, execute malicious code or trigger unintended behavior.

Hapi's validation system using @hapi/joi can also be a vector if custom validation schemas deserialize unsafe data:

const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  server.route({
    method: 'POST',
    path: '/validate',
    options: {
      validate: {
        payload: Joi.object({
          data: Joi.string().required()
        })
      }
    },
    handler: async (request, h) => {
      const unsafeData = request.payload.data;
      
      // UNSAFE: Custom parsing that may execute code
      const obj = require('unsafe-serializer').deserialize(unsafeData);
      
      return obj;
    }
  });

  await server.start();
};
init();

The vulnerability here is using third-party serializers that may have unsafe deserialization methods, especially if they support executing code during deserialization.

Hapi-Specific Detection

Detecting insecure deserialization in Hapi requires examining both the application code and runtime behavior. Here's how to identify these vulnerabilities specifically in Hapi applications.

Static code analysis should focus on these Hapi-specific patterns:

# Search for unsafe deserialization patterns
grep -r 'JSON\.parse(' . --include='*.js'
grep -r 'deserialize(' . --include='*.js'
grep -r 'jwt\.verify(' . --include='*.js'
grep -r 'require(' . --include='*.js' | grep -i 'serializer'

Look for Hapi-specific patterns like:

// Dangerous patterns to flag
server.route({
  method: 'POST',
  path: '/api',
  handler: (request, h) => {
    const payload = request.payload;
    // Check if payload is directly deserialized
    const obj = unsafeDeserialize(payload.data);
    return obj;
  }
});

// Plugin configuration issues
const plugin = {
  name: 'bad-plugin',
  register: (server, options) => {
    // Check if plugin uses unsafe deserialization
    server.ext('onRequest', (request, h) => {
      const unsafe = request.headers['x-custom-data'];
      const obj = JSON.parse(unsafe); // Vulnerable
      return h.continue;
    });
  }
};

Runtime detection with middleBrick specifically identifies Hapi deserialization vulnerabilities by:

  • Scanning for endpoints that accept serialized data without proper validation
  • Testing for prototype pollution through deserialization
  • Checking for unsafe JSON parsing with malicious payloads
  • Verifying JWT implementations for weak signature verification
  • Testing for command injection through deserialization

middleBrick's black-box scanning approach tests the actual API surface without requiring source code access. For Hapi applications, this means:

# Scan a Hapi API endpoint
middlebrick scan https://api.example.com --report=json

# Scan with specific focus on deserialization vulnerabilities
middlebrick scan https://api.example.com --test=deserialization

The scanner sends crafted payloads to test for deserialization vulnerabilities, including:

  • Malicious JSON objects with prototype pollution attempts
  • JSON with overridden getter/setter methods
  • Invalid JWT tokens with manipulated payloads
  • Serialized objects with unexpected types

middleBrick provides a security score (A-F) and specific findings for each vulnerability detected, with remediation guidance tailored to Hapi applications.

Hapi-Specific Remediation

Remediating insecure deserialization in Hapi requires both code fixes and architectural changes. Here are specific solutions for Hapi applications.

First, implement strict input validation using Hapi's built-in validation system:

const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  server.route({
    method: 'POST',
    path: '/safe-endpoint',
    options: {
      validate: {
        payload: Joi.object({
          data: Joi.string().trim().required()
            .pattern(/^[\w\s,.\-]+$/) // Allow only safe characters
        })
      }
    },
    handler: async (request, h) => {
      const safeData = request.payload.data;
      
      // SAFE: No deserialization of untrusted data
      return { success: true, data: safeData };
    }
  });

  await server.start();
};
init();

For endpoints that must handle serialized data, use safe deserialization methods:

const Hapi = require('@hapi/hapi');

const safeDeserialize = (data) => {
  try {
    // Only allow specific safe types
    const parsed = JSON.parse(data, (key, value) => {
      if (typeof value === 'object') {
        // Reject objects with dangerous properties
        if (Object.keys(value).some(k => k.startsWith('__'))) {
          throw new Error('Unsafe property detected');
        }
      }
      return value;
    });
    
    return parsed;
  } catch (err) {
    return null;
  }
};

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  server.route({
    method: 'POST',
    path: '/safe-deserialize',
    handler: async (request, h) => {
      const data = request.payload.data;
      
      // SAFE: Using controlled deserialization
      const obj = safeDeserialize(data);
      
      if (!obj) {
        return h.response({ error: 'Invalid data' }).code(400);
      }
      
      return obj;
    }
  });

  await server.start();
};
init();

For JWT handling in Hapi, use the hapi-auth-jwt2 plugin correctly with proper secret management:

const Hapi = require('@hapi/hapi');
const jwt = require('hapi-auth-jwt2');

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  await server.register(jwt);

  server.auth.strategy('jwt', 'jwt', {
    key: process.env.JWT_SECRET, // Use strong, environment-specific secret
    validate: async (decoded, request) => {
      // Validate token claims and expiration
      return { isValid: true };
    },
    verifyOptions: { 
      algorithms: ['HS256'] 
    }
  });

  server.route({
    method: 'POST',
    path: '/protected',
    options: {
      auth: 'jwt'
    },
    handler: async (request, h) => {
      return { authenticated: true, user: request.auth.credentials };
    }
  });

  await server.start();
};
init();

Implement content security policies and strict Content-Type headers to prevent unexpected deserialization:

const init = async () => {
  const server = Hapi.server({ port: 3000 });

  server.ext('onRequest', (request, h) => {
    const contentType = request.headers['content-type'];
    
    // Only allow specific content types
    if (!['application/json', 'application/x-www-form-urlencoded'].includes(contentType)) {
      return h.response({ error: 'Unsupported content type' }).code(415);
    }
    
    return h.continue;
  });

  await server.start();
};
init();

For plugin development, ensure plugins don't introduce deserialization vulnerabilities:

const safePlugin = {
  name: 'safe-plugin',
  version: '1.0.0',
  register: (server, options) => {
    server.ext('onRequest', (request, h) => {
      // SAFE: No deserialization of request data
      return h.continue;
    });
  }
};

module.exports = safePlugin;

Frequently Asked Questions

How can I test my Hapi application for insecure deserialization vulnerabilities?
Use middleBrick's black-box scanning by running middlebrick scan https://yourapi.com which tests for deserialization vulnerabilities without requiring source code access. For manual testing, use tools like Burp Suite to send malicious JSON payloads with prototype pollution attempts and observe if the application behaves unexpectedly or crashes.
What's the difference between JSON.parse() and safe deserialization in Hapi?
JSON.parse() is vulnerable if it processes untrusted data containing malicious objects with overridden prototypes or getter/setter methods that trigger side effects. Safe deserialization uses strict validation, reviver functions that reject dangerous properties, and only allows specific safe data types. In Hapi, always validate with Joi schemas before any parsing and use try-catch blocks with proper error handling.