Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IAST taint tracking for DB values #8072

Merged
merged 23 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,98 +15,153 @@
import static datadog.trace.api.iast.VulnerabilityMarks.XPATH_INJECTION_MARK;
import static datadog.trace.api.iast.VulnerabilityMarks.XSS_MARK;

import datadog.trace.api.iast.SourceTypes;
import datadog.trace.api.iast.VulnerabilityTypes;
import java.io.File;
import java.util.BitSet;
import java.util.function.BiFunction;
import java.util.zip.CRC32;
import javax.annotation.Nonnull;

public interface VulnerabilityType {

VulnerabilityType WEAK_CIPHER = type(VulnerabilityTypes.WEAK_CIPHER).build();
VulnerabilityType WEAK_HASH = type(VulnerabilityTypes.WEAK_HASH).build();
BitSet DB_EXCLUDED = new BitSet(SourceTypes.SQL_TABLE);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually wrong, the parameter is the size in bits of the bitset (aka be the biggest bit we want to address)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in this PR #8212


VulnerabilityType WEAK_CIPHER =
type(VulnerabilityTypes.WEAK_CIPHER).excludedSources(DB_EXCLUDED).build();
VulnerabilityType WEAK_HASH =
type(VulnerabilityTypes.WEAK_HASH).excludedSources(DB_EXCLUDED).build();
VulnerabilityType INSECURE_COOKIE =
type(VulnerabilityTypes.INSECURE_COOKIE).hash(VulnerabilityType::evidenceHash).build();
type(VulnerabilityTypes.INSECURE_COOKIE)
.hash(VulnerabilityType::evidenceHash)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType NO_HTTPONLY_COOKIE =
type(VulnerabilityTypes.NO_HTTPONLY_COOKIE).hash(VulnerabilityType::evidenceHash).build();
type(VulnerabilityTypes.NO_HTTPONLY_COOKIE)
.hash(VulnerabilityType::evidenceHash)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType HSTS_HEADER_MISSING =
type(VulnerabilityTypes.HSTS_HEADER_MISSING).hash(VulnerabilityType::serviceHash).build();
type(VulnerabilityTypes.HSTS_HEADER_MISSING)
.hash(VulnerabilityType::serviceHash)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType XCONTENTTYPE_HEADER_MISSING =
type(VulnerabilityTypes.XCONTENTTYPE_HEADER_MISSING)
.hash(VulnerabilityType::serviceHash)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType NO_SAMESITE_COOKIE =
type(VulnerabilityTypes.NO_SAMESITE_COOKIE).hash(VulnerabilityType::evidenceHash).build();
type(VulnerabilityTypes.NO_SAMESITE_COOKIE)
.hash(VulnerabilityType::evidenceHash)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType SQL_INJECTION =
type(VulnerabilityTypes.SQL_INJECTION).mark(SQL_INJECTION_MARK).build();
VulnerabilityType COMMAND_INJECTION =
type(VulnerabilityTypes.COMMAND_INJECTION).mark(COMMAND_INJECTION_MARK).build();
type(VulnerabilityTypes.COMMAND_INJECTION)
.mark(COMMAND_INJECTION_MARK)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType PATH_TRAVERSAL =
type(VulnerabilityTypes.PATH_TRAVERSAL)
.separator(File.separatorChar)
.mark(PATH_TRAVERSAL_MARK)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType LDAP_INJECTION =
type(VulnerabilityTypes.LDAP_INJECTION).mark(LDAP_INJECTION_MARK).build();
VulnerabilityType SSRF = type(VulnerabilityTypes.SSRF).mark(SSRF_MARK).build();
type(VulnerabilityTypes.LDAP_INJECTION)
.mark(LDAP_INJECTION_MARK)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType SSRF =
type(VulnerabilityTypes.SSRF).mark(SSRF_MARK).excludedSources(DB_EXCLUDED).build();
VulnerabilityType UNVALIDATED_REDIRECT =
type(VulnerabilityTypes.UNVALIDATED_REDIRECT).mark(UNVALIDATED_REDIRECT_MARK).build();
VulnerabilityType WEAK_RANDOMNESS = type(VulnerabilityTypes.WEAK_RANDOMNESS).build();
type(VulnerabilityTypes.UNVALIDATED_REDIRECT)
.mark(UNVALIDATED_REDIRECT_MARK)
.excludedSources(DB_EXCLUDED)
.build();
VulnerabilityType WEAK_RANDOMNESS =
type(VulnerabilityTypes.WEAK_RANDOMNESS).excludedSources(DB_EXCLUDED).build();

VulnerabilityType XPATH_INJECTION =
type(VulnerabilityTypes.XPATH_INJECTION).mark(XPATH_INJECTION_MARK).build();
type(VulnerabilityTypes.XPATH_INJECTION)
.mark(XPATH_INJECTION_MARK)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType TRUST_BOUNDARY_VIOLATION =
type(VulnerabilityTypes.TRUST_BOUNDARY_VIOLATION).mark(TRUST_BOUNDARY_VIOLATION_MARK).build();
type(VulnerabilityTypes.TRUST_BOUNDARY_VIOLATION)
.mark(TRUST_BOUNDARY_VIOLATION_MARK)
.excludedSources(DB_EXCLUDED)
.build();

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

VulnerabilityType HEADER_INJECTION =
type(VulnerabilityTypes.HEADER_INJECTION).mark(HEADER_INJECTION_MARK).build();
type(VulnerabilityTypes.HEADER_INJECTION)
.mark(HEADER_INJECTION_MARK)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType STACKTRACE_LEAK = type(VulnerabilityTypes.STACKTRACE_LEAK).build();
VulnerabilityType STACKTRACE_LEAK =
type(VulnerabilityTypes.STACKTRACE_LEAK).excludedSources(DB_EXCLUDED).build();

VulnerabilityType VERB_TAMPERING = type(VulnerabilityTypes.VERB_TAMPERING).build();
VulnerabilityType VERB_TAMPERING =
type(VulnerabilityTypes.VERB_TAMPERING).excludedSources(DB_EXCLUDED).build();

VulnerabilityType ADMIN_CONSOLE_ACTIVE =
type(VulnerabilityTypes.ADMIN_CONSOLE_ACTIVE)
.deduplicable(false)
.hash(VulnerabilityType::serviceHash)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType DEFAULT_HTML_ESCAPE_INVALID =
type(VulnerabilityTypes.DEFAULT_HTML_ESCAPE_INVALID).build();
type(VulnerabilityTypes.DEFAULT_HTML_ESCAPE_INVALID).excludedSources(DB_EXCLUDED).build();

VulnerabilityType SESSION_TIMEOUT = type(VulnerabilityTypes.SESSION_TIMEOUT).build();
VulnerabilityType SESSION_TIMEOUT =
type(VulnerabilityTypes.SESSION_TIMEOUT).excludedSources(DB_EXCLUDED).build();

VulnerabilityType DIRECTORY_LISTING_LEAK =
type(VulnerabilityTypes.DIRECTORY_LISTING_LEAK).build();
VulnerabilityType INSECURE_JSP_LAYOUT = type(VulnerabilityTypes.INSECURE_JSP_LAYOUT).build();
type(VulnerabilityTypes.DIRECTORY_LISTING_LEAK).excludedSources(DB_EXCLUDED).build();
VulnerabilityType INSECURE_JSP_LAYOUT =
type(VulnerabilityTypes.INSECURE_JSP_LAYOUT).excludedSources(DB_EXCLUDED).build();

VulnerabilityType HARDCODED_SECRET = type(VulnerabilityTypes.HARDCODED_SECRET).build();
VulnerabilityType HARDCODED_SECRET =
type(VulnerabilityTypes.HARDCODED_SECRET).excludedSources(DB_EXCLUDED).build();

VulnerabilityType INSECURE_AUTH_PROTOCOL =
type(VulnerabilityTypes.INSECURE_AUTH_PROTOCOL).hash(VulnerabilityType::evidenceHash).build();
type(VulnerabilityTypes.INSECURE_AUTH_PROTOCOL)
.hash(VulnerabilityType::evidenceHash)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType REFLECTION_INJECTION =
type(VulnerabilityTypes.REFLECTION_INJECTION).mark(REFLECTION_INJECTION_MARK).build();
type(VulnerabilityTypes.REFLECTION_INJECTION)
.mark(REFLECTION_INJECTION_MARK)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType SESSION_REWRITING =
type(VulnerabilityTypes.SESSION_REWRITING)
.deduplicable(false)
.hash(VulnerabilityType::serviceHash)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType DEFAULT_APP_DEPLOYED =
type(VulnerabilityTypes.DEFAULT_APP_DEPLOYED)
.deduplicable(false)
.hash(VulnerabilityType::serviceHash)
.excludedSources(DB_EXCLUDED)
.build();

VulnerabilityType UNTRUSTED_DESERIALIZATION =
type(VulnerabilityTypes.UNTRUSTED_DESERIALIZATION)
.mark(UNTRUSTED_DESERIALIZATION_MARK)
.excludedSources(DB_EXCLUDED)
.build();

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

byte type();

BitSet excludedSources();

static Builder type(final byte type) {
return new Builder(type);
}
Expand All @@ -153,18 +210,22 @@ class VulnerabilityTypeImpl implements VulnerabilityType {

private final boolean deduplicable;

private final BitSet excludedSources;

private final BiFunction<VulnerabilityType, Vulnerability, Long> hash;

public VulnerabilityTypeImpl(
final byte type,
final char separator,
final int mark,
final boolean deduplicable,
final BitSet excludedSources,
final BiFunction<VulnerabilityType, Vulnerability, Long> hash) {
this.type = type;
this.separator = separator;
this.mark = mark;
this.deduplicable = deduplicable;
this.excludedSources = excludedSources;
this.hash = hash;
}

Expand Down Expand Up @@ -198,6 +259,11 @@ public byte type() {
return type;
}

@Override
public BitSet excludedSources() {
return excludedSources;
Mariovido marked this conversation as resolved.
Show resolved Hide resolved
}

/** Useful for troubleshooting issues when vulns are serialized without moshi */
public String getName() {
return name();
Expand All @@ -209,6 +275,7 @@ class Builder {
private char separator = ' ';
private int mark = NOT_MARKED;
private boolean deduplicable = true;
private BitSet excludedSources = new BitSet();
private BiFunction<VulnerabilityType, Vulnerability, Long> hash =
VulnerabilityType::fileAndLineHash;

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

public Builder excludedSources(final BitSet excludedSources) {
this.excludedSources = excludedSources;
return this;
}

public Builder hash(final BiFunction<VulnerabilityType, Vulnerability, Long> hash) {
this.hash = hash;
return this;
}

public VulnerabilityType build() {
return new VulnerabilityTypeImpl(type, separator, mark, deduplicable, hash);
return new VulnerabilityTypeImpl(type, separator, mark, deduplicable, excludedSources, hash);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,21 @@ protected final Evidence checkInjection(
return null;
}

// filter excluded ranges
final Range[] filteredRanges;
Mariovido marked this conversation as resolved.
Show resolved Hide resolved
if (!type.excludedSources().isEmpty()) {
filteredRanges = Ranges.excludeRangesBySource(valueRanges, type.excludedSources());
} else {
filteredRanges = valueRanges;
}

if (filteredRanges == null || filteredRanges.length == 0) {
return null;
}

final StringBuilder evidence = new StringBuilder();
final RangeBuilder ranges = new RangeBuilder();
addToEvidence(type, evidence, ranges, value, valueRanges, evidenceBuilder);
addToEvidence(type, evidence, ranges, value, filteredRanges, evidenceBuilder);

// check if finally we have an injection
if (ranges.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.datadog.iast.util.Ranged;
import com.datadog.iast.util.StringUtils;
import datadog.trace.api.iast.SourceTypes;
import java.util.BitSet;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -426,4 +427,22 @@ public static Range[] splitRanges(

return splittedRanges;
}

/**
* Remove the ranges that have the same origin as the input source.
*
* @param ranges the ranges to filter
* @param source the byte value of the source to exclude (see {@link SourceTypes})
*/
public static Range[] excludeRangesBySource(Range[] ranges, BitSet source) {
RangeBuilder newRanges = new RangeBuilder(ranges.length);

for (Range range : ranges) {
if (!source.get(range.getSource().getOrigin())) {
newRanges.add(range);
}
}

return newRanges.toArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import static com.datadog.iast.util.HttpHeader.LOCATION
import static com.datadog.iast.util.HttpHeader.REFERER
import static datadog.trace.api.iast.SourceTypes.GRPC_BODY
import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_VALUE
import static datadog.trace.api.iast.SourceTypes.REQUEST_QUERY
import static datadog.trace.api.iast.SourceTypes.SQL_TABLE
import static datadog.trace.api.iast.VulnerabilityMarks.NOT_MARKED
import static com.datadog.iast.taint.Ranges.mergeRanges
import static datadog.trace.api.iast.SourceTypes.REQUEST_HEADER_NAME
Expand Down Expand Up @@ -378,6 +380,24 @@ class RangesTest extends DDSpecification {
1 | 3 | 2 | range(8, 8) | 0 | 0 | []
}

void 'test excludeRangesBySource method'() {
when:
final result = Ranges.excludeRangesBySource(ranges as Range[], source as BitSet)

then:
final expectedArray = expected as Range[]
result == expectedArray

where:
ranges | source | expected
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(SQL_TABLE) | [range(5, 3)]
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(SQL_TABLE, REQUEST_QUERY) | [range(5, 3)]
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(REQUEST_HEADER_NAME) | [rangeWithSource(0, 5, SQL_TABLE)]
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(REQUEST_QUERY) | [rangeWithSource(0, 5, SQL_TABLE), range(5, 3)]
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | bitSetOf(REQUEST_QUERY, REQUEST_HEADER_NAME) | [rangeWithSource(0, 5, SQL_TABLE)]
[] | bitSetOf(SQL_TABLE) | []
}

Range[] rangesFromSpec(List<List<Object>> spec) {
def ranges = new Range[spec.size()]
int j = 0
Expand Down Expand Up @@ -417,4 +437,12 @@ class RangesTest extends DDSpecification {
Range rangeWithSource(final int start, final int length, final byte source, final String name = 'name', final String value = 'value') {
return new Range(start, length, new Source(source, name, value), NOT_MARKED)
}

BitSet bitSetOf(byte... values) {
BitSet bitSet = new BitSet()
for (byte value : values) {
bitSet.set(value)
}
return bitSet
}
}
Loading
Loading