Skip to content

Commit 2cf53f0

Browse files
Try to match tainted objects with sources when checking vulnerabilities with unbounded objects
1 parent 0eaf4a2 commit 2cf53f0

File tree

3 files changed

+88
-4
lines changed

3 files changed

+88
-4
lines changed

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

+23-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import static com.datadog.iast.util.ObjectVisitor.State.CONTINUE;
44
import static com.datadog.iast.util.ObjectVisitor.State.EXIT;
5+
import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED;
56

67
import com.datadog.iast.Dependencies;
78
import com.datadog.iast.IastRequestContext;
89
import com.datadog.iast.Reporter;
910
import com.datadog.iast.model.Evidence;
1011
import com.datadog.iast.model.Location;
1112
import com.datadog.iast.model.Range;
13+
import com.datadog.iast.model.Source;
1214
import com.datadog.iast.model.Vulnerability;
1315
import com.datadog.iast.model.VulnerabilityType;
1416
import com.datadog.iast.model.VulnerabilityType.InjectionType;
@@ -57,7 +59,7 @@ protected SinkModuleBase(@Nonnull final Dependencies dependencies) {
5759
if (!overheadController.consumeQuota(Operations.REPORT_VULNERABILITY, span)) {
5860
return null;
5961
}
60-
final Evidence result = new Evidence(value.toString(), ranges);
62+
final Evidence result = buildEvidence(value, ranges);
6163
report(span, type, result);
6264
return result;
6365
}
@@ -119,7 +121,7 @@ protected SinkModuleBase(@Nonnull final Dependencies dependencies) {
119121
return null;
120122
}
121123

122-
final Evidence result = new Evidence(evidence, notMarkedRanges);
124+
final Evidence result = buildEvidence(evidence, notMarkedRanges);
123125
report(span, type, result);
124126
return result;
125127
}
@@ -161,7 +163,7 @@ protected SinkModuleBase(@Nonnull final Dependencies dependencies) {
161163
if (notMarkedRanges == null || notMarkedRanges.length == 0) {
162164
return null;
163165
}
164-
final Evidence result = new Evidence(evidence.toString(), notMarkedRanges);
166+
final Evidence result = buildEvidence(evidence, notMarkedRanges);
165167
report(span, type, result);
166168
return result;
167169
}
@@ -179,6 +181,24 @@ protected StackTraceElement getCurrentStackTrace() {
179181
return stackWalker.walk(SinkModuleBase::findValidPackageForVulnerability);
180182
}
181183

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

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

+9
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

+56-1
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)