Command Injection in Hapi (Javascript)
Command Injection in Hapi with Javascript — how this specific combination creates or exposes the vulnerability
Command Injection occurs when an Hapi application passes untrusted data into a system shell or process-spawning function without proper validation or sanitization. In a JavaScript-based Hapi server, this typically arises from using child process utilities like child_process.exec or child_process.spawn with user-controlled input such as query parameters, headers, or request payloads.
Consider a route that echoes a user-supplied string into a shell command:
const Hapi = require('@hapi/hapi');
const { exec } = require('child_process');
const init = async () => {
const server = Hapi.server({ port: 3000 });
server.route({
method: 'GET',
path: '/ping',
handler: (request, h) => {
const name = request.query.name;
exec(`ping -c 1 ${name}`, (error, stdout, stderr) => {
if (error) {
return `Error: ${error.message}`;
}
return stdout;
});
return 'OK';
}
});
await server.start();
};
init();
In this example, the value of name is interpolated directly into the shell command string. An attacker can supply a value such as localhost; cat /etc/passwd, causing the server to execute additional commands. Because Hapi routes often handle external input, developers must remain vigilant about injection paths even when the framework does not introduce built-in command execution features.
The JavaScript runtime environment does not inherently protect against command injection; the risk is introduced by the developer’s use of low-level process execution APIs. middleBrick detects this pattern during its unauthenticated black-box scanning, flagging endpoints that reflect or execute shell commands with unchecked input. This aligns with the broader OWASP API Top 10 category of Broken Object Level Authorization and Improper Neutralization of Special Elements used in an OS Command context.
Unlike authenticated scans that test business logic flaws, middleBrick’s unauthenticated approach can still identify command injection when endpoint behaviors reveal command chaining or error-based leakage. The scanner does not fix the issue but provides prioritized findings with remediation guidance, helping teams understand how an attacker could manipulate inputs to achieve unintended execution.
Javascript-Specific Remediation in Hapi — concrete code fixes
To mitigate command injection in Hapi with JavaScript, avoid constructing shell commands with user input. Instead, prefer language-native operations or strict allowlists. When shell interaction is unavoidable, use parameterized APIs that separate arguments from commands.
1. Avoid shell metacharacters by using built-in modules
Replace shell-based operations with Node.js built-in modules. For network diagnostics, use the dns module instead of invoking ping:
const Hapi = require('@hapi/hapi');
const dns = require('dns');
const init = async () => {
const server = Hapi.server({ port: 3000 });
server.route({
method: 'GET',
path: '/resolve',
handler: async (request, h) => {
const hostname = request.query.host;
return new Promise((resolve, reject) => {
dns.lookup(hostname, (err, address) => {
if (err) {
return reject({ error: err.message });
}
resolve(address);
});
});
}
});
await server.start();
};
init();
2. Use execFile with argument array when shell execution is necessary
If you must execute an external binary, use child_process.execFile and pass arguments as an array. This prevents the shell from interpreting metacharacters:
const Hapi = require('@hapi/hapi');
const { execFile } = require('child_process');
const init = async () => {
const server = Hapi.server({ port: 3000 });
server.route({
method: 'GET',
path: '/version',
handler: (request, h) => {
const tool = request.query.tool;
// Only allow known tools to prevent path traversal or injection
const allowed = { 'node': true, 'npm': true };
if (!allowed[tool]) {
return { error: 'Invalid tool' };
}
execFile(tool, ['--version'], (error, stdout, stderr) => {
if (error) {
return { error: error.message };
}
return { output: stdout.trim() };
});
return 'OK';
}
});
await server.start();
};
init();
3. Validate and sanitize using an allowlist
When input must be used, enforce a strict allowlist rather than a blocklist. For example, if the input must be a numeric ID, ensure it matches a regular expression for integers only:
const Hapi = require('@hapi/hapi');
const { execFile } = require('child_process');
const isValidId = (value) => /^\d+$/.test(value);
const init = async () => {
const server = Hapi.server({ port: 3000 });
server.route({
method: 'GET',
path: '/user/{id}',
handler: (request, h) => {
const id = request.params.id;
if (!isValidId(id)) {
return { error: 'Invalid ID' };
}
execFile('id', [id], (error, stdout, stderr) => {
if (error) {
return { error: error.message };
}
return { info: stdout.trim() };
});
return 'OK';
}
});
await server.start();
};
init();
These JavaScript-specific practices reduce the attack surface by eliminating shell interpretation and enforcing strict input constraints. middleBrick’s checks complement these efforts by identifying endpoints where user data reaches process execution, supporting compliance mappings to OWASP API Top 10 and other frameworks.
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 |