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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
import datadog.trace.api.Config;
import datadog.trace.api.Pair;
import datadog.trace.api.iast.IastContext;
import datadog.trace.api.iast.SourceTypes;
import datadog.trace.api.iast.Taintable;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.instrumentation.iastinstrumenter.IastExclusionTrie;
import datadog.trace.instrumentation.iastinstrumenter.SourceMapperImpl;
import datadog.trace.util.stacktrace.StackWalker;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
Expand All @@ -39,6 +42,8 @@
public abstract class SinkModuleBase {

private static final int MAX_EVIDENCE_LENGTH = Config.get().getIastTruncationMaxValueLength();
private static final List<VulnerabilityType> DB_INJECTION_TYPES =
Arrays.asList(VulnerabilityType.SQL_INJECTION, VulnerabilityType.XSS);

protected final OverheadController overheadController;
protected final Reporter reporter;
Expand Down Expand Up @@ -143,9 +148,21 @@ protected final Evidence checkInjection(
return null;
}

// filter out ranges that are not SQL_TABLE for types that are not DB_INJECTION_TYPES
final Range[] filteredRanges;
if (!DB_INJECTION_TYPES.contains(type)) {
filteredRanges = Ranges.excludeRangesBySource(valueRanges, SourceTypes.SQL_TABLE);
} 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 @@ -426,4 +426,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, byte source) {
RangeBuilder newRanges = new RangeBuilder(ranges.length);

for (Range range : ranges) {
if (range.getSource().getOrigin() != source) {
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,22 @@ 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)

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

where:
ranges | source | expected
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | SQL_TABLE | [range(5, 3)]
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | REQUEST_HEADER_NAME | [rangeWithSource(0, 5, SQL_TABLE)]
[rangeWithSource(0, 5, SQL_TABLE), range(5, 3)] | REQUEST_QUERY | [rangeWithSource(0, 5, SQL_TABLE), range(5, 3)]
[] | SQL_TABLE | []
Mariovido marked this conversation as resolved.
Show resolved Hide resolved
}

Range[] rangesFromSpec(List<List<Object>> spec) {
def ranges = new Range[spec.size()]
int j = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package datadog.trace.instrumentation.jdbc;

import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.implementsInterface;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.google.auto.service.AutoService;
import datadog.trace.advice.ActiveRequestContext;
import datadog.trace.advice.RequiresRequestContext;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.Config;
import datadog.trace.api.gateway.RequestContext;
import datadog.trace.api.gateway.RequestContextSlot;
import datadog.trace.api.iast.IastContext;
import datadog.trace.api.iast.InstrumentationBridge;
import datadog.trace.api.iast.Source;
import datadog.trace.api.iast.SourceTypes;
import datadog.trace.api.iast.propagation.PropagationModule;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.bootstrap.InstrumentationContext;
import java.sql.ResultSet;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumenterModule.class)
public class IastResultSetInstrumentation extends InstrumenterModule.Iast
implements Instrumenter.ForTypeHierarchy {

public IastResultSetInstrumentation() {
super("jdbc", "jdbc-resultset", "iast-resultset");
}

@Override
public String hierarchyMarkerType() {
return "java.sql.ResultSet";
}

@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return implementsInterface(named("java.sql.ResultSet"));
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(
isMethod().and(named("next").and(takesArguments(0))),
IastResultSetInstrumentation.class.getName() + "$NextAdvice");
transformer.applyAdvice(
isMethod()
.and(named("getString").or(named("getNString")))
.and(takesArguments(int.class).or(takesArguments(String.class))),
IastResultSetInstrumentation.class.getName() + "$GetParameterAdvice");
}

@Override
public Map<String, String> contextStore() {
return singletonMap("java.sql.ResultSet", Integer.class.getName());
}

public static class NextAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
@Source(SourceTypes.SQL_TABLE)
public static void onExit(@Advice.This final ResultSet resultSet) {
ContextStore<ResultSet, Integer> contextStore =
InstrumentationContext.get(ResultSet.class, Integer.class);
if (contextStore.get(resultSet) != null) {
contextStore.put(resultSet, contextStore.get(resultSet) + 1);
} else {
// first time
contextStore.put(resultSet, 1);
}
}
}

@RequiresRequestContext(RequestContextSlot.IAST)
public static class GetParameterAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
@Source(SourceTypes.SQL_TABLE)
public static void onExit(
@Advice.Return final String value,
@Advice.This final ResultSet resultSet,
@ActiveRequestContext RequestContext reqCtx) {
ContextStore<ResultSet, Integer> contextStore =
InstrumentationContext.get(ResultSet.class, Integer.class);
if (contextStore.get(resultSet) > Config.get().getIastDbRowsToTaint()) {
return;
}
if (value == null) {
return;
}
final PropagationModule module = InstrumentationBridge.PROPAGATION;
if (module == null) {
return;
}
IastContext ctx = reqCtx.getData(RequestContextSlot.IAST);
module.taintString(ctx, value, SourceTypes.SQL_TABLE);
}
}
}
Loading
Loading