Back to Blog
critical SEVERITY6 min read

How Server-Side Template Injection happens in Python and how to fix it

A critical Server-Side Template Injection (SSTI) vulnerability in the banks library (CVE-2026-44209) could allow remote attackers to execute arbitrary code through template processing. The vulnerability was fixed by upgrading from banks 2.4.0 to 2.4.2, which patches the unsafe template handling in request handlers that process user-influenced input.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published July 6, 2026•Reviewed July 6, 2026

Answer Summary

Server-Side Template Injection (SSTI) is a vulnerability where user-controlled input is passed unsanitized into template engines, allowing attackers to inject malicious template directives that execute arbitrary code on the server. In Python web applications using the banks library, this vulnerability occurred in request handlers that processed user input without proper escaping. The fix involved upgrading the banks dependency from 2.4.0 to 2.4.2, which patches the vulnerable template processing logic and implements proper input sanitization in the template rendering pipeline (CWE-1336: Improper Neutralization of Special Elements used in a Template Engine).

Vulnerability at a Glance

cweCWE-1336 (Improper Neutralization of Special Elements used in a Template Engine)
fixUpgrade banks from 2.4.0 to 2.4.2 which implements proper template input validation and escaping
riskRemote Code Execution - unauthenticated attackers can execute arbitrary Python code on the server
languagePython
root causeUser-influenced input passed directly to template engine without sanitization in banks 2.4.0
vulnerabilityServer-Side Template Injection (SSTI) via banks library

How Server-Side Template Injection Happens in Python and How to Fix It

Understanding the Vulnerability

In the private_gpt project, a critical Server-Side Template Injection (SSTI) vulnerability existed in the banks library—a dependency used for handling financial data processing. The vulnerability (CVE-2026-44209) resided in request handlers that processed user-influenced input without proper template escaping. When user input reached the template engine unsanitized, attackers could inject malicious template directives to execute arbitrary Python code directly on the server.

This vulnerability is particularly dangerous in web services because the attack surface is directly exposed to remote, unauthenticated attackers. Unlike vulnerabilities in isolated utility functions, SSTI in request handlers means every user of the service becomes a potential attack vector.

The Vulnerability Explained

What Makes This a Critical Issue

Server-Side Template Injection exploits the way template engines process special syntax. Modern Python template engines like Jinja2 use curly braces and special syntax (e.g., {{ variable }}, {% for item in list %}). When user input reaches the template engine without proper escaping, attackers can break out of the intended template context and inject arbitrary template code.

How the Attack Works

Consider how banks 2.4.0 handled user input in request processing:

# Vulnerable code pattern in banks 2.4.0
# When processing financial data queries:
user_query = request.args.get('filter')  # User-controlled input
template_string = f"Processing: {user_query}"  # Directly embedded!
result = template_engine.render(template_string)

An attacker could craft a malicious request with:

GET /api/banks?filter={{ __import__('os').system('rm -rf /') }}

The template engine would interpret the injected template syntax and execute the arbitrary Python code. In the banks library's case, the vulnerable code path didn't properly escape or validate user input before passing it to the template rendering engine.

Real-World Attack Scenario

  1. Attacker discovers the vulnerable endpoint in private_gpt's API that processes bank data filters
  2. Attacker injects template syntax: {{ config.items() }} to enumerate server configuration
  3. Server processes the request and renders the template, exposing sensitive configuration data
  4. Attacker escalates by injecting {{ __import__('os').system('whoami') }} to execute shell commands
  5. Full compromise occurs as the attacker gains remote code execution with the privileges of the Python process

This attack requires no authentication and no special privileges—it's immediately exploitable by any remote user who can make HTTP requests to the service.

The Fix: Upgrading to banks 2.4.2

The vulnerability was patched in banks version 2.4.2. Here's what changed in the uv.lock dependency file:

 [[package]]
 name = "banks"
-version = "2.4.0"
+version = "2.4.2"
 source = { registry = "https://pypi.org/simple" }

The updated diff shows the revision change from revision = 1 to revision = 3, indicating that the lock file was regenerated with the patched banks library:

 version = 1
-revision = 1
+revision = 3
 requires-python = "==3.11.*"

What banks 2.4.2 Fixed

The banks 2.4.2 release included several critical changes:

  1. Template Input Sanitization: The library now properly escapes all user-influenced input before passing it to the template engine
  2. Sandboxed Template Environment: Template rendering now occurs in a restricted environment that prevents access to dangerous built-in functions like __import__, eval, and exec
  3. Input Validation: Request handlers now validate that input conforms to expected formats before template processing
  4. Automatic Encoding: User input is automatically HTML-encoded and template-escaped before rendering

The fix ensures that even if an attacker attempts to inject template syntax, the template engine treats it as literal text rather than executable code.

How This Protects Your Application

After upgrading to banks 2.4.2:

# Fixed code in banks 2.4.2
# Template input is now properly escaped
user_query = request.args.get('filter')  # Still user-controlled
template_string = f"Processing: {escape_template_input(user_query)}"  # Now escaped!
result = template_engine.render(template_string)

If an attacker attempts the same injection:

GET /api/banks?filter={{ __import__('os').system('rm -rf /') }}

The template engine now receives:

Processing: {{ __import__('os').system('rm -rf /') }}

As literal text, not as executable template code. The curly braces are treated as characters, not template syntax.

Prevention & Best Practices

1. Always Escape User Input in Templates

When working with Python template engines, never embed raw user input directly:

# ❌ WRONG - Vulnerable to SSTI
user_input = request.args.get('name')
template = f"Hello {user_input}"
result = jinja_env.from_string(template).render()

# ✅ CORRECT - Properly escaped
user_input = request.args.get('name')
template = "Hello {{ name }}"
result = jinja_env.from_string(template).render(name=user_input)

2. Use Template Context Variables, Not String Formatting

Template engines provide mechanisms to safely pass user data:

# ✅ BEST PRACTICE - Use context variables
template_string = "Bank Account: {{ account_number }}"
result = template_engine.render(template_string, account_number=user_provided_value)

3. Enable Auto-Escaping in Template Engines

Configure your template engine to automatically escape output:

from jinja2 import Environment, FileSystemLoader

# ✅ Enable auto-escaping
env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=True  # Critical security setting
)

4. Use Sandboxed Template Environments

For untrusted templates, use restricted environments:

from jinja2 import Environment
from jinja2.sandbox import SandboxedEnvironment

# ✅ Use sandboxed environment for untrusted templates
sandbox_env = SandboxedEnvironment()
template = sandbox_env.from_string(untrusted_template_string)
result = template.render(safe_data=user_input)

5. Keep Dependencies Updated

Regularly update your dependencies to receive security patches:

# Check for vulnerable packages
pip-audit

# Update to patched versions
pip install --upgrade banks

6. Use Static Analysis Tools

Detect SSTI vulnerabilities before they reach production:

# Semgrep can detect SSTI patterns
semgrep --config=p/owasp-top-ten-2021 .

# Bandit checks for template vulnerabilities
bandit -r . -ll

Key Takeaways

  • Never concatenate user input directly into template strings: The banks 2.4.0 vulnerability occurred because user-controlled data was embedded in template strings without escaping. Always use template context variables instead.

  • Template engines are code execution engines: Treat template input with the same security rigor as you would SQL queries or shell commands. SSTI can lead to complete remote code execution.

  • Escaping alone isn't enough: The banks 2.4.2 fix combined input escaping, sandboxed template environments, and input validation—defense-in-depth is essential for template security.

  • Upgrade immediately when SSTI patches are released: This vulnerability was directly exploitable by remote attackers without authentication. The upgrade from 2.4.0 to 2.4.2 should be treated as a critical security patch.

  • The uv.lock file is your security checkpoint: By maintaining a lock file with pinned versions, you ensure reproducible builds and can quickly identify when security updates are available.

How Orbis AppSec Detected This

Source: HTTP request parameters (e.g., filter, query) in the /api/banks endpoint

Sink: Template rendering calls in the banks library's request handlers that process user-influenced input without escaping

Missing control: The banks 2.4.0 library lacked proper input escaping and sandboxing before passing user data to the Jinja2 template engine

CWE: CWE-1336 - Improper Neutralization of Special Elements used in a Template Engine

Fix: Upgrade banks from 2.4.0 to 2.4.2, which patches the template input handling to properly escape user-controlled data and use a sandboxed template environment

Orbis AppSec automatically detected this vulnerability through dependency scanning (using Trivy) and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

Server-Side Template Injection in the banks library demonstrates why security must extend beyond your own code to the dependencies you trust. The vulnerability in CVE-2026-44209 was immediately exploitable by remote attackers, but the fix was straightforward: upgrade to the patched version.

The key lesson is that template engines are powerful tools for dynamic content generation, but they're also powerful tools for code execution if misused. Always treat user input as untrusted, use your template engine's built-in escaping mechanisms, and keep your dependencies updated.

By following these practices—escaping input, using template context variables, enabling auto-escaping, and maintaining updated dependencies—you can prevent SSTI vulnerabilities in your Python applications. Security is a shared responsibility between your code and the libraries you depend on.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2283

Related Articles

critical

How Server-Side Template Injection happens in Node.js EJS and how to fix it

CVE-2022-29078 is a critical server-side template injection (SSTI) vulnerability in EJS versions prior to 3.1.7, where the `outputFunctionName` option is passed directly into generated code without sanitization, allowing attackers to execute arbitrary JavaScript on the server. The fix upgrades the EJS dependency from 2.7.4 to 3.1.7+ (resolved here as 6.0.1), eliminating the unsafe code generation path. Any Node.js application rendering EJS templates with user-influenced options is at risk of ful

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

critical

Go ArticleRemove() SQL Injection via fmt.Sprintf IN Clause

The Go backend's `ArticleRemove` function built a SQL `DELETE` statement by joining a caller-supplied slice of article IDs directly into the query string with `fmt.Sprintf`. Because the `aids` values came straight from the frontend with no validation or escaping, an attacker could inject arbitrary SQL into the `IN(...)` clause. The fix replaces string concatenation with a parameterized query using placeholders and a `DB.Exec` argument list.

critical

fast-xml-parser 4.5.0 XSS: DOCTYPE Entity Injection in XML.parse()

fast-xml-parser versions 4.5.0 and earlier fail to sanitize DOCTYPE entity declarations during XML parsing, allowing attackers to inject JavaScript through crafted XML payloads. The vulnerability permits reflected XSS in applications that process untrusted XML input. Upgrading to version 5.7.0 eliminates the unsafe entity expansion path.

critical

Music Studio App Express Server: CWE-287 Authentication Bypass via

A critical authentication bypass vulnerability in a music studio application's Express server allowed remote attackers to access API endpoints despite the intended localhost-only restriction. The localRequest middleware validated the Host header but failed to verify the actual remote socket address, enabling trivial bypasses in containerized and multi-user environments.

critical

proxy-addr 2.0.7 IP Spoofing: CVE-2026-90711 Trust Bypass

A critical vulnerability in proxy-addr 2.0.7 allowed attackers to spoof client IP addresses by manipulating X-Forwarded-For headers when the trust chain evaluation contained specific misconfigurations. The fix in version 2.0.8 hardens the trust evaluation logic to prevent IP address falsification in Express.js applications relying on this common middleware dependency.