Path Traversal in Hapi
How Path Traversal Manifests in Hapi
Path traversal vulnerabilities in Hapi applications typically arise from improper handling of file system operations where user-controlled input determines file paths. Hapi's routing system and handler patterns create specific scenarios where these vulnerabilities become exploitable.
The most common pattern involves using request.params or request.query values directly in file system operations without proper validation. Consider a file download endpoint:
server.route({
method: 'GET',
path: '/download/{filename}',
handler: (request, h) => {
const filePath = `/var/files/${request.params.filename}`;
return h.file(filePath);
}
});An attacker can request /download/../../../etc/passwd to traverse outside the intended directory. Hapi's path parameter parsing doesn't inherently prevent these sequences.
Another Hapi-specific pattern involves dynamic view rendering with user input:
server.views({
engines: { html: require('handlebars') },
path: './templates'
});If a handler uses user input to determine which template to render:
const templateName = request.query.template;
server.render(templateName, context);An attacker could request ?template=../../package.json to read arbitrary files if the view engine allows it.
Static file serving with dynamic paths presents another attack surface:
server.route({
method: 'GET',
path: '/static/{path*}',
handler: {
directory: {
path: './public',
listing: true
}
}
});The wildcard path parameter {path*} can be exploited with sequences like /static/../../package.json to access files outside the public directory.
Hapi's plugin system can introduce path traversal through plugins that handle file uploads or dynamic content generation. A plugin that saves uploaded files using user-provided names without sanitization creates an obvious vulnerability:
const upload = require('hapi-upload');
server.register({ plugin: upload });
server.route({
method: 'POST',
path: '/upload',
handler: (request, h) => {
const file = request.payload.file;
const filename = file.hapi.filename; // User-controlled
const savePath = `/uploads/${filename}`;
// Save file without validation
return { success: true };
}
});The h.file() response toolkit method itself doesn't validate paths, making it dangerous when combined with user input. Hapi's architecture allows these patterns to emerge naturally when developers prioritize convenience over security.
Hapi-Specific Detection
Detecting path traversal in Hapi applications requires examining both the routing configuration and handler implementations. Static analysis can identify risky patterns, but runtime scanning provides comprehensive coverage.
Code review should focus on these Hapi-specific patterns:
// Dangerous: direct parameter usage
const filePath = `/uploads/${request.params.filename}`;
// Safer: validate before use
const filename = request.params.filename;
if (!/^[a-zA-Z0-9_.-]+$/.test(filename)) {
return h.response('Invalid filename').code(400);
}
const filePath = `/uploads/${filename}`;Scan for wildcard path parameters {path*} that allow arbitrary path segments. These parameters can match multiple path segments, making traversal attacks more flexible.
middleBrick's scanning approach for Hapi applications includes:
- Endpoint discovery through Hapi's routing table (if accessible)
- Parameter fuzzing with traversal payloads like
../,..∖, and URL-encoded variants - Response analysis for directory listing indicators or file content leaks
- Content-type checking to distinguish between intended responses and unintended file disclosures
The scanner tests common traversal sequences:
../etc/passwd
..\windows\system32\drivers\etc\hosts
./././boot.ini
%2e%2e%2fetc%2fpasswd
%2e%2e%5cwindows%5c%2fFor Hapi applications using plugins, middleBrick analyzes plugin configurations that might introduce file handling capabilities. The scanner checks for:
- Directory listing enabled on file routes
- Static file serving with broad path patterns
- Upload handlers with user-controlled filenames
- Template rendering with dynamic template names
Runtime detection benefits from middleBrick's black-box scanning methodology. Since no credentials or source code access is required, the scanner can identify vulnerabilities in production APIs immediately. The 5-15 second scan time means you can test multiple endpoints rapidly without service disruption.
middleBrick's LLM security checks are particularly relevant for Hapi applications using AI features. If your Hapi server exposes AI endpoints, the scanner tests for prompt injection and system prompt leakage that could be combined with path traversal for more sophisticated attacks.
Hapi-Specific Remediation
Remediating path traversal in Hapi requires a defense-in-depth approach combining input validation, path normalization, and secure defaults. Hapi provides several native mechanisms to prevent these vulnerabilities.
The most effective approach is using Hapi's built-in validation with Joi schemas:
const Joi = require('@hapi/joi');
server.route({
method: 'GET',
path: '/download/{filename}',
options: {
validate: {
params: Joi.object({
filename: Joi.string()
.regex(/^[a-zA-Z0-9_.-]+$/) // Alphanumeric, dots, underscores, hyphens
.max(255)
})
}
},
handler: (request, h) => {
const filePath = `/var/files/${request.params.filename}`;
return h.file(filePath);
}
});This validation rejects traversal attempts before they reach the handler logic. The regex pattern ensures only safe characters are accepted.
For dynamic paths, use Hapi's path normalization utilities:
const Path = require('path');
server.route({
method: 'GET',
path: '/static/{path*}',
handler: (request, h) => {
const userPath = request.params.path.join('/') || 'index.html';
const safePath = Path.normalize(userPath);
const fullPath = Path.join(__dirname, 'public', safePath);
// Verify the resolved path is within the intended directory
if (!fullPath.startsWith(Path.join(__dirname, 'public'))) {
return h.response('Forbidden').code(403);
}
return h.file(fullPath);
}
});The Path.normalize() call resolves ../ sequences, and the prefix check ensures the final path stays within the public directory.
For file upload handling, sanitize filenames systematically:
const sanitize = require('sanitize-filename');
server.route({
method: 'POST',
path: '/upload',
handler: async (request, h) => {
const file = request.payload.file;
const originalName = file.hapi.filename;
// Sanitize filename and generate safe version
const safeName = sanitize(originalName);
const timestamp = Date.now();
const finalName = `${timestamp}_${safeName}`;
const savePath = Path.join(__dirname, 'uploads', finalName);
// Save file securely
await file.pipe(fs.createWriteStream(savePath));
return { success: true, filename: finalName };
}
});The sanitize-filename package removes dangerous characters and path separators, while the timestamp prefix prevents name collisions and obscures original filenames.
When using h.file(), always construct paths with Path.join() and validate the result:
handler: (request, h) => {
const filename = request.params.filename;
const safeFilename = sanitize(filename);
const filePath = Path.join(__dirname, 'files', safeFilename);
// Double-check path is within bounds
if (!filePath.startsWith(Path.join(__dirname, 'files'))) {
return h.response('Invalid path').code(400);
}
return h.file(filePath);
}For template rendering, use a whitelist approach:
const validTemplates = ['home', 'about', 'contact', 'profile'];
server.route({
method: 'GET',
path: '/page/{template}',
handler: (request, h) => {
const template = request.params.template;
if (!validTemplates.includes(template)) {
return h.response('Template not found').code(404);
}
return h.view(template, { /* context */ });
}
});This approach ensures only pre-approved templates can be rendered, eliminating the traversal vector entirely.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |
Frequently Asked Questions
How does middleBrick scan for path traversal in Hapi applications?
../ sequences, URL-encoded variants, and Windows-specific traversal attempts. The scanner examines response content types and payloads to detect when files are being read outside intended directories. Since no credentials or source code access is needed, middleBrick can scan production Hapi APIs in 5-15 seconds and identify vulnerabilities immediately.