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:
- 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. - Input validation testing - The scanner tests how the application handles unexpected characters, null bytes, and encoded path sequences that could bypass simple validation.
- Child process detection - middleBrick identifies endpoints that might execute system commands and tests them with safe payloads to detect command injection vulnerabilities.
- 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?
What's the difference between path traversal and container escape?
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.