Skip to content

Commit 761cc11

Browse files
committed
feat: add freemarker template injection guidance hook
1 parent 196391d commit 761cc11

File tree

8 files changed

+154
-2
lines changed

8 files changed

+154
-2
lines changed

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ TEST_MAVEN_ARTIFACTS = [
8888
"ognl:ognl:3.3.0",
8989
"org.apache.commons:commons-jexl:2.1.1",
9090
"org.assertj:assertj-core:3.27.6",
91+
"org.freemarker:freemarker:2.3.34",
9192
"org.jacoco:org.jacoco.core:0.8.14",
9293
"org.mockito:mockito-core:5.20.0",
9394
"org.mvel:mvel2:2.5.2.Final",

maven_install.json

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
3-
"__INPUT_ARTIFACTS_HASH": -620116506,
4-
"__RESOLVED_ARTIFACTS_HASH": 1157173568,
3+
"__INPUT_ARTIFACTS_HASH": -1980670473,
4+
"__RESOLVED_ARTIFACTS_HASH": -157334146,
55
"conflict_resolution": {
66
"com.google.code.gson:gson:2.8.6": "com.google.code.gson:gson:2.8.9",
77
"com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.1",
@@ -428,6 +428,12 @@
428428
},
429429
"version": "3.27.6"
430430
},
431+
"org.freemarker:freemarker": {
432+
"shasums": {
433+
"jar": "9a9fb91cd64199232eb1ca9766148a5d30ef8944be5fac051018f96c70c8f6a3"
434+
},
435+
"version": "2.3.34"
436+
},
431437
"org.glassfish:javax.el": {
432438
"shasums": {
433439
"jar": "c255fe3ff4d7e491caf92c10c497f3c77d19acc4832d9bd2e80180d168fcedd2"
@@ -1854,6 +1860,27 @@
18541860
"org.assertj.core.util.introspection",
18551861
"org.assertj.core.util.xml"
18561862
],
1863+
"org.freemarker:freemarker": [
1864+
"freemarker.cache",
1865+
"freemarker.core",
1866+
"freemarker.debug",
1867+
"freemarker.debug.impl",
1868+
"freemarker.ext.ant",
1869+
"freemarker.ext.beans",
1870+
"freemarker.ext.dom",
1871+
"freemarker.ext.jakarta.jsp",
1872+
"freemarker.ext.jakarta.servlet",
1873+
"freemarker.ext.jdom",
1874+
"freemarker.ext.jsp",
1875+
"freemarker.ext.jython",
1876+
"freemarker.ext.rhino",
1877+
"freemarker.ext.servlet",
1878+
"freemarker.ext.util",
1879+
"freemarker.ext.xml",
1880+
"freemarker.log",
1881+
"freemarker.template",
1882+
"freemarker.template.utility"
1883+
],
18571884
"org.glassfish:javax.el": [
18581885
"com.sun.el",
18591886
"com.sun.el.lang",
@@ -2840,6 +2867,7 @@
28402867
"org.apache.xmlgraphics:xmlgraphics-commons",
28412868
"org.apiguardian:apiguardian-api",
28422869
"org.assertj:assertj-core",
2870+
"org.freemarker:freemarker",
28432871
"org.glassfish:javax.el",
28442872
"org.hamcrest:hamcrest-core",
28452873
"org.hibernate:hibernate-validator",

sanitizers/sanitizers.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ _sanitizer_class_names = [
3232
"ScriptEngineInjection",
3333
"ServerSideRequestForgery",
3434
"SqlInjection",
35+
"TemplateInjection",
3536
"UnsafeSanitizer",
3637
"XPathInjection",
3738
"XmlParserSsrfGuidance",

sanitizers/src/main/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ kt_jvm_library(
7979
"OsCommandInjection.kt",
8080
"ReflectiveCall.kt",
8181
"RegexInjection.kt",
82+
"TemplateInjection.kt",
8283
"Utils.kt",
8384
"XPathInjection.kt",
8485
"XmlParserSsrfGuidance.kt",
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2025 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.code_intelligence.jazzer.sanitizers
18+
19+
import com.code_intelligence.jazzer.api.HookType
20+
import com.code_intelligence.jazzer.api.Jazzer
21+
import com.code_intelligence.jazzer.api.MethodHook
22+
import java.io.Reader
23+
import java.lang.invoke.MethodHandle
24+
25+
/**
26+
* Guides FreeMarker templates towards patterns that can trigger OS command injections.
27+
*
28+
* This does not report findings directly; it steers inputs towards OS command injections,
29+
* which are detected by another bug detector.
30+
*/
31+
@Suppress("unused_parameter", "unused")
32+
object TemplateInjection {
33+
private const val FREEMARKER_INJECTION_ATTACK: String = "\${\"freemarker.template.utility.Execute\"?new()(\"jazze\")}"
34+
35+
init {
36+
require(FREEMARKER_INJECTION_ATTACK.length <= 64) {
37+
"FreeMarker injection must fit in a table of recent compares entry (64 bytes)"
38+
}
39+
}
40+
41+
@MethodHook(
42+
type = HookType.BEFORE,
43+
targetClassName = "freemarker.template.Template",
44+
targetMethod = "<init>",
45+
additionalClassesToHook = ["freemarker.template.Template"],
46+
)
47+
@JvmStatic
48+
fun hookFreemarker(
49+
method: MethodHandle?,
50+
thisObject: Any?,
51+
arguments: Array<Any>,
52+
hookId: Int,
53+
) {
54+
(1..2)
55+
.mapNotNull { idx -> (arguments.getOrNull(idx) as? Reader) }
56+
.forEach { reader ->
57+
Jazzer.guideTowardsContainment(
58+
peekMarkableReader(reader, FREEMARKER_INJECTION_ATTACK.length),
59+
FREEMARKER_INJECTION_ATTACK,
60+
hookId,
61+
)
62+
}
63+
}
64+
}

sanitizers/src/test/java/com/code_intelligence/jazzer/sanitizers/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ java_junit5_test(
2626
"@maven//:javax_validation_validation_api",
2727
"@maven//:ognl_ognl",
2828
"@maven//:org_apache_commons_commons_jexl",
29+
"@maven//:org_freemarker_freemarker",
2930
"@maven//:org_junit_jupiter_junit_jupiter_api",
3031
"@maven//:org_junit_jupiter_junit_jupiter_params",
3132
"@maven//:org_mvel_mvel2",

sanitizers/src/test/java/com/example/BUILD.bazel

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,26 @@ java_fuzz_target_test(
314314
],
315315
)
316316

317+
java_fuzz_target_test(
318+
name = "TemplateInjection",
319+
srcs = [
320+
"TemplateInjection.java",
321+
],
322+
allowed_findings = [
323+
"com.code_intelligence.jazzer.api.FuzzerSecurityIssueCritical",
324+
],
325+
# FreeMarker has too many classes to instrument safely
326+
fuzzer_args = [
327+
"--instrumentation_includes=com.example.**",
328+
],
329+
tags = ["dangerous"],
330+
target_class = "com.example.TemplateInjection",
331+
verify_crash_reproducer = False,
332+
deps = [
333+
"@maven//:org_freemarker_freemarker",
334+
],
335+
)
336+
317337
[java_fuzz_target_test(
318338
name = "UnsafeArrayOutOfBounds_" + method,
319339
srcs = [
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2025 Code Intelligence GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example;
18+
19+
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
20+
import freemarker.template.Template;
21+
import java.io.StringReader;
22+
import java.io.StringWriter;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
26+
public class TemplateInjection {
27+
public static void fuzzerTestOneInput(FuzzedDataProvider fdp) throws Exception {
28+
Map<String, Object> model = new HashMap<>();
29+
String data = fdp.consumeRemainingAsString();
30+
try {
31+
Template tmpl = new Template("test", new StringReader(data));
32+
tmpl.process(model, new StringWriter());
33+
} catch (Throwable ignored) {
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)