From d90aad2d9c85a9312861a837ec1c7822be04e4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gl=C3=A4=C3=9Fer?= Date: Mon, 3 Nov 2025 14:18:33 +0100 Subject: [PATCH 1/2] chore: suppress unused params in XmlParserSsrfGuidance --- .../jazzer/sanitizers/XmlParserSsrfGuidance.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XmlParserSsrfGuidance.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XmlParserSsrfGuidance.kt index 65607e26b..6846d9778 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XmlParserSsrfGuidance.kt +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/XmlParserSsrfGuidance.kt @@ -31,7 +31,7 @@ import java.lang.invoke.MethodHandle * (e.g. Socket/SocketChannel hooks) can observe network connections initiated by XML parsers * resolving external entities, schemas, or includes. */ -@Suppress("unused") +@Suppress("unused_parameter", "unused") object XmlParserSsrfGuidance { private val EXTERNAL_DOCTYPE = "" private val EXTERNAL_DOCTYPE_SIZE = EXTERNAL_DOCTYPE.toByteArray().size From a6142f2f337819031f5f347ca9423c5421645bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gl=C3=A4=C3=9Fer?= Date: Thu, 30 Oct 2025 10:31:14 +0100 Subject: [PATCH 2/2] feat: add freemarker template injection guidance hook --- MODULE.bazel | 1 + maven_install.json | 29 ++++++++- sanitizers/sanitizers.bzl | 1 + .../jazzer/sanitizers/BUILD.bazel | 1 + .../jazzer/sanitizers/TemplateInjection.kt | 61 +++++++++++++++++++ .../jazzer/sanitizers/BUILD.bazel | 1 + .../src/test/java/com/example/BUILD.bazel | 19 ++++++ .../java/com/example/TemplateInjection.java | 36 +++++++++++ 8 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/TemplateInjection.kt create mode 100644 sanitizers/src/test/java/com/example/TemplateInjection.java diff --git a/MODULE.bazel b/MODULE.bazel index 382f5531e..4a0408611 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -81,6 +81,7 @@ TEST_MAVEN_ARTIFACTS = [ "com.google.truth.extensions:truth-liteproto-extension:1.4.5", "com.google.truth.extensions:truth-proto-extension:1.4.5", "com.google.truth:truth:1.4.5", + "freemarker:freemarker:2.3.1", "jakarta.el:jakarta.el-api:6.0.1", "jakarta.validation:jakarta.validation-api:3.0.2", "javax.persistence:javax.persistence-api:2.2", diff --git a/maven_install.json b/maven_install.json index fef74579a..8a6b95de2 100755 --- a/maven_install.json +++ b/maven_install.json @@ -1,7 +1,7 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": -620116506, - "__RESOLVED_ARTIFACTS_HASH": 1157173568, + "__INPUT_ARTIFACTS_HASH": 962534745, + "__RESOLVED_ARTIFACTS_HASH": -862033946, "conflict_resolution": { "com.google.code.gson:gson:2.8.6": "com.google.code.gson:gson:2.8.9", "com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.1", @@ -170,6 +170,12 @@ }, "version": "1.1.1" }, + "freemarker:freemarker": { + "shasums": { + "jar": "501cf529f069fbfb47b433043fa970b119472a04bda44ea8f94141ffb8ced6c0" + }, + "version": "2.3.1" + }, "io.github.classgraph:classgraph": { "shasums": { "jar": "6e564e29cec95a392268a609f09071d56199383d906ac70e91753a7998d1a3e8" @@ -1301,6 +1307,24 @@ "org.apache.commons.logging", "org.apache.commons.logging.impl" ], + "freemarker:freemarker": [ + "freemarker.cache", + "freemarker.core", + "freemarker.debug", + "freemarker.debug.impl", + "freemarker.ext.ant", + "freemarker.ext.beans", + "freemarker.ext.dom", + "freemarker.ext.jdom", + "freemarker.ext.jsp", + "freemarker.ext.jython", + "freemarker.ext.servlet", + "freemarker.ext.util", + "freemarker.ext.xml", + "freemarker.log", + "freemarker.template", + "freemarker.template.utility" + ], "io.github.classgraph:classgraph": [ "io.github.classgraph", "nonapi.io.github.classgraph.classloaderhandler", @@ -2797,6 +2821,7 @@ "com.unboundid:unboundid-ldapsdk", "commons-io:commons-io", "commons-logging:commons-logging", + "freemarker:freemarker", "io.github.classgraph:classgraph", "io.projectreactor:reactor-core", "jakarta.el:jakarta.el-api", diff --git a/sanitizers/sanitizers.bzl b/sanitizers/sanitizers.bzl index 9f32fba30..88b04be64 100644 --- a/sanitizers/sanitizers.bzl +++ b/sanitizers/sanitizers.bzl @@ -32,6 +32,7 @@ _sanitizer_class_names = [ "ScriptEngineInjection", "ServerSideRequestForgery", "SqlInjection", + "TemplateInjection", "UnsafeSanitizer", "XPathInjection", "XmlParserSsrfGuidance", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index 89fb76664..59dcf55b9 100644 --- a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -79,6 +79,7 @@ kt_jvm_library( "OsCommandInjection.kt", "ReflectiveCall.kt", "RegexInjection.kt", + "TemplateInjection.kt", "Utils.kt", "XPathInjection.kt", "XmlParserSsrfGuidance.kt", diff --git a/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/TemplateInjection.kt b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/TemplateInjection.kt new file mode 100644 index 000000000..aec948dd9 --- /dev/null +++ b/sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/TemplateInjection.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.code_intelligence.jazzer.sanitizers + +import com.code_intelligence.jazzer.api.HookType +import com.code_intelligence.jazzer.api.Jazzer +import com.code_intelligence.jazzer.api.MethodHook +import java.io.Reader +import java.lang.invoke.MethodHandle + +/** + * Guides FreeMarker templates towards patterns that can trigger OS command injections. + * + * This does not report findings directly; it steers inputs towards OS command injections, + * which are detected by another bug detector. + */ +@Suppress("unused_parameter", "unused") +object TemplateInjection { + private const val FREEMARKER_INJECTION_ATTACK: String = "\${\"freemarker.template.utility.Execute\"?new()(\"jazze\")}" + + init { + require(FREEMARKER_INJECTION_ATTACK.length <= 64) { + "Expression language exploit must fit in a table of recent compares entry (64 bytes)" + } + } + + @MethodHook( + type = HookType.BEFORE, + targetClassName = "freemarker.template.Template", + targetMethod = "", + ) + @JvmStatic + fun hookFreemarker( + method: MethodHandle?, + thisObject: Any?, + arguments: Array, + hookId: Int, + ) { + if (arguments.size < 2) return + val reader = arguments[1] as? Reader ?: return + Jazzer.guideTowardsContainment( + peekMarkableReader(reader, FREEMARKER_INJECTION_ATTACK.length), + FREEMARKER_INJECTION_ATTACK, + hookId, + ) + } +} diff --git a/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel b/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel index a9bc80797..4cc4aea69 100644 --- a/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel +++ b/sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel @@ -19,6 +19,7 @@ java_junit5_test( "//sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers:constants", "//src/main/java/com/code_intelligence/jazzer/api:hooks", "@clojure_jar//jar", + "@maven//:freemarker_freemarker", "@maven//:jakarta_el_jakarta_el_api", "@maven//:jakarta_validation_jakarta_validation_api", "@maven//:javax_el_javax_el_api", diff --git a/sanitizers/src/test/java/com/example/BUILD.bazel b/sanitizers/src/test/java/com/example/BUILD.bazel index 038080a63..978823744 100644 --- a/sanitizers/src/test/java/com/example/BUILD.bazel +++ b/sanitizers/src/test/java/com/example/BUILD.bazel @@ -314,6 +314,25 @@ java_fuzz_target_test( ], ) +java_fuzz_target_test( + name = "TemplateInjection", + srcs = [ + "TemplateInjection.java", + ], + allowed_findings = [ + "com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical", + ], + fuzzer_args = [ + "--instrumentation_includes=com.example.**", + ], + tags = ["dangerous"], + target_class = "com.example.TemplateInjection", + verify_crash_reproducer = False, + deps = [ + "@maven//:freemarker_freemarker", + ], +) + [java_fuzz_target_test( name = "UnsafeArrayOutOfBounds_" + method, srcs = [ diff --git a/sanitizers/src/test/java/com/example/TemplateInjection.java b/sanitizers/src/test/java/com/example/TemplateInjection.java new file mode 100644 index 000000000..909d1becf --- /dev/null +++ b/sanitizers/src/test/java/com/example/TemplateInjection.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example; + +import com.code_intelligence.jazzer.api.FuzzedDataProvider; +import freemarker.template.Template; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +public class TemplateInjection { + public static void fuzzerTestOneInput(FuzzedDataProvider fdp) throws Exception { + Map model = new HashMap<>(); + String data = fdp.consumeRemainingAsString(); + try { + Template tmpl = new Template("test", new StringReader(data)); + tmpl.process(model, new StringWriter()); + } catch (Throwable ignored) { + } + } +}