HIGH sandbox escapeexpress

Sandbox Escape in Express

How Sandbox Escape Manifests in Express

Sandbox escape in Express applications typically occurs when user-controlled data bypasses intended security boundaries, allowing attackers to execute code or access resources outside the application's restricted environment. In Express, this often manifests through template injection, improper module resolution, or unsafe evaluation of dynamic content.

One common Express-specific pattern involves template engines like EJS, Pug, or Handlebars. When user input is directly passed to template rendering without proper sanitization, attackers can inject malicious code. For example, in EJS:

app.get('/render', (req, res) => {
  const userData = req.query.data;
  res.render('template', { data: userData });
});

If userData contains EJS tags like <%= process.mainModule.require('fs').readFileSync('/etc/passwd') %>, the template engine will execute this code, escaping the sandbox and reading sensitive files.

Another Express-specific sandbox escape vector involves eval() or Function() constructors with user input:

app.post('/eval', (req, res) => {
  const code = req.body.code;
  try {
    const result = eval(code);
    res.json({ result });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

Attackers can use this to execute arbitrary JavaScript, access Node.js internals, or even spawn child processes to escape the application's boundaries.

Express's require() function can also be exploited when combined with user input. While Node.js has protections against require() with non-static strings, Express applications sometimes use dynamic module loading:

app.get('/load-module', (req, res) => {
  const moduleName = req.query.module;
  const module = require(moduleName);
  res.json({ module });
});

An attacker could request /load-module?module=child_process to gain access to dangerous modules that can spawn processes or read files.

Middleware configuration can also create sandbox escape opportunities. Express's app.use() with dynamic paths or improperly configured static file serving can expose sensitive files:

app.use('/static', express.static('.')); // Exposes entire directory tree
app.use((req, res) => {
  const filePath = path.join(__dirname, req.query.file);
  res.sendFile(filePath);
});

These patterns allow directory traversal attacks where ../ sequences can navigate outside intended directories.

Express-Specific Detection

Detecting sandbox escape vulnerabilities in Express requires both static analysis and dynamic testing. middleBrick's Express-specific scanning identifies these patterns by analyzing your API endpoints and testing for common sandbox escape vectors.

For template injection detection, middleBrick tests endpoints that accept user input and render templates. It submits payloads containing template syntax for popular Express template engines (EJS, Pug, Handlebars) and monitors responses for signs of code execution, such as unexpected content or error messages revealing system information.

The scanner specifically looks for Express patterns like:

res.render(template, data)
app.engine('view engine', engine)
app.set('view engine', engine)

When these patterns are detected, middleBrick performs active testing by injecting template syntax that attempts to read files, execute system commands, or access environment variables.

For dynamic evaluation vulnerabilities, middleBrick identifies Express routes that use eval(), Function(), or similar dynamic execution patterns. It then tests these endpoints with payloads designed to escape the sandbox, such as:

process.mainModule.require('child_process').execSync('id')
this.constructor.constructor('return process')().mainModule.require('fs').readdirSync('/')
global.process.mainModule.require('os').networkInterfaces()

The scanner monitors for successful escapes by checking if responses contain system information, file listings, or other indicators of successful sandbox escape.

middleBrick also tests Express's module resolution system by attempting to load sensitive modules through dynamic require() calls. It checks for routes that might allow module loading and tests with modules like child_process, fs, os, and crypto to see if they can be loaded and used to escape the sandbox.

For middleware-related vulnerabilities, the scanner examines static file serving configurations and path traversal protections. It tests for directory traversal by submitting paths with ../ sequences and checking if sensitive files like /etc/passwd, /proc/self/environ, or application source files can be accessed.

The LLM/AI security features in middleBrick's Pro tier are particularly relevant for Express applications using AI integrations. If your Express app serves as an API for AI models or uses AI features, middleBrick tests for system prompt leakage and prompt injection vulnerabilities that could allow attackers to manipulate AI behavior or extract sensitive information.

Express-Specific Remediation

Remediating sandbox escape vulnerabilities in Express requires a combination of input validation, secure coding practices, and proper configuration. Here are Express-specific fixes for the vulnerabilities discussed:

For template injection, always sanitize user input before rendering templates. Use template engines that don't allow arbitrary code execution, or implement strict content security policies:

// Safer template rendering with input validation
app.get('/render', (req, res) => {
  const userData = req.query.data;
  
  // Sanitize input - remove template syntax
  const sanitizedData = userData
    .replace(/<%=.*?%>/g, '')
    .replace(/<%.*?%>/g, '')
    .replace(/
/g, ' ');
    
  res.render('template', { data: sanitizedData });
});

Better yet, use a template engine that doesn't allow code execution, like Handlebars with strict context:

const exphbs = require('express-handlebars');

app.engine('handlebars', exphbs({
  strict: true // Prevents access to undefined variables and prototype pollution
}));
app.set('view engine', 'handlebars');

For dynamic evaluation vulnerabilities, completely avoid eval() and similar functions with user input. If you need to evaluate expressions, use a safe parser:

// Instead of eval(), use a safe math expression parser
const { parse } = require('mathjs');

app.post('/calculate', (req, res) => {
  const expression = req.body.expression;
  
  try {
    // mathjs only allows mathematical expressions, no code execution
    const result = parse(expression).compile().evaluate();
    res.json({ result });
  } catch (err) {
    res.status(400).json({ error: 'Invalid expression' });
  }
});

For dynamic module loading, implement a whitelist approach:

const allowedModules = new Set(['lodash', 'uuid', 'axios']);

app.get('/load-module', (req, res) => {
  const moduleName = req.query.module;
  
  if (!allowedModules.has(moduleName)) {
    return res.status(400).json({ error: 'Module not allowed' });
  }
  
  try {
    const module = require(moduleName);
    res.json({ module: module.name || 'Loaded successfully' });
  } catch (err) {
    res.status(500).json({ error: 'Module loading failed' });
  }
});

For static file serving, use Express's built-in protections and avoid serving from the root directory:

// Serve only from a specific directory with protections
app.use('/static', express.static(path.join(__dirname, 'public'), {
  dotfiles: 'ignore', // Don't serve dotfiles
  index: false, // Don't serve index files
  redirect: false
}));

// For file downloads, validate paths strictly
app.get('/download', (req, res) => {
  const fileName = path.basename(req.query.file);
  const filePath = path.join(__dirname, 'uploads', fileName);
  
  // Verify file exists and is within the uploads directory
  if (!filePath.startsWith(path.join(__dirname, 'uploads'))) {
    return res.status(400).json({ error: 'Invalid file path' });
  }
  
  res.sendFile(filePath);
});

Implement comprehensive input validation using libraries like validator.js or joi:

const { body, validationResult } = require('express-validator');

app.post('/submit', [
  body('data')
    .trim()
    .escape() // Escape HTML entities
    .matches(/^[a-zA-Z0-9\s]+$/) // Allow only alphanumeric and spaces
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  
  // Process sanitized data
  res.json({ success: true });
});

Finally, use middleBrick's continuous monitoring to ensure these fixes remain effective. The Pro tier's scheduled scanning will automatically retest your Express endpoints after remediation, providing a new security score and confirming that sandbox escape vulnerabilities have been resolved.

Frequently Asked Questions

How can I tell if my Express app has sandbox escape vulnerabilities?
Look for patterns like eval() with user input, dynamic require() calls, template rendering with unsanitized data, and improper static file serving. Use middleBrick to scan your API endpoints - it specifically tests for template injection, dynamic evaluation, and module loading vulnerabilities that could lead to sandbox escape.
Are sandbox escape vulnerabilities only a concern for Node.js/Express?
While this article focuses on Express, sandbox escape is a universal security concern across all platforms. Any application that processes user input and executes code (templates, eval, dynamic loading) can be vulnerable. Express applications are particularly susceptible because Node.js provides powerful APIs that, if misused, can easily escape application boundaries.