Container Escape in Restify
How Container Escape Manifests in Restify
Restify is a lightweight Node.js framework for building RESTful APIs. By itself it does not create container isolation boundaries, but the handlers you attach to Restify routes can inadvertently expose paths that lead to a container escape when the container runs with elevated privileges or has sensitive mounts (e.g., the Docker socket).
The most common pattern is a route that accepts user‑supplied input and passes it directly to a Node.js child process without proper sanitization. If the container is privileged or has /var/run/docker.sock mounted, an attacker can execute arbitrary host commands, effectively breaking out of the container.
const restify = require('restify');
const { exec } = require('child_process');
const server = restify.createServer();
// Vulnerable endpoint: executes arbitrary shell commands
server.post('/run', (req, res, next) => {
const cmd = req.body.command; // user‑controlled string
exec(cmd, (error, stdout, stderr) => {
if (error) {
res.send(500, { error: error.message });
} else {
res.send(200, { output: stdout });
}
next();
});
});
server.listen(3000, () => console.log('Restify API listening on :3000'));
In this example, a POST to /run with { "command": "rm -rf /" } would delete files inside the container. If the container is started with --privileged or mounts the Docker socket, the same endpoint could be used to run docker run -v /:/host alpine chroot /host sh and gain root access on the host — a classic container escape.
Another vector is exposing the Docker socket through a misconfigured Restify service that proxies Docker API calls. An attacker who can reach /docker/containers/create can start a new container with hostile mounts, again achieving escape.
Restify‑Specific Detection
Detecting the conditions that could lead to a container escape relies on spotting unsafe usage of Node.js child‑process APIs and checking for exposed privileged mounts. middleBrick’s unauthenticated black‑box scan includes an Input Validation check that looks for patterns where user‑controlled data reaches dangerous functions such as child_process.exec, spawn, or eval. It also flags endpoints that return detailed error messages revealing internal paths, which can aid an attacker in crafting escape payloads.
When you submit a URL to middleBrick, the scanner:
- Enumerates all reachable Restify routes (via standard HTTP methods).
- Analyzes request/response pairs for reflections of input in error messages, headers, or body.
- Applies heuristic signatures for known dangerous sinks (e.g., strings passed to
execwithout whitelisting). - Reports any finding with severity, location (URL and HTTP method), and remediation guidance.
Example CLI usage:
# Scan a Restify API hosted at https://api.example.com
npx middlebrick scan https://api.example.com
The output will include a finding similar to:
Input Validation – High
Endpoint: POST /run
Description: User‑supplied "command" parameter is passed directly to child_process.exec without sanitization.
Remediation: Use execFile with a whitelist of allowed commands or avoid shell execution entirely.
If the scanner detects that the host’s Docker socket is reachable (e.g., via a REST proxy that forwards to /var/run/docker.sock), it will raise a finding under the BFLA/Privilege Escalation category, indicating a potential container escape path.
Restify‑Specific Remediation
Fixing the issue involves eliminating the unsafe flow of user input into privileged operations and applying defense‑in‑depth hardening. Below are Restify‑native strategies that address the most common escape vectors.
1. Avoid shell execution; use execFile with a whitelist
Replace exec with execFile, which does not invoke a shell, and restrict the executable to a known safe binary.
const { execFile } = require('child_process');
const ALLOWED_CMDS = ['/usr/bin/ffmpeg', '/usr/bin/convert'];
server.post('/process', (req, res, next) => {
const { cmd, args } = req.body;
if (!ALLOWED_CMDS.includes(cmd)) {
return res.send(400, { error: 'Command not allowed' });
}
execFile(cmd, args, (error, stdout, stderr) => {
if (error) {
return res.send(500, { error: error.message });
}
res.send(200, { output: stdout });
next();
});
});
2. Validate and sanitize all inputs with Restify plugins
Use restify-plugins or a validation library like Joi to enforce strict schemas before any logic runs.
const restify = require('restify');
const Joi = require('joi');
const server = restify.createServer();
server.use(restify.plugins.bodyParser({ mapParams: false }));
const processSchema = Joi.object({
cmd: Joi.string().valid('ffmpeg', 'convert').required(),
args: Joi.array().items(Joi.string()).max(5)
});
server.post('/process', (req, res, next) => {
const { error, value } = processSchema.validate(req.body);
if (error) {
return res.send(400, { error: error.details[0].message });
}
// safe to use value.cmd and value.args here
next();
});
3. Drop privileges and isolate the container
Even with safe code, run the Restify service as a non‑root user and avoid mounting /var/run/docker.sock unless absolutely required. If the socket is needed, place it behind an authentication proxy and never expose it to unauthenticated API endpoints.
By combining these Restify‑specific fixes — eliminating dangerous child‑process patterns, enforcing strict input validation, and minimizing privileged container capabilities — you remove the realistic paths that could lead to a container escape.
Frequently Asked Questions
Can middleBrick fix the container escape vulnerability in my Restify API?
What specific Restify code patterns does middleBrick look for when scanning for container escape risks?
child_process.exec, spawn, or eval without sanitization or whitelisting. It also flags endpoints that leak internal paths or expose privileged sockets like Docker’s, which could be abused for escape.