Skip to content

Commit 0b1e6ff

Browse files
authored
Add IAST taint tracking for DB values (#8072)
1 parent 692835e commit 0b1e6ff

File tree

10 files changed

+539
-26
lines changed

10 files changed

+539
-26
lines changed

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java

+96-24
Original file line numberDiff line numberDiff line change
@@ -15,98 +15,153 @@
1515
import static datadog.trace.api.iast.VulnerabilityMarks.XPATH_INJECTION_MARK;
1616
import static datadog.trace.api.iast.VulnerabilityMarks.XSS_MARK;
1717

18+
import datadog.trace.api.iast.SourceTypes;
1819
import datadog.trace.api.iast.VulnerabilityTypes;
1920
import java.io.File;
21+
import java.util.BitSet;
2022
import java.util.function.BiFunction;
2123
import java.util.zip.CRC32;
2224
import javax.annotation.Nonnull;
2325

2426
public interface VulnerabilityType {
2527

26-
VulnerabilityType WEAK_CIPHER = type(VulnerabilityTypes.WEAK_CIPHER).build();
27-
VulnerabilityType WEAK_HASH = type(VulnerabilityTypes.WEAK_HASH).build();
28+
BitSet DB_EXCLUDED = new BitSet(SourceTypes.SQL_TABLE);
29+
30+
VulnerabilityType WEAK_CIPHER =
31+
type(VulnerabilityTypes.WEAK_CIPHER).excludedSources(DB_EXCLUDED).build();
32+
VulnerabilityType WEAK_HASH =
33+
type(VulnerabilityTypes.WEAK_HASH).excludedSources(DB_EXCLUDED).build();
2834
VulnerabilityType INSECURE_COOKIE =
29-
type(VulnerabilityTypes.INSECURE_COOKIE).hash(VulnerabilityType::evidenceHash).build();
35+
type(VulnerabilityTypes.INSECURE_COOKIE)
36+
.hash(VulnerabilityType::evidenceHash)
37+
.excludedSources(DB_EXCLUDED)
38+
.build();
3039
VulnerabilityType NO_HTTPONLY_COOKIE =
31-
type(VulnerabilityTypes.NO_HTTPONLY_COOKIE).hash(VulnerabilityType::evidenceHash).build();
40+
type(VulnerabilityTypes.NO_HTTPONLY_COOKIE)
41+
.hash(VulnerabilityType::evidenceHash)
42+
.excludedSources(DB_EXCLUDED)
43+
.build();
3244
VulnerabilityType HSTS_HEADER_MISSING =
33-
type(VulnerabilityTypes.HSTS_HEADER_MISSING).hash(VulnerabilityType::serviceHash).build();
45+
type(VulnerabilityTypes.HSTS_HEADER_MISSING)
46+
.hash(VulnerabilityType::serviceHash)
47+
.excludedSources(DB_EXCLUDED)
48+
.build();
3449
VulnerabilityType XCONTENTTYPE_HEADER_MISSING =
3550
type(VulnerabilityTypes.XCONTENTTYPE_HEADER_MISSING)
3651
.hash(VulnerabilityType::serviceHash)
52+
.excludedSources(DB_EXCLUDED)
3753
.build();
3854
VulnerabilityType NO_SAMESITE_COOKIE =
39-
type(VulnerabilityTypes.NO_SAMESITE_COOKIE).hash(VulnerabilityType::evidenceHash).build();
55+
type(VulnerabilityTypes.NO_SAMESITE_COOKIE)
56+
.hash(VulnerabilityType::evidenceHash)
57+
.excludedSources(DB_EXCLUDED)
58+
.build();
4059

4160
VulnerabilityType SQL_INJECTION =
4261
type(VulnerabilityTypes.SQL_INJECTION).mark(SQL_INJECTION_MARK).build();
4362
VulnerabilityType COMMAND_INJECTION =
44-
type(VulnerabilityTypes.COMMAND_INJECTION).mark(COMMAND_INJECTION_MARK).build();
63+
type(VulnerabilityTypes.COMMAND_INJECTION)
64+
.mark(COMMAND_INJECTION_MARK)
65+
.excludedSources(DB_EXCLUDED)
66+
.build();
4567
VulnerabilityType PATH_TRAVERSAL =
4668
type(VulnerabilityTypes.PATH_TRAVERSAL)
4769
.separator(File.separatorChar)
4870
.mark(PATH_TRAVERSAL_MARK)
71+
.excludedSources(DB_EXCLUDED)
4972
.build();
5073
VulnerabilityType LDAP_INJECTION =
51-
type(VulnerabilityTypes.LDAP_INJECTION).mark(LDAP_INJECTION_MARK).build();
52-
VulnerabilityType SSRF = type(VulnerabilityTypes.SSRF).mark(SSRF_MARK).build();
74+
type(VulnerabilityTypes.LDAP_INJECTION)
75+
.mark(LDAP_INJECTION_MARK)
76+
.excludedSources(DB_EXCLUDED)
77+
.build();
78+
VulnerabilityType SSRF =
79+
type(VulnerabilityTypes.SSRF).mark(SSRF_MARK).excludedSources(DB_EXCLUDED).build();
5380
VulnerabilityType UNVALIDATED_REDIRECT =
54-
type(VulnerabilityTypes.UNVALIDATED_REDIRECT).mark(UNVALIDATED_REDIRECT_MARK).build();
55-
VulnerabilityType WEAK_RANDOMNESS = type(VulnerabilityTypes.WEAK_RANDOMNESS).build();
81+
type(VulnerabilityTypes.UNVALIDATED_REDIRECT)
82+
.mark(UNVALIDATED_REDIRECT_MARK)
83+
.excludedSources(DB_EXCLUDED)
84+
.build();
85+
VulnerabilityType WEAK_RANDOMNESS =
86+
type(VulnerabilityTypes.WEAK_RANDOMNESS).excludedSources(DB_EXCLUDED).build();
5687

5788
VulnerabilityType XPATH_INJECTION =
58-
type(VulnerabilityTypes.XPATH_INJECTION).mark(XPATH_INJECTION_MARK).build();
89+
type(VulnerabilityTypes.XPATH_INJECTION)
90+
.mark(XPATH_INJECTION_MARK)
91+
.excludedSources(DB_EXCLUDED)
92+
.build();
5993

6094
VulnerabilityType TRUST_BOUNDARY_VIOLATION =
61-
type(VulnerabilityTypes.TRUST_BOUNDARY_VIOLATION).mark(TRUST_BOUNDARY_VIOLATION_MARK).build();
95+
type(VulnerabilityTypes.TRUST_BOUNDARY_VIOLATION)
96+
.mark(TRUST_BOUNDARY_VIOLATION_MARK)
97+
.excludedSources(DB_EXCLUDED)
98+
.build();
6299

63100
VulnerabilityType XSS = type(VulnerabilityTypes.XSS).mark(XSS_MARK).build();
64101

65102
VulnerabilityType HEADER_INJECTION =
66-
type(VulnerabilityTypes.HEADER_INJECTION).mark(HEADER_INJECTION_MARK).build();
103+
type(VulnerabilityTypes.HEADER_INJECTION)
104+
.mark(HEADER_INJECTION_MARK)
105+
.excludedSources(DB_EXCLUDED)
106+
.build();
67107

68-
VulnerabilityType STACKTRACE_LEAK = type(VulnerabilityTypes.STACKTRACE_LEAK).build();
108+
VulnerabilityType STACKTRACE_LEAK =
109+
type(VulnerabilityTypes.STACKTRACE_LEAK).excludedSources(DB_EXCLUDED).build();
69110

70-
VulnerabilityType VERB_TAMPERING = type(VulnerabilityTypes.VERB_TAMPERING).build();
111+
VulnerabilityType VERB_TAMPERING =
112+
type(VulnerabilityTypes.VERB_TAMPERING).excludedSources(DB_EXCLUDED).build();
71113

72114
VulnerabilityType ADMIN_CONSOLE_ACTIVE =
73115
type(VulnerabilityTypes.ADMIN_CONSOLE_ACTIVE)
74116
.deduplicable(false)
75117
.hash(VulnerabilityType::serviceHash)
118+
.excludedSources(DB_EXCLUDED)
76119
.build();
77120

78121
VulnerabilityType DEFAULT_HTML_ESCAPE_INVALID =
79-
type(VulnerabilityTypes.DEFAULT_HTML_ESCAPE_INVALID).build();
122+
type(VulnerabilityTypes.DEFAULT_HTML_ESCAPE_INVALID).excludedSources(DB_EXCLUDED).build();
80123

81-
VulnerabilityType SESSION_TIMEOUT = type(VulnerabilityTypes.SESSION_TIMEOUT).build();
124+
VulnerabilityType SESSION_TIMEOUT =
125+
type(VulnerabilityTypes.SESSION_TIMEOUT).excludedSources(DB_EXCLUDED).build();
82126

83127
VulnerabilityType DIRECTORY_LISTING_LEAK =
84-
type(VulnerabilityTypes.DIRECTORY_LISTING_LEAK).build();
85-
VulnerabilityType INSECURE_JSP_LAYOUT = type(VulnerabilityTypes.INSECURE_JSP_LAYOUT).build();
128+
type(VulnerabilityTypes.DIRECTORY_LISTING_LEAK).excludedSources(DB_EXCLUDED).build();
129+
VulnerabilityType INSECURE_JSP_LAYOUT =
130+
type(VulnerabilityTypes.INSECURE_JSP_LAYOUT).excludedSources(DB_EXCLUDED).build();
86131

87-
VulnerabilityType HARDCODED_SECRET = type(VulnerabilityTypes.HARDCODED_SECRET).build();
132+
VulnerabilityType HARDCODED_SECRET =
133+
type(VulnerabilityTypes.HARDCODED_SECRET).excludedSources(DB_EXCLUDED).build();
88134

89135
VulnerabilityType INSECURE_AUTH_PROTOCOL =
90-
type(VulnerabilityTypes.INSECURE_AUTH_PROTOCOL).hash(VulnerabilityType::evidenceHash).build();
136+
type(VulnerabilityTypes.INSECURE_AUTH_PROTOCOL)
137+
.hash(VulnerabilityType::evidenceHash)
138+
.excludedSources(DB_EXCLUDED)
139+
.build();
91140

92141
VulnerabilityType REFLECTION_INJECTION =
93-
type(VulnerabilityTypes.REFLECTION_INJECTION).mark(REFLECTION_INJECTION_MARK).build();
142+
type(VulnerabilityTypes.REFLECTION_INJECTION)
143+
.mark(REFLECTION_INJECTION_MARK)
144+
.excludedSources(DB_EXCLUDED)
145+
.build();
94146

95147
VulnerabilityType SESSION_REWRITING =
96148
type(VulnerabilityTypes.SESSION_REWRITING)
97149
.deduplicable(false)
98150
.hash(VulnerabilityType::serviceHash)
151+
.excludedSources(DB_EXCLUDED)
99152
.build();
100153

101154
VulnerabilityType DEFAULT_APP_DEPLOYED =
102155
type(VulnerabilityTypes.DEFAULT_APP_DEPLOYED)
103156
.deduplicable(false)
104157
.hash(VulnerabilityType::serviceHash)
158+
.excludedSources(DB_EXCLUDED)
105159
.build();
106160

107161
VulnerabilityType UNTRUSTED_DESERIALIZATION =
108162
type(VulnerabilityTypes.UNTRUSTED_DESERIALIZATION)
109163
.mark(UNTRUSTED_DESERIALIZATION_MARK)
164+
.excludedSources(DB_EXCLUDED)
110165
.build();
111166

112167
/* All vulnerability types that have a mark. Should be updated if new vulnerabilityType with mark is added */
@@ -139,6 +194,8 @@ public interface VulnerabilityType {
139194

140195
byte type();
141196

197+
BitSet excludedSources();
198+
142199
static Builder type(final byte type) {
143200
return new Builder(type);
144201
}
@@ -153,18 +210,22 @@ class VulnerabilityTypeImpl implements VulnerabilityType {
153210

154211
private final boolean deduplicable;
155212

213+
private final BitSet excludedSources;
214+
156215
private final BiFunction<VulnerabilityType, Vulnerability, Long> hash;
157216

158217
public VulnerabilityTypeImpl(
159218
final byte type,
160219
final char separator,
161220
final int mark,
162221
final boolean deduplicable,
222+
final BitSet excludedSources,
163223
final BiFunction<VulnerabilityType, Vulnerability, Long> hash) {
164224
this.type = type;
165225
this.separator = separator;
166226
this.mark = mark;
167227
this.deduplicable = deduplicable;
228+
this.excludedSources = excludedSources;
168229
this.hash = hash;
169230
}
170231

@@ -198,6 +259,11 @@ public byte type() {
198259
return type;
199260
}
200261

262+
@Override
263+
public BitSet excludedSources() {
264+
return excludedSources;
265+
}
266+
201267
/** Useful for troubleshooting issues when vulns are serialized without moshi */
202268
public String getName() {
203269
return name();
@@ -209,6 +275,7 @@ class Builder {
209275
private char separator = ' ';
210276
private int mark = NOT_MARKED;
211277
private boolean deduplicable = true;
278+
private BitSet excludedSources = new BitSet();
212279
private BiFunction<VulnerabilityType, Vulnerability, Long> hash =
213280
VulnerabilityType::fileAndLineHash;
214281

@@ -231,13 +298,18 @@ public Builder deduplicable(final boolean deduplicable) {
231298
return this;
232299
}
233300

301+
public Builder excludedSources(final BitSet excludedSources) {
302+
this.excludedSources = excludedSources;
303+
return this;
304+
}
305+
234306
public Builder hash(final BiFunction<VulnerabilityType, Vulnerability, Long> hash) {
235307
this.hash = hash;
236308
return this;
237309
}
238310

239311
public VulnerabilityType build() {
240-
return new VulnerabilityTypeImpl(type, separator, mark, deduplicable, hash);
312+
return new VulnerabilityTypeImpl(type, separator, mark, deduplicable, excludedSources, hash);
241313
}
242314
}
243315

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,21 @@ protected final Evidence checkInjection(
148148
return null;
149149
}
150150

151+
// filter excluded ranges
152+
final Range[] filteredRanges;
153+
if (!type.excludedSources().isEmpty()) {
154+
filteredRanges = Ranges.excludeRangesBySource(valueRanges, type.excludedSources());
155+
} else {
156+
filteredRanges = valueRanges;
157+
}
158+
159+
if (filteredRanges == null || filteredRanges.length == 0) {
160+
return null;
161+
}
162+
151163
final StringBuilder evidence = new StringBuilder();
152164
final RangeBuilder ranges = new RangeBuilder();
153-
addToEvidence(type, evidence, ranges, value, valueRanges, evidenceBuilder);
165+
addToEvidence(type, evidence, ranges, value, filteredRanges, evidenceBuilder);
154166

155167
// check if finally we have an injection
156168
if (ranges.isEmpty()) {

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

+19
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.datadog.iast.util.Ranged;
1111
import com.datadog.iast.util.StringUtils;
1212
import datadog.trace.api.iast.SourceTypes;
13+
import java.util.BitSet;
1314
import javax.annotation.Nonnull;
1415
import javax.annotation.Nullable;
1516

@@ -426,4 +427,22 @@ public static Range[] splitRanges(
426427

427428
return splittedRanges;
428429
}
430+
431+
/**
432+
* Remove the ranges that have the same origin as the input source.
433+
*
434+
* @param ranges the ranges to filter
435+
* @param source the byte value of the source to exclude (see {@link SourceTypes})
436+
*/
437+
public static Range[] excludeRangesBySource(Range[] ranges, BitSet source) {
438+
RangeBuilder newRanges = new RangeBuilder(ranges.length);
439+
440+
for (Range range : ranges) {
441+
if (!source.get(range.getSource().getOrigin())) {
442+
newRanges.add(range);
443+
}
444+
}
445+
446+
return newRanges.toArray();
447+
}
429448
}

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/taint/RangesTest.groovy

+28
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import static com.datadog.iast.util.HttpHeader.LOCATION
1212
import static com.datadog.iast.util.HttpHeader.REFERER
1313
import static datadog.trace.api.iast.SourceTypes.GRPC_BODY
1414
import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_VALUE
15+
import static datadog.trace.api.iast.SourceTypes.REQUEST_QUERY
16+
import static datadog.trace.api.iast.SourceTypes.SQL_TABLE
1517
import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED
1618
import static com.datadog.iast.taint.Ranges.mergeRanges
1719
import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_NAME
@@ -378,6 +380,24 @@ class RangesTest extends DDSpecification {
378380
1 | 3 | 2 | range(8, 8) | 0 | 0 | []
379381
}
380382

383+
void 'test excludeRangesBySource method'() {
384+
when:
385+
final result = Ranges.excludeRangesBySource(ranges as Range[], source as BitSet)
386+
387+
then:
388+
final expectedArray = expected as Range[]
389+
result == expectedArray
390+
391+
where:
392+
ranges | source | expected
393+
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(SQL_TABLE) | [range(5, 3)]
394+
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(SQL_TABLE, REQUEST_QUERY) | [range(5, 3)]
395+
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(REQUEST_HEADER_NAME) | [rangeWithSource(0, 5, SQL_TABLE)]
396+
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(REQUEST_QUERY) | [rangeWithSource(0, 5, SQL_TABLE), range(5, 3)]
397+
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(REQUEST_QUERY, REQUEST_HEADER_NAME) | [rangeWithSource(0, 5, SQL_TABLE)]
398+
[] | bitSetOf(SQL_TABLE) | []
399+
}
400+
381401
Range[] rangesFromSpec(List<List<Object>> spec) {
382402
def ranges = new Range[spec.size()]
383403
int j = 0
@@ -417,4 +437,12 @@ class RangesTest extends DDSpecification {
417437
Range rangeWithSource(final int start, final int length, final byte source, final String name = 'name', final String value = 'value') {
418438
return new Range(start, length, new Source(source, name, value), NOT_MARKED)
419439
}
440+
441+
BitSet bitSetOf(byte... values) {
442+
BitSet bitSet = new BitSet()
443+
for (byte value : values) {
444+
bitSet.set(value)
445+
}
446+
return bitSet
447+
}
420448
}

0 commit comments

Comments
 (0)