Back to Blog
critical SEVERITY6 min read

How server-side template injection happens in Node.js EJS and how to fix it

A critical server-side template injection vulnerability (CVE-2022-29078) was discovered in EJS version 3.1.6, allowing attackers to execute arbitrary code on the server through the `outputFunctionName` option. This vulnerability was fixed by upgrading EJS from 3.1.6 to 3.1.7 in the react-redux-bad-algo project, eliminating a dangerous remote code execution vector.

O
By Orbis AppSec
Published July 30, 2026Reviewed July 30, 2026

Answer Summary

CVE-2022-29078 is a critical server-side template injection (SSTI) vulnerability in EJS (Embedded JavaScript) for Node.js, classified as CWE-94 (Improper Control of Generation of Code). Attackers can exploit the `outputFunctionName` render option to inject and execute arbitrary JavaScript on the server. The fix is straightforward: upgrade EJS from version 3.1.6 to 3.1.7 or later, which adds proper validation to prevent code injection through template options.

Vulnerability at a Glance

cweCWE-94 (Improper Control of Generation of Code)
fixUpgrade EJS from 3.1.6 to 3.1.7
riskRemote code execution allowing full server compromise
languageJavaScript (Node.js)
root causeUnsanitized `outputFunctionName` option allowed arbitrary code injection during template compilation
vulnerabilityServer-Side Template Injection (SSTI)

Introduction

In the react-redux-bad-algo project, a critical server-side template injection vulnerability was lurking in the dependency tree. The package-lock.json file pinned EJS (Embedded JavaScript) to version 3.1.6, which contains CVE-2022-29078—a flaw that could allow attackers to execute arbitrary code on the server by manipulating the outputFunctionName render option.

This isn't a theoretical risk. The vulnerability is rated critical with a CVSS score of 9.8, and proof-of-concept exploits are publicly available. Any application using EJS 3.1.6 or earlier that passes user-influenced data to template rendering options could be completely compromised.

Looking at the vulnerable dependency declaration:

"node_modules/ejs": {
  "version": "3.1.6",
  "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
  "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
  "dependencies": {
    "jake": "^10.6.1"
  }
}

This version contains the vulnerable code path that fails to sanitize the outputFunctionName option during template compilation.

The Vulnerability Explained

What is Server-Side Template Injection?

EJS is a popular templating engine for Node.js that lets developers embed JavaScript code within HTML templates. When you render a template, EJS compiles it into a JavaScript function that generates the final HTML output.

The vulnerability exists in how EJS handles the outputFunctionName option. This option allows developers to customize the name of the function that writes output during template rendering. In vulnerable versions, EJS directly concatenates this option value into generated JavaScript code without proper sanitization.

How the Attack Works

An attacker can craft a malicious outputFunctionName value that breaks out of the intended context and injects arbitrary JavaScript. Consider this attack payload:

// Malicious request to a vulnerable endpoint
GET /page?settings[view%20options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('id');s

// Or via POST body
{
  "settings": {
    "view options": {
      "outputFunctionName": "x;process.mainModule.require('child_process').execSync('whoami');s"
    }
  }
}

When EJS compiles a template with this poisoned option, it generates code like:

var x;process.mainModule.require('child_process').execSync('whoami');s = "";
// ... rest of template code

The injected code executes during template compilation, giving the attacker full control over the server. They can:

  • Execute system commands
  • Read sensitive files (environment variables, credentials)
  • Establish reverse shells
  • Pivot to internal network resources
  • Modify or delete data

Real-World Impact for This Application

The react-redux-bad-algo project is a React application with server-side rendering capabilities. If this application:

  1. Uses EJS for any server-side templating (email templates, PDF generation, SSR)
  2. Allows any user input to influence render options (through query parameters, request bodies, or configuration)

Then an attacker could achieve remote code execution on the production server hosting this application.

The Fix

The fix is elegantly simple: upgrade EJS from version 3.1.6 to 3.1.7. The EJS maintainers added proper validation to the outputFunctionName option, rejecting any values that contain characters that could enable code injection.

Before (Vulnerable)

"node_modules/ejs": {
  "version": "3.1.6",
  "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
  "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
  "dependencies": {
    "jake": "^10.6.1"
  }
}

After (Fixed)

"node_modules/ejs": {
  "version": "3.1.7",
  "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.7.tgz",
  "integrity": "sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==",
  "license": "Apache-2.0",
  "dependencies": {
    "jake": "^10.8.5"
  }
}

What Changed in EJS 3.1.7

The patched version adds validation that checks outputFunctionName against a strict pattern, ensuring it can only contain valid JavaScript identifier characters. Any attempt to inject semicolons, parentheses, or other code-breaking characters is now rejected before template compilation begins.

The fix also updated the jake dependency from ^10.6.1 to ^10.8.5, and transitively updated filelist from 1.0.2 to 1.0.6 with its minimatch dependency, ensuring the entire dependency chain is secure.

Changes Made

Two files were modified:

  1. react-redux-bad-algo/package.json: Added explicit EJS dependency at version ^3.1.7
  2. react-redux-bad-algo/package-lock.json: Updated the resolved EJS version and all transitive dependencies

By explicitly declaring EJS in package.json, the project ensures that even if other dependencies try to pull in an older version, npm will resolve to the safe 3.1.7+ version.

Prevention & Best Practices

1. Never Trust User Input in Template Options

The safest approach is to never allow user-controlled data to influence template engine configuration:

// DANGEROUS: User input in render options
app.get('/page', (req, res) => {
  res.render('template', req.query.data, req.query.options); // NO!
});

// SAFE: Hardcoded options, user data only in template context
app.get('/page', (req, res) => {
  const safeData = sanitize(req.query.data);
  res.render('template', { userData: safeData }); // Options controlled by developer
});

2. Keep Dependencies Updated

Use automated tools to monitor and update dependencies:

# Check for known vulnerabilities
npm audit

# Update to patched versions
npm audit fix

# Use tools like Dependabot or Renovate for automated PRs

3. Use Lockfiles Correctly

Always commit your package-lock.json and use npm ci in CI/CD pipelines to ensure reproducible builds with exact versions.

4. Implement Defense in Depth

Even with updated dependencies:

  • Run Node.js with minimal privileges
  • Use containers with read-only filesystems where possible
  • Implement network segmentation
  • Monitor for suspicious process execution

5. Consider Template Engine Alternatives

For high-security applications, consider template engines with sandboxed execution or no code execution capabilities (like Mustache/Handlebars in logic-less mode).

Key Takeaways

  • EJS 3.1.6's outputFunctionName option was vulnerable to code injection because it concatenated user input directly into generated JavaScript without validation
  • The react-redux-bad-algo project's package-lock.json pinned the vulnerable version, making the application susceptible to RCE attacks
  • Upgrading to EJS 3.1.7 adds input validation that rejects malicious characters in template options
  • Explicitly declaring dependencies in package.json (not just relying on transitive dependencies) gives you control over which versions are installed
  • Automated dependency scanning with tools like Trivy can catch these vulnerabilities before they reach production

How Orbis AppSec Detected This

  • Source: The package-lock.json dependency declaration that resolved EJS to version 3.1.6
  • Sink: The EJS template compilation function that processes outputFunctionName without sanitization
  • Missing control: No validation of the outputFunctionName option before code generation
  • CWE: CWE-94 (Improper Control of Generation of Code)
  • Fix: Upgraded EJS from 3.1.6 to 3.1.7, which adds proper validation to reject injection attempts in template options

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2022-29078 demonstrates how a single unsanitized configuration option can turn a widely-used template engine into a remote code execution vector. The vulnerability in EJS's outputFunctionName handling allowed attackers to break out of the template context and execute arbitrary JavaScript on the server.

The fix was straightforward—a version bump from 3.1.6 to 3.1.7—but the impact of leaving it unpatched could have been catastrophic. This case reinforces the importance of:

  1. Automated dependency scanning in your CI/CD pipeline
  2. Prompt patching of known vulnerabilities
  3. Understanding how your dependencies handle user input
  4. Defense in depth, because even trusted libraries can have critical flaws

Keep your dependencies updated, audit your template rendering code paths, and never let user input control template engine configuration.

References

Frequently Asked Questions

What is server-side template injection?

Server-side template injection (SSTI) occurs when user input is embedded into a template engine in an unsafe way, allowing attackers to inject template directives that execute arbitrary code on the server.

How do you prevent server-side template injection in Node.js?

Prevent SSTI by never passing user-controlled input to template engine options, keeping template engines updated, using sandboxed rendering environments, and validating all inputs that influence template compilation.

What CWE is server-side template injection?

Server-side template injection is classified under CWE-94 (Improper Control of Generation of Code) and sometimes CWE-1336 (Improper Neutralization of Special Elements Used in a Template Engine).

Is input validation enough to prevent server-side template injection?

Input validation alone is often insufficient because template injection payloads can be obfuscated. The safest approach is to never allow user input to control template options like `outputFunctionName`, and to keep your template engine updated.

Can static analysis detect server-side template injection?

Yes, static analysis tools like Trivy, Snyk, and Semgrep can detect known vulnerable versions of template engines and flag dangerous patterns where user input flows into template compilation options.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #185

Related Articles

medium

How Insufficient Password Hashing Cost Factor Happens in Node.js and How to Fix It

A bcrypt password hashing implementation in the User.js model was using a cost factor of 10, which falls below OWASP's 2024 recommendation of 12 for applications handling sensitive data. This fix upgrades the salt rounds from 10 to 12, increasing the computational work required to crack passwords by approximately 4x, significantly improving protection against brute-force attacks on this e-commerce platform.

high

How Arbitrary Code Execution via Template Imports Happens in JavaScript (lodash) and How to Fix It

A high-severity arbitrary code execution vulnerability (CVE-2026-4800) was discovered in lodash's template function, specifically in how it handles the `imports` option with untrusted input. The fix upgrades lodash from version 4.17.21 to 4.18.0 in the project's `package.json` and `yarn.lock`, eliminating the attack surface where crafted template imports could execute arbitrary code on the server.

critical

How Server-Side Request Forgery (SSRF) happens in Node.js IP address parsing and how to fix it

A critical SSRF vulnerability (CVE-2026-69192) was discovered in the ip-address npm package version 10.2.0, which could allow attackers to bypass IP address validation and access internal services. The fix upgrades the dependency to version 10.3.1, which properly handles edge cases in IP address parsing that previously allowed trust-boundary bypasses.

critical

How Sensitive Data Exposure happens in Python web applications and how to fix it

A critical sensitive data exposure vulnerability was discovered in `nodes/google_gemini.py` where the Google Gemini API key was returned in plaintext through a web endpoint. The fix masks the token in API responses, preventing credential theft from any client that queries the token endpoint. This protects downstream users of this Node.js library from unauthorized access to their Google Gemini services.

high

How Authentication Bypass happens in Next.js App Router with Turbopack and how to fix it

A critical authentication bypass vulnerability (CVE-2026-64642) was discovered in Next.js versions prior to 16.2.11, specifically affecting App Router applications using Turbopack with a single locale configuration. This vulnerability allowed attackers to bypass middleware and proxy protections, potentially gaining unauthorized access to protected routes and resources that should have been secured by authentication checks.

critical

How SQL Injection Happens in CSV-to-SQL Converters and How to Fix It

A critical SQL injection vulnerability was discovered in the `csv2sql()` function in `src/data/converter/csv.js`, where CSV data and table names were directly interpolated into SQL INSERT statements without sanitization. The fix implements input validation through identifier sanitization and proper value escaping, eliminating the attack surface while preserving legitimate functionality.