Skip to content

Commit 9f2354e

Browse files
jandro996devflow.devflow-routing-intake
andauthored
Skip XSS check for freemarker built-in escaping expressions in 2.3.24 instrumentation (#10865)
fix(iast): skip XSS check for freemarker built-in escaping expressions in 2.3.24 instrumentation Fixed. Replaced instanceof BuiltIn with instanceof BuiltInForLegacyEscaping, the abstract base class that covers exactly ?html, ?xml, and ?xhtml in freemarker 2.3.24. Non-escaping built-ins like ?upper_case and ?js_string extend different base classes and are unaffected. Added parameterized unit tests to cover all cases. Merge branch 'master' into alejandro.gonzalez/fix-iast-freemarker Merge branch 'master' into alejandro.gonzalez/fix-iast-freemarker Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent d5e870e commit 9f2354e

File tree

3 files changed

+65
-5
lines changed

3 files changed

+65
-5
lines changed

dd-java-agent/instrumentation/freemarker/freemarker-2.3.24/src/main/java/datadog/trace/instrumentation/freemarker24/DollarVariableDatadogAdvice.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public static void onEnter(
2727
return;
2828
}
2929
String charSec = DollarVariable24Helper.fetchCharSec(self, environment);
30+
if (charSec == null) {
31+
return;
32+
}
3033
final String templateName = environment.getMainTemplate().getName();
3134
final int line = DollarVariable24Helper.fetchBeginLine(self);
3235
xssModule.onXss(charSec, templateName, line);

dd-java-agent/instrumentation/freemarker/freemarker-2.3.24/src/main/java/freemarker/core/DollarVariable24Helper.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ private DollarVariable24Helper() {}
1212
private static final Logger log = LoggerFactory.getLogger(DollarVariable24Helper.class);
1313

1414
private static final Field AUTO_ESCAPE = prepareAutoEscape();
15+
private static final Field ESCAPED_EXPRESSION = prepareEscapedExpression();
1516

1617
private static Field prepareAutoEscape() {
1718
Field autoEscape = null;
@@ -25,6 +26,17 @@ private static Field prepareAutoEscape() {
2526
return autoEscape;
2627
}
2728

29+
private static Field prepareEscapedExpression() {
30+
try {
31+
Field escapedExpression = DollarVariable.class.getDeclaredField("escapedExpression");
32+
escapedExpression.setAccessible(true);
33+
return escapedExpression;
34+
} catch (Throwable e) {
35+
log.debug("Failed to get DollarVariable escapedExpression", e);
36+
return null;
37+
}
38+
}
39+
2840
public static boolean fetchAutoEscape(Object dollarVariable) {
2941
if (AUTO_ESCAPE == null || !(dollarVariable instanceof DollarVariable)) {
3042
return true;
@@ -40,6 +52,15 @@ public static String fetchCharSec(Object object, Environment environment) {
4052
if (!(object instanceof DollarVariable)) {
4153
return null;
4254
}
55+
if (ESCAPED_EXPRESSION != null) {
56+
try {
57+
if (ESCAPED_EXPRESSION.get(object) instanceof BuiltInForLegacyEscaping) {
58+
return null;
59+
}
60+
} catch (IllegalAccessException e) {
61+
throw new UndeclaredThrowableException(e);
62+
}
63+
}
4364
try {
4465
return (String) ((DollarVariable) object).calculateInterpolatedStringOrMarkup(environment);
4566
} catch (TemplateException e) {

dd-java-agent/instrumentation/freemarker/freemarker-2.3.24/src/test/groovy/datadog/trace/instrumentation/freemarker24/DollarVariableInstrumentationTest.groovy

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,60 @@ class DollarVariableInstrumentationTest extends InstrumentationSpecification {
1515
injectSysConfig('dd.iast.enabled', 'true')
1616
}
1717

18-
void 'test freemarker process'() {
18+
void 'plain interpolation reports xss'() {
1919
given:
2020
final module = Mock(XssModule)
2121
InstrumentationBridge.registerIastModule(module)
2222

2323
final Configuration cfg = new Configuration()
24-
final Template template = new Template("test", new StringReader("test \${$stringExpression}"), cfg)
24+
final Template template = new Template("test", new StringReader('test ${name}'), cfg)
2525
final TemplateHashModel rootDataModel = new SimpleHash(cfg.getObjectWrapper())
26-
rootDataModel.put(stringExpression, expression)
26+
rootDataModel.put("name", "<script>alert(1)</script>")
2727

2828
when:
2929
template.process(rootDataModel, Mock(FileWriter))
3030

3131
then:
3232
1 * module.onXss(_, _, _)
33+
}
34+
35+
void 'non-escaping built-in still reports xss'() {
36+
given:
37+
final module = Mock(XssModule)
38+
InstrumentationBridge.registerIastModule(module)
39+
40+
final Configuration cfg = new Configuration()
41+
final Template template = new Template("test", new StringReader("test \${name?$builtIn}"), cfg)
42+
final TemplateHashModel rootDataModel = new SimpleHash(cfg.getObjectWrapper())
43+
rootDataModel.put("name", "<script>alert(1)</script>")
44+
45+
when:
46+
template.process(rootDataModel, Mock(FileWriter))
47+
48+
then:
49+
1 * module.onXss(_, _, _)
50+
51+
where:
52+
builtIn << ['upper_case', 'js_string', 'j_string']
53+
}
54+
55+
void 'html/xml escaping built-ins do not report xss'() {
56+
given:
57+
final module = Mock(XssModule)
58+
InstrumentationBridge.registerIastModule(module)
59+
60+
final Configuration cfg = new Configuration()
61+
final Template template = new Template("test", new StringReader("test \${name?$builtIn}"), cfg)
62+
final TemplateHashModel rootDataModel = new SimpleHash(cfg.getObjectWrapper())
63+
rootDataModel.put("name", "<script>alert(1)</script>")
64+
65+
when:
66+
template.process(rootDataModel, Mock(FileWriter))
67+
68+
then:
69+
0 * module.onXss(_, _, _)
3370

3471
where:
35-
stringExpression | expression
36-
"test" | "test"
72+
builtIn << ['html', 'xml', 'xhtml']
3773
}
3874
}

0 commit comments

Comments
 (0)