Skip to content

Commit 00358aa

Browse files
Try to match tainted objects with sources when checking vulnerabilities with unbounded objects (DataDog#6122)
1 parent b1ae59a commit 00358aa

File tree

3 files changed

+87
-4
lines changed

3 files changed

+87
-4
lines changed

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.datadog.iast.model.Evidence;
1010
import com.datadog.iast.model.Location;
1111
import com.datadog.iast.model.Range;
12+
import com.datadog.iast.model.Source;
1213
import com.datadog.iast.model.Vulnerability;
1314
import com.datadog.iast.model.VulnerabilityType;
1415
import com.datadog.iast.model.VulnerabilityType.InjectionType;
@@ -57,7 +58,7 @@ protected SinkModuleBase(@Nonnull final Dependencies dependencies) {
5758
if (!overheadController.consumeQuota(Operations.REPORT_VULNERABILITY, span)) {
5859
return null;
5960
}
60-
final Evidence result = new Evidence(value.toString(), ranges);
61+
final Evidence result = buildEvidence(value, ranges);
6162
report(span, type, result);
6263
return result;
6364
}
@@ -119,7 +120,7 @@ protected SinkModuleBase(@Nonnull final Dependencies dependencies) {
119120
return null;
120121
}
121122

122-
final Evidence result = new Evidence(evidence, notMarkedRanges);
123+
final Evidence result = buildEvidence(evidence, notMarkedRanges);
123124
report(span, type, result);
124125
return result;
125126
}
@@ -161,7 +162,7 @@ protected SinkModuleBase(@Nonnull final Dependencies dependencies) {
161162
if (notMarkedRanges == null || notMarkedRanges.length == 0) {
162163
return null;
163164
}
164-
final Evidence result = new Evidence(evidence.toString(), notMarkedRanges);
165+
final Evidence result = buildEvidence(evidence, notMarkedRanges);
165166
report(span, type, result);
166167
return result;
167168
}
@@ -179,6 +180,24 @@ protected StackTraceElement getCurrentStackTrace() {
179180
return stackWalker.walk(SinkModuleBase::findValidPackageForVulnerability);
180181
}
181182

183+
protected Evidence buildEvidence(final Object value, final Range[] ranges) {
184+
final Range unbound = Ranges.findUnbound(ranges);
185+
if (unbound != null) {
186+
final Source source = unbound.getSource();
187+
if (source != null && source.getValue() != null) {
188+
final String sourceValue = source.getValue();
189+
final String evidenceValue = value.toString();
190+
final int start = evidenceValue.indexOf(sourceValue);
191+
if (start >= 0) {
192+
return new Evidence(
193+
evidenceValue,
194+
new Range[] {new Range(start, sourceValue.length(), source, unbound.getMarks())});
195+
}
196+
}
197+
}
198+
return new Evidence(value instanceof String ? (String) value : value.toString(), ranges);
199+
}
200+
182201
static StackTraceElement findValidPackageForVulnerability(
183202
@Nonnull final Stream<StackTraceElement> stream) {
184203
final StackTraceElement[] first = new StackTraceElement[1];

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/taint/Ranges.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ public static Range[] forObject(final @Nonnull Source source, final int mark) {
5959
return new Range[] {new Range(0, Integer.MAX_VALUE, source, mark)};
6060
}
6161

62+
@Nullable
63+
public static Range findUnbound(@Nonnull final Range[] ranges) {
64+
if (ranges.length != 1) {
65+
return null;
66+
}
67+
final Range range = ranges[0];
68+
return range.getStart() == 0 && range.getLength() == Integer.MAX_VALUE ? range : null;
69+
}
70+
6271
public static void copyShift(
6372
final @Nonnull Range[] src, final @Nonnull Range[] dst, final int dstPos, final int shift) {
6473
copyShift(src, dst, dstPos, shift, src.length);

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

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,37 @@
11
package com.datadog.iast.sink
22

33
import com.datadog.iast.IastModuleImplTestBase
4+
import com.datadog.iast.IastRequestContext
5+
import com.datadog.iast.model.Source
6+
import com.datadog.iast.overhead.Operations
7+
import com.datadog.iast.propagation.PropagationModuleImpl
8+
import com.datadog.iast.taint.Ranges
9+
import datadog.trace.api.gateway.RequestContext
10+
import datadog.trace.api.gateway.RequestContextSlot
11+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan
412

5-
class AbstractSinkModuleTest extends IastModuleImplTestBase {
13+
import static com.datadog.iast.model.VulnerabilityType.SSRF
14+
import static datadog.trace.api.iast.SourceTypes.REQUEST_PARAMETER_VALUE
15+
16+
class AbstractSinkModuleTest extends IastModuleImplTestBase {
617

718
final StackTraceElement ignoredPackageClassElement = element("org.springframework.Ignored")
819
final StackTraceElement notIgnoredPackageClassElement = element("datadog.smoketest.NotIgnored")
920
final StackTraceElement notInIastExclusionTrie = element("not.in.iast.exclusion.Class")
1021

22+
private IastRequestContext ctx
23+
private AgentSpan span
24+
25+
void setup() {
26+
ctx = new IastRequestContext()
27+
final reqCtx = Mock(RequestContext) {
28+
getData(RequestContextSlot.IAST) >> ctx
29+
}
30+
span = Mock(AgentSpan) {
31+
getRequestContext() >> reqCtx
32+
}
33+
tracer.activeSpan() >> span
34+
}
1135

1236
void 'filter ignored package element from stack'() {
1337

@@ -43,6 +67,37 @@ class AbstractSinkModuleTest extends IastModuleImplTestBase {
4367
result == expected
4468
}
4569

70+
void 'test reporting evidence on objects'() {
71+
given:
72+
overheadController.consumeQuota(Operations.REPORT_VULNERABILITY, span) >> true
73+
final sink = new SinkModuleBase(dependencies) {}
74+
final propagation = new PropagationModuleImpl()
75+
final input = new String(source.value)
76+
ctx.getTaintedObjects().taint(input, Ranges.forCharSequence(input, source))
77+
78+
when:
79+
propagation.taintIfTainted(toReport, input)
80+
final evidence = sink.checkInjection(span, ctx, SSRF, toReport)
81+
82+
then:
83+
evidence.ranges.length == 1
84+
final range = evidence.ranges[0]
85+
if (matches) {
86+
final taintedEvidence = evidence.value.substring(range.start, range.start + range.length)
87+
taintedEvidence == input
88+
} else {
89+
final taintedEvidence = evidence.value
90+
taintedEvidence != input
91+
}
92+
93+
where:
94+
source | toReport | matches
95+
new Source(REQUEST_PARAMETER_VALUE, 'url', 'datadog.com') | new URL('https://datadog.com/index.html') | true
96+
new Source(REQUEST_PARAMETER_VALUE, 'url', 'datadog.com') | new URI('https://datadog.com/index.html') | true
97+
new Source(REQUEST_PARAMETER_VALUE, 'url', 'datadog.com') | new URI('https://dAtAdOg.com/index.html') | false
98+
new Source(REQUEST_PARAMETER_VALUE, 'url', 'datadog.com') | new URI('https://dAtAdOg.com/index.html') | false
99+
}
100+
46101
private StackTraceElement element(final String declaringClass) {
47102
return new StackTraceElement(declaringClass, "method", "fileName", 1)
48103
}

0 commit comments

Comments
 (0)