Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
29 changes: 27 additions & 2 deletions maven_install.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -170,6 +170,12 @@
},
"version": "1.1.1"
},
"freemarker:freemarker": {
"shasums": {
"jar": "501cf529f069fbfb47b433043fa970b119472a04bda44ea8f94141ffb8ced6c0"
},
"version": "2.3.1"
},
"io.github.classgraph:classgraph": {
"shasums": {
"jar": "6e564e29cec95a392268a609f09071d56199383d906ac70e91753a7998d1a3e8"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions sanitizers/sanitizers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ _sanitizer_class_names = [
"ScriptEngineInjection",
"ServerSideRequestForgery",
"SqlInjection",
"TemplateInjection",
"UnsafeSanitizer",
"XPathInjection",
"XmlParserSsrfGuidance",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ kt_jvm_library(
"OsCommandInjection.kt",
"ReflectiveCall.kt",
"RegexInjection.kt",
"TemplateInjection.kt",
"Utils.kt",
"XPathInjection.kt",
"XmlParserSsrfGuidance.kt",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = "<init>",
)
@JvmStatic
fun hookFreemarker(
method: MethodHandle?,
thisObject: Any?,
arguments: Array<Any>,
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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<!DOCTYPE x PUBLIC \"\" \"http://foo\">"
private val EXTERNAL_DOCTYPE_SIZE = EXTERNAL_DOCTYPE.toByteArray().size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions sanitizers/src/test/java/com/example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,24 @@ java_fuzz_target_test(
],
)

java_fuzz_target_test(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have tags = ["dangerous"] in case it can trigger OS command execution?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, you are right!

name = "TemplateInjection",
srcs = [
"TemplateInjection.java",
],
allowed_findings = [
"com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical",
],
fuzzer_args = [
"--instrumentation_includes=com.example.**",
],
target_class = "com.example.TemplateInjection",
verify_crash_reproducer = False,
deps = [
"@maven//:freemarker_freemarker",
],
)

[java_fuzz_target_test(
name = "UnsafeArrayOutOfBounds_" + method,
srcs = [
Expand Down
36 changes: 36 additions & 0 deletions sanitizers/src/test/java/com/example/TemplateInjection.java
Original file line number Diff line number Diff line change
@@ -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<String, Object> model = new HashMap<>();
String data = fdp.consumeRemainingAsString();
try {
Template tmpl = new Template("test", new StringReader(data));
tmpl.process(model, new StringWriter());
} catch (Throwable ignored) {
}
}
}