Skip to content

Commit b0e6c61

Browse files
authored
Send IAST vulnerability secure marks to backend (#7645)
What Does This Do Added the ability for ranges to return the types of vulnerabilities they are marked for. A closed list of vulnerability types with assigned marks has been created. The encoding and redaction of vulnerability evidence were updated to include a new secure_marks field in the IAST JSON, which holds an array of vulnerability types for which the evidence is marked. Motivation Send vulnerability secure marks to allow backend to recalculate vulnerability score
1 parent 716ecbd commit b0e6c61

File tree

9 files changed

+383
-8
lines changed

9 files changed

+383
-8
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
import com.datadog.iast.model.json.SourceIndex;
66
import com.datadog.iast.util.Ranged;
7+
import java.util.HashSet;
78
import java.util.Objects;
9+
import java.util.Set;
810
import java.util.StringJoiner;
911
import javax.annotation.Nonnegative;
1012
import javax.annotation.Nonnull;
13+
import javax.annotation.Nullable;
1114

1215
public final class Range implements Ranged {
1316

@@ -91,4 +94,17 @@ public Range consolidate() {
9194
return new Range(
9295
start, length, new Source(source.getOrigin(), source.getName(), source.getValue()), marks);
9396
}
97+
98+
public @Nullable Set<VulnerabilityType> getMarkedVulnerabilities() {
99+
if (marks == NOT_MARKED) {
100+
return null;
101+
}
102+
Set<VulnerabilityType> vulnerabilities = new HashSet<>();
103+
for (VulnerabilityType type : VulnerabilityType.MARKED_VULNERABILITIES) {
104+
if ((marks & type.mark()) != 0) {
105+
vulnerabilities.add(type);
106+
}
107+
}
108+
return vulnerabilities;
109+
}
94110
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ public interface VulnerabilityType {
109109
.mark(UNTRUSTED_DESERIALIZATION_MARK)
110110
.build();
111111

112+
/* All vulnerability types that have a mark. Should be updated if new vulnerabilityType with mark is added */
113+
VulnerabilityType[] MARKED_VULNERABILITIES = {
114+
SQL_INJECTION,
115+
COMMAND_INJECTION,
116+
PATH_TRAVERSAL,
117+
LDAP_INJECTION,
118+
SSRF,
119+
UNVALIDATED_REDIRECT,
120+
XPATH_INJECTION,
121+
TRUST_BOUNDARY_VIOLATION,
122+
XSS,
123+
HEADER_INJECTION,
124+
REFLECTION_INJECTION,
125+
UNTRUSTED_DESERIALIZATION
126+
};
127+
112128
String name();
113129

114130
/** A bit flag to ignore tainted ranges for this vulnerability. Set to 0 if none. */

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ public static class RedactionContext {
184184
private final boolean sensitive;
185185
private boolean sensitiveRanges;
186186
@Nullable private String redactedValue;
187+
@Nullable private Set<VulnerabilityType> markedTypes;
187188

188189
public RedactionContext(final Source source) {
189190
this.source = source;
@@ -192,6 +193,7 @@ public RedactionContext(final Source source) {
192193
if (this.sensitive) {
193194
this.redactedValue = handler.redactSource(source);
194195
}
196+
this.markedTypes = null;
195197
}
196198

197199
public Source getSource() {
@@ -217,5 +219,14 @@ public void markWithSensitiveRanges() {
217219
redactedValue = SensitiveHandler.get().redactSource(source);
218220
}
219221
}
222+
223+
public void setMarkedTypes(@Nullable Set<VulnerabilityType> markedTypes) {
224+
this.markedTypes = markedTypes;
225+
}
226+
227+
@Nullable
228+
public Set<VulnerabilityType> getMarkedTypes() {
229+
return markedTypes;
230+
}
220231
}
221232
}

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Map;
2929
import java.util.NoSuchElementException;
3030
import java.util.Queue;
31+
import java.util.Set;
3132
import java.util.stream.Collectors;
3233
import javax.annotation.Nonnull;
3334
import javax.annotation.Nullable;
@@ -71,6 +72,20 @@ private String substring(final String value, final Ranged range) {
7172
return value.substring(range.getStart(), end);
7273
}
7374

75+
private static void writeSecureMarks(
76+
final JsonWriter writer, final @Nullable Set<VulnerabilityType> markedVulnerabilities)
77+
throws IOException {
78+
if (markedVulnerabilities == null || markedVulnerabilities.isEmpty()) {
79+
return;
80+
}
81+
writer.name("secure_marks");
82+
writer.beginArray();
83+
for (VulnerabilityType type : markedVulnerabilities) {
84+
writer.value(type.name());
85+
}
86+
writer.endArray();
87+
}
88+
7489
private class DefaultEvidenceAdapter extends FormattingAdapter<Evidence> {
7590

7691
@Override
@@ -128,6 +143,7 @@ private void writeValuePart(
128143
if (range != null) {
129144
writer.name("source");
130145
sourceAdapter.toJson(writer, range.getSource());
146+
writeSecureMarks(writer, range.getMarkedVulnerabilities());
131147
}
132148
writer.endObject();
133149
}
@@ -389,6 +405,8 @@ static class RedactableTaintedValuePart implements ValuePart {
389405

390406
private final List<Ranged> sensitiveRanges;
391407

408+
@Nullable private final Set<VulnerabilityType> markedTypes;
409+
392410
private RedactableTaintedValuePart(
393411
final JsonAdapter<Source> adapter,
394412
final Range range,
@@ -403,11 +421,14 @@ private RedactableTaintedValuePart(
403421
.map(it -> shift(it, -range.getStart()))
404422
.sorted(Comparator.comparing(Ranged::getStart))
405423
.collect(Collectors.toList());
424+
425+
this.markedTypes = range.getMarkedVulnerabilities();
406426
}
407427

408428
@Override
409429
public void write(final Context ctx, final JsonWriter writer) throws IOException {
410430
final RedactionContext redaction = ctx.getRedaction(source);
431+
redaction.setMarkedTypes(markedTypes);
411432
if (redaction.shouldRedact()) {
412433
for (final ValuePart part : split(redaction)) {
413434
part.write(ctx, writer);
@@ -418,6 +439,7 @@ public void write(final Context ctx, final JsonWriter writer) throws IOException
418439
writeTruncableValue(writer, value);
419440
writer.name("source");
420441
adapter.toJson(writer, source);
442+
writeSecureMarks(writer, markedTypes);
421443
writer.endObject();
422444
}
423445
}
@@ -454,9 +476,10 @@ private void addValuePart(
454476
if (start < end) {
455477
final Source source = ctx.getSource();
456478
final String chunk = value.substring(start, end);
479+
final Set<VulnerabilityType> markedTypes = ctx.getMarkedTypes();
457480
if (!redact) {
458481
// append the value
459-
valueParts.add(new TaintedValuePart(adapter, source, chunk, false));
482+
valueParts.add(new TaintedValuePart(adapter, source, chunk, false, markedTypes));
460483
} else {
461484
final int length = chunk.length();
462485
final String sourceValue = source.getValue();
@@ -470,7 +493,7 @@ private void addValuePart(
470493
// otherwise redact the string
471494
pattern = SensitiveHandler.get().redactString(chunk);
472495
}
473-
valueParts.add(new TaintedValuePart(adapter, source, pattern, true));
496+
valueParts.add(new TaintedValuePart(adapter, source, pattern, true, markedTypes));
474497
}
475498
}
476499
}
@@ -489,15 +512,19 @@ static class TaintedValuePart implements ValuePart {
489512

490513
private final boolean redacted;
491514

515+
@Nullable private final Set<VulnerabilityType> markedTypes;
516+
492517
private TaintedValuePart(
493518
final JsonAdapter<Source> adapter,
494519
final Source source,
495520
final String value,
496-
final boolean redacted) {
521+
final boolean redacted,
522+
final @Nullable Set<VulnerabilityType> markedTypes) {
497523
this.adapter = adapter;
498524
this.source = source;
499525
this.value = value;
500526
this.redacted = redacted;
527+
this.markedTypes = markedTypes;
501528
}
502529

503530
@Override
@@ -516,6 +543,7 @@ public void write(final Context ctx, final JsonWriter writer) throws IOException
516543
writer.name("value");
517544
}
518545
writeTruncableValue(writer, value);
546+
writeSecureMarks(writer, markedTypes);
519547
writer.endObject();
520548
}
521549
}

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/RangeTest.groovy

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
package com.datadog.iast.model
22

3+
import static com.datadog.iast.model.VulnerabilityType.SQL_INJECTION
4+
import static com.datadog.iast.model.VulnerabilityType.XSS
35
import datadog.trace.api.iast.SourceTypes
46
import datadog.trace.api.iast.VulnerabilityMarks
7+
import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED
8+
import static datadog.trace.api.iast.VulnerabilityMarks.SQL_INJECTION_MARK
9+
import static datadog.trace.api.iast.VulnerabilityMarks.XSS_MARK
510
import datadog.trace.test.util.DDSpecification
11+
import spock.lang.Shared
612

713
class RangeTest extends DDSpecification {
814

15+
@Shared
16+
int multipleMarks = SQL_INJECTION_MARK | XSS_MARK
17+
918
def 'shift'() {
1019
given:
1120
final source = new Source(SourceTypes.NONE, null, null)
12-
final orig = new Range(start, length, source, VulnerabilityMarks.SQL_INJECTION_MARK)
21+
final orig = new Range(start, length, source, SQL_INJECTION_MARK)
1322

1423
when:
1524
final result = orig.shift(shift)
@@ -19,7 +28,7 @@ class RangeTest extends DDSpecification {
1928
result.source == source
2029
result.start == startResult
2130
result.length == lengthResult
22-
result.marks == VulnerabilityMarks.SQL_INJECTION_MARK
31+
result.marks == SQL_INJECTION_MARK
2332
result.isValid() == valid
2433

2534
where:
@@ -43,4 +52,24 @@ class RangeTest extends DDSpecification {
4352
then:
4453
result.is(orig)
4554
}
55+
56+
57+
58+
void 'test getMarkedVulnerabilities'() {
59+
given:
60+
final source = new Source(SourceTypes.NONE, null, null)
61+
final range = new Range(0, 1, source, marks)
62+
63+
when:
64+
final vulnerabilities = range.getMarkedVulnerabilities()
65+
66+
then:
67+
vulnerabilities == expected
68+
69+
where:
70+
marks | expected
71+
NOT_MARKED | null
72+
SQL_INJECTION_MARK | [SQL_INJECTION] as Set
73+
multipleMarks | [SQL_INJECTION, XSS] as Set
74+
}
4675
}

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/VulnerabilityTypeTest.groovy

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.datadog.iast.model
22

3+
import datadog.trace.api.iast.VulnerabilityMarks
34
import datadog.trace.bootstrap.instrumentation.api.AgentSpan
45
import datadog.trace.test.util.DDSpecification
56

@@ -52,6 +53,13 @@ class VulnerabilityTypeTest extends DDSpecification {
5253
INSECURE_AUTH_PROTOCOL | getSpanAndStackLocation(123) | new Evidence("Authorization : Digest") | 871205334
5354
}
5455

56+
void 'test marked vulnerabilities are not NOT_MARKED'() {
57+
expect:
58+
for (VulnerabilityType type : VulnerabilityType.MARKED_VULNERABILITIES) {
59+
assert type.mark() != VulnerabilityMarks.NOT_MARKED
60+
}
61+
}
62+
5563
private Location getSpanAndStackLocation(final long spanId) {
5664
final span = Stub(AgentSpan)
5765
span.getSpanId() >> spanId

dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/model/json/EvidenceEncodingTest.groovy

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.datadog.iast.model.Source
66
import com.squareup.moshi.JsonAdapter
77
import com.squareup.moshi.Moshi
88
import datadog.trace.api.config.IastConfig
9+
import static datadog.trace.api.iast.VulnerabilityMarks.XSS_MARK
910
import datadog.trace.test.util.DDSpecification
1011
import org.skyscreamer.jsonassert.JSONAssert
1112
import spock.lang.Shared
@@ -64,10 +65,15 @@ class EvidenceEncodingTest extends DDSpecification {
6465
'Hello World' | [range(0, 11, source(0))] | '{"valueParts": [{"value": "Hello World", "source": 0}]}'
6566
'Hello World' | [range(5, 1, source(0))] | '{"valueParts": [{"value": "Hello"}, {"value": " ", "source": 0}, {"value": "World"}]}'
6667
'java.lang.Object@1cb991da' | [range(0, Integer.MAX_VALUE, source(0))] | '{"valueParts": [{"value": "java.lang.Object@1cb991da", "source": 0}]}'
68+
'Hello World' | [range(6, 5, source(0), XSS_MARK)] | '{"valueParts": [{"value": "Hello "}, {"value": "World", "source": 0, "secure_marks": [XSS]}]}'
6769
}
6870

6971
private static Range range(final int start, final int length, final Source source) {
70-
return new Range(start, length, source, NOT_MARKED)
72+
return range(start, length, source, NOT_MARKED)
73+
}
74+
75+
private static Range range(final int start, final int length, final Source source, final int mark) {
76+
return new Range(start, length, source, mark)
7177
}
7278

7379
private static Source source(final int index) {

0 commit comments

Comments
 (0)