Skip to content

Commit b3e2ecd

Browse files
authored
Limit the maximum size of the location path in IAST vulnerabilities (#9028)
What Does This Do Add truncation to path, class and method if it's necessary for LocationSuppliers to report XSS vulnerabilities Motivation incident-39654 In this incident, it was reported that the location.path field of an IAST vulnerability was populated with a large HTML payload, which caused a backend error and prevented the vulnerability from being reported. This occurred specifically with an XSS vulnerability located in a Thymeleaf template. Normally, the location.path is extracted from the stacktrace, so this kind of behavior is unusual. However, in cases where vulnerabilities occur in template-based frameworks, we use a different approach to improve precision — specifying the template name instead of the compiled class in the vulnerability location. In Thymeleaf, the instrumented method getTemplateName may return a full HTML document instead of just the template name, as originally expected. To guard against these cases, we’ve decided to truncate the values of path, class, and method when they are generated using suppliers rather than stacktrace-based extraction.
1 parent 1666056 commit b3e2ecd

File tree

4 files changed

+117
-3
lines changed

4 files changed

+117
-3
lines changed

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/XssModuleImpl.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
public class XssModuleImpl extends SinkModuleBase implements XssModule {
1515

16+
private static final int MAX_LENGTH = 500;
17+
1618
public XssModuleImpl(final Dependencies dependencies) {
1719
super(dependencies);
1820
}
@@ -61,6 +63,13 @@ public void onXss(@Nonnull CharSequence s, @Nullable String file, int line) {
6163
checkInjection(VulnerabilityType.XSS, s, new FileAndLineLocationSupplier(file, line));
6264
}
6365

66+
private static String truncate(final String s) {
67+
if (s == null || s.length() <= MAX_LENGTH) {
68+
return s;
69+
}
70+
return s.substring(0, MAX_LENGTH);
71+
}
72+
6473
private static class ClassMethodLocationSupplier implements LocationSupplier {
6574
private final String clazz;
6675
private final String method;
@@ -72,7 +81,7 @@ private ClassMethodLocationSupplier(final String clazz, final String method) {
7281

7382
@Override
7483
public Location build(final @Nullable AgentSpan span) {
75-
return Location.forSpanAndClassAndMethod(span, clazz, method);
84+
return Location.forSpanAndClassAndMethod(span, truncate(clazz), truncate(method));
7685
}
7786
}
7887

@@ -87,7 +96,7 @@ private FileAndLineLocationSupplier(final String file, final int line) {
8796

8897
@Override
8998
public Location build(@Nullable final AgentSpan span) {
90-
return Location.forSpanAndFileAndLine(span, file, line);
99+
return Location.forSpanAndFileAndLine(span, truncate(file), line);
91100
}
92101
}
93102
}

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XssModuleTest.groovy

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,45 @@ class XssModuleTest extends IastModuleImplTestBase {
155155
'/==>var<==' | VulnerabilityMarks.SQL_INJECTION_MARK| "/==>var<=="
156156
}
157157
158+
void 'class and method names are truncated when exceeding max length'() {
159+
setup:
160+
final param = mapTainted('/==>value<==', NOT_MARKED)
161+
final clazz = 'c' * 600
162+
final method = 'm' * 600
163+
164+
when:
165+
module.onXss(param, clazz, method)
166+
167+
then:
168+
1 * reporter.report(_, _) >> { args ->
169+
final vuln = args[1] as Vulnerability
170+
assertEvidence(vuln, '/==>value<==')
171+
assert vuln.location.path.length() == 500
172+
assert vuln.location.method.length() == 500
173+
assert vuln.location.path == clazz.substring(0, 500)
174+
assert vuln.location.method == method.substring(0, 500)
175+
}
176+
}
177+
178+
void 'file name is truncated when exceeding max length'() {
179+
setup:
180+
final param = mapTainted('/==>value<==', NOT_MARKED)
181+
final file = 'f' * 600
182+
final line = 42
183+
184+
when:
185+
module.onXss(param as CharSequence, file, line)
186+
187+
then:
188+
1 * reporter.report(_, _) >> { args ->
189+
final vuln = args[1] as Vulnerability
190+
assertEvidence(vuln, '/==>value<==')
191+
assert vuln.location.path.length() == 500
192+
assert vuln.location.line == line
193+
assert vuln.location.path == file.substring(0, 500)
194+
}
195+
}
196+
158197
159198
private String mapTainted(final String value, final int mark) {
160199
final result = addFromTaintFormat(ctx.taintedObjects, value, mark)

dd-smoke-tests/springboot-thymeleaf/src/main/java/datadog/smoketest/springboot/XssController.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,43 @@
55
import org.springframework.web.bind.annotation.GetMapping;
66
import org.springframework.web.bind.annotation.RequestMapping;
77
import org.springframework.web.bind.annotation.RequestParam;
8+
import org.springframework.web.bind.annotation.ResponseBody;
9+
import org.thymeleaf.context.Context;
10+
import org.thymeleaf.spring5.SpringTemplateEngine;
11+
import org.thymeleaf.templateresolver.StringTemplateResolver;
812

913
@Controller
1014
@RequestMapping("/xss")
1115
public class XssController {
1216

17+
private static final String TEMPLATE = "<p th:utext=\"${xss}\">Test!</p>";
18+
19+
private static final String BIG_TEMPLATE =
20+
new String(new char[500]).replace('\0', 'A') + "<p th:utext=\"${xss}\">Test!</p>";
21+
1322
@GetMapping("/utext")
1423
public String utext(@RequestParam(name = "string") String name, Model model) {
1524
model.addAttribute("xss", name);
1625
return "utext";
1726
}
27+
28+
@GetMapping(value = "/string-template", produces = "text/html")
29+
@ResponseBody
30+
public String stringTemplate(@RequestParam(name = "string") String name) {
31+
SpringTemplateEngine engine = new SpringTemplateEngine();
32+
engine.setTemplateResolver(new StringTemplateResolver());
33+
Context context = new Context();
34+
context.setVariable("xss", name);
35+
return engine.process(TEMPLATE, context);
36+
}
37+
38+
@GetMapping(value = "/big-string-template", produces = "text/html")
39+
@ResponseBody
40+
public String bigStringTemplate(@RequestParam(name = "string") String name) {
41+
SpringTemplateEngine engine = new SpringTemplateEngine();
42+
engine.setTemplateResolver(new StringTemplateResolver());
43+
Context context = new Context();
44+
context.setVariable("xss", name);
45+
return engine.process(BIG_TEMPLATE, context);
46+
}
1847
}

dd-smoke-tests/springboot-thymeleaf/src/test/groovy/datadog/smoketest/springboot/IastSpringBootThymeleafSmokeTest.groovy

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,50 @@ class IastSpringBootThymeleafSmokeTest extends AbstractIastServerSmokeTest {
3737
final request = new Request.Builder().url(url).get().build()
3838

3939
when:
40-
client.newCall(request).execute()
40+
def response = client.newCall(request).execute()
4141

4242
then:
43+
response.code() == 200
4344
hasVulnerability { vul -> vul.type == 'XSS' && vul.location.path == templateName && vul.location.line == line }
4445

4546
where:
4647
method | param |templateName| line
4748
'utext' | 'test' | 'utext' | 12
4849
}
50+
51+
void 'xss with string template returns html as template name'() {
52+
setup:
53+
final param = '<script>'
54+
final encoded = URLEncoder.encode(param, 'UTF-8')
55+
final url = "http://localhost:${httpPort}/xss/string-template?string=${encoded}"
56+
final request = new Request.Builder().url(url).get().build()
57+
58+
when:
59+
client.newCall(request).execute()
60+
61+
then:
62+
hasVulnerability { vul ->
63+
vul.type == 'XSS' &&
64+
vul.location.path == '<p th:utext="${xss}">Test!</p>' &&
65+
vul.location.line == 1
66+
}
67+
}
68+
69+
void 'xss with string template returns html as template name - truncated'() {
70+
setup:
71+
final param = '<script>'
72+
final encoded = URLEncoder.encode(param, 'UTF-8')
73+
final url = "http://localhost:${httpPort}/xss/big-string-template?string=${encoded}"
74+
final request = new Request.Builder().url(url).get().build()
75+
76+
when:
77+
client.newCall(request).execute()
78+
79+
then:
80+
hasVulnerability { vul ->
81+
vul.type == 'XSS' &&
82+
vul.location.path == 'A'*500 &&
83+
vul.location.line == 1
84+
}
85+
}
4986
}

0 commit comments

Comments
 (0)