Container Escape in Koa (Javascript)
Container Escape in Koa with Javascript
When a Koa application runs inside a container, the runtime environment may expose host filesystem, network interfaces, or process metadata through misconfigured endpoints. If the Koa server does not validate file paths or restrict access to sensitive resources, an attacker who can trigger a request to a vulnerable route may achieve a container escape. This typically occurs when the application uses fs or child_process modules to read arbitrary files or execute shell commands based on user-supplied input.
For example, consider a route that reads a configuration file based on a query parameter:
const Koa = require('koa');
const fs = require('fs');
const app = new Koa();
app.use(async (ctx) => {
const filePath = ctx.query.file;
const absolutePath = `/app/config/${filePath}`;
const data = fs.readFileSync(absolutePath, 'utf8');
ctx.body = { content: data };
});
app.listen(3000);
An attacker can send a request like http://target.com/?file=../../../../etc/passwd to retrieve sensitive host files. If the container runs with sufficient privileges, the attacker may also escape the container by writing a malicious binary to a mounted volume or by invoking a shell command via child_process. Since Koa does not enforce path sanitization by default, such attacks are feasible when input is passed directly to filesystem or process APIs.
This pattern violates OWASP API Top 10 category Broken Object Level Authorization and can map to CVE-2021-44228 (Log4Shell) when logging frameworks are used unsafely in Koa middleware. The vulnerability is not inherent to Koa itself but to how developers handle untrusted input in combination with container-mounted resources.
Javascript-Specific Remediation in Koa
To prevent container escape via path traversal or command injection in Koa, developers must validate and sanitize all inputs before using them with filesystem or process operations. Use a whitelist of allowed paths and avoid resolving user input directly to filesystem paths.
Here is a corrected implementation that prevents directory traversal:
const Koa = require('koa');
const fs = require('fs');
const path = require('path');
const ALLOWED_BASE = '/app/config/';
const ALLOWED_FILES = ['settings.json', 'app.config']; // Example whitelist
app.use(async (ctx) => {
const requestedFile = ctx.query.file;
const isAllowed = ALLOWED_FILES.includes(requestedFile);
if (!isAllowed) {
ctx.status = 400;
ctx.body = { error: 'Invalid file request' };
return;
}
const safePath = path.join(ALLOWED_BASE, requestedFile);
if (!safePath.startsWith(ALLOWED_BASE)) {
ctx.status = 403;
ctx.body = { error: 'Access denied' };
return;
}
try {
const data = fs.readFileSync(safePath, 'utf8');
ctx.body = { content: data };
} catch (err) {
ctx.status = 500;
ctx.body = { error: 'File not found' };
}
});
app.listen(3000);
Additionally, never pass unsanitized input to child_process methods. If command execution is necessary, use argument-based invocation instead of shell-based parsing to avoid injection:
const { exec } = require('child_process');
// Safe: exec('echo ' + userInput, { shell: true }); // Avoid if possible
// Better: exec('echo', [userInput], { shell: false });
By enforcing strict input validation, path resolution checks, and avoiding shell interpreters, Koa applications reduce the risk of container escape when deployed in shared or cloud environments.