HIGH container escapehapi

Container Escape in Hapi

How Container Escape Manifests in Hapi

Container escape vulnerabilities in Hapi applications typically occur when the framework's file system operations or child process handling are exploited to break out of the container's isolation boundaries. The most common manifestation involves Hapi's reply.file() method or reply.download() method being used to serve files without proper path validation, allowing attackers to traverse outside the intended directory structure.

A classic example is when Hapi servers use reply.file() with user-controlled paths:

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

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

  server.route({
    method: 'GET',
    path: '/download/{filename}',
    handler: async (request, h) => {
      const filename = request.params.filename;
      return h.file(`/var/www/files/${filename}`); // Vulnerable to path traversal
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};
init();

This code allows an attacker to request /download/../../etc/passwd, potentially exposing sensitive files outside the container's intended file system boundaries. The vulnerability becomes a container escape when the container runs with elevated privileges or mounted volumes that expose the host file system.

Another Hapi-specific pattern involves using reply.download() with dynamic content generation:

server.route({
  method: 'GET',
  path: '/export/{type}',
  handler: async (request, h) => {
    const type = request.params.type;
    const filePath = `/tmp/${type}.csv`;
    
    // Generate file content
    await generateCsv(type, filePath);
    
    return h.download(filePath, `${type}_export.csv`);
  }
});

If type is user-controlled and the application doesn't validate it properly, an attacker could manipulate the path to access files outside the intended directory, potentially exposing host system files if the container has mounted volumes.

Child process execution through Hapi's request handlers presents another attack vector. When Hapi applications use child_process.exec() or child_process.spawn() with user input:

server.route({
  method: 'POST',
  path: '/run-command',
  handler: async (request, h) => {
    const { command } = request.payload;
    
    // Direct command execution - extremely dangerous
    const { exec } = require('child_process');
    exec(command, (error, stdout, stderr) => {
      return h.response(stdout);
    });
  }
});

This pattern allows attackers to execute arbitrary commands within the container, potentially breaking out by mounting host volumes or accessing Docker socket files if they exist within the container's file system.

Hapi-Specific Detection

Detecting container escape vulnerabilities in Hapi applications requires examining both the application code and runtime behavior. Static analysis tools can identify dangerous patterns, but runtime scanning with middleBrick provides comprehensive coverage by testing actual API endpoints.

middleBrick's scanning approach for Hapi applications includes:

  1. Path traversal testing - middleBrick automatically tests for directory traversal attempts by appending ../ sequences to file-serving endpoints, attempting to access sensitive files like /etc/passwd, /proc/self/cwd, and /proc/self/root.
  2. Input validation testing - The scanner tests how the application handles unexpected characters, null bytes, and encoded path sequences that could bypass simple validation.
  3. Child process detection - middleBrick identifies endpoints that might execute system commands and tests them with safe payloads to detect command injection vulnerabilities.
  4. LLM security testing - For Hapi applications using AI features, middleBrick tests for system prompt leakage and prompt injection vulnerabilities that could lead to unauthorized data access.

Running middleBrick against a Hapi API is straightforward:

npm install -g middlebrick
middlebrick scan https://your-hapi-api.com

The scanner will test all exposed endpoints and provide a security score with detailed findings. For Hapi applications specifically, middleBrick checks for:

  • Authentication bypass - Testing if authentication mechanisms can be circumvented to access restricted file operations
  • Authorization flaws - Verifying that users cannot access files or execute commands beyond their permissions
  • Input validation - Testing for SQL injection, XSS, and other injection attacks that could be combined with container escape attempts
  • Data exposure - Checking if sensitive files or system information can be accessed through API endpoints

The output includes severity levels and specific remediation guidance for each finding. Critical findings related to container escape would include exact request paths and payloads that demonstrate the vulnerability.

Hapi-Specific Remediation

Securing Hapi applications against container escape vulnerabilities requires a defense-in-depth approach with proper input validation, secure file handling, and principle of least privilege. Here are Hapi-specific remediation patterns:

1. Secure File Serving with Path Validation

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

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

  const BASE_DIR = path.join(__dirname, 'files');

  server.route({
    method: 'GET',
    path: '/download/{filename}',
    handler: async (request, h) => {
      const filename = request.params.filename;
      
      // Validate filename - only allow alphanumeric and basic punctuation
      if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {
        return h.response('Invalid filename').code(400);
      }
      
      const filePath = path.join(BASE_DIR, filename);
      
      // Ensure the resolved path is within the base directory
      if (!filePath.startsWith(BASE_DIR)) {
        return h.response('Invalid path').code(400);
      }
      
      // Verify file exists and is a file
      if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
        return h.response('File not found').code(404);
      }
      
      return h.file(filePath);
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};
init();

2. Safe Child Process Execution

server.route({
  method: 'POST',
  path: '/run-command',
  handler: async (request, h) => {
    const { command } = request.payload;
    
    // Whitelist allowed commands
    const allowedCommands = ['ls', 'cat', 'grep'];
    const [cmd, ...args] = command.split(' ');
    
    if (!allowedCommands.includes(cmd)) {
      return h.response('Command not allowed').code(400);
    }
    
    // Use execFile instead of exec for better security
    const { execFile } = require('child_process');
    
    return new Promise((resolve, reject) => {
      execFile(cmd, args, { cwd: '/safe/directory' }, (error, stdout, stderr) => {
        if (error) {
          return reject(h.response(error.message).code(500));
        }
        resolve(h.response(stdout));
      });
    });
  }
});

3. Content Security Policy and Headers

While not directly preventing container escape, adding security headers helps protect against related attacks:

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

  // Register security plugin
  await server.register(require('@hapi/iron'));

  server.ext('onRequest', (request, h) => {
    // Set security headers
    request.response.headers['X-Content-Type-Options'] = 'nosniff';
    request.response.headers['X-Frame-Options'] = 'DENY';
    request.response.headers['X-XSS-Protection'] = '1; mode=block';
    
    return h.continue;
  });

  // ... routes
};

4. Input Validation with Joi

Hapi integrates well with Joi for comprehensive input validation:

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

server.route({
  method: 'POST',
  path: '/upload',
  options: {
    validate: {
      payload: Joi.object({
        filename: Joi.string().regex(/^[a-zA-Z0-9._-]+$/).required(),
        content: Joi.string().max(1000000).required()
      })
    }
  },
  handler: async (request, h) => {
    const { filename, content } = request.payload;
    const filePath = path.join(BASE_DIR, filename);
    
    // Write file safely
    await fs.promises.writeFile(filePath, content);
    return h.response('File uploaded successfully');
  }
});

5. Container Runtime Security

While not Hapi-specific, ensure your container runs with proper security contexts:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
readOnlyRootFilesystem: true
volumeMounts:
- name: app-data
  mountPath: /var/www/files
  readOnly: false

This configuration prevents the container from accessing host resources and limits file system writes to specific volumes.

Frequently Asked Questions

How can I test my Hapi API for container escape vulnerabilities?
Use middleBrick to scan your API endpoints. The scanner tests for path traversal, command injection, and other container escape patterns by sending malicious payloads to file-serving and command-execution endpoints. middleBrick provides specific findings with severity levels and remediation guidance, helping you identify and fix vulnerabilities before they can be exploited.
What's the difference between path traversal and container escape?
Path traversal is a technique that allows attackers to access files outside the intended directory, while container escape is the successful exploitation that breaks out of container isolation. In Hapi applications, path traversal vulnerabilities (like using reply.file() with unvalidated paths) can lead to container escape if the container has mounted host volumes or elevated privileges. middleBrick tests for both the traversal technique and the potential for escape by checking what files can be accessed.