Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public abstract class NamedContext {

public abstract void taintName(@Nullable String name);

public abstract void setCurrentName(@Nullable final String name);

@Nonnull
public static <E> NamedContext getOrCreate(
@Nonnull final ContextStore<E, NamedContext> store, @Nonnull final E target) {
Expand Down Expand Up @@ -47,6 +49,9 @@ public void taintValue(@Nullable final String value) {}

@Override
public void taintName(@Nullable final String name) {}

@Override
public void setCurrentName(@Nullable final String name) {}
}

private static class NamedContextImpl extends NamedContext {
Expand Down Expand Up @@ -78,6 +83,11 @@ public void taintName(@Nullable final String name) {
}
}

@Override
public void setCurrentName(@Nullable final String name) {
currentName = name;
}

private IastContext iastCtx() {
if (!fetched) {
fetched = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ dependencies {
iastTestImplementation(testFixtures(project(':dd-java-agent:agent-iast')))
iastTestCompileOnly group: 'de.thetaphi', name: 'forbiddenapis', version: '3.4'
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core')
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:jackson-core:jackson-core-2.8')
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter')
iastTestRuntimeOnly project(':dd-java-agent:instrumentation:akka-http:akka-http-10.2-iast')

Expand Down Expand Up @@ -161,6 +162,7 @@ dependencies {
latestDepIastTestImplementation group: 'com.typesafe.akka', name: 'akka-actor_2.13', version: '2.8.+'
latestDepIastTestImplementation group: 'com.typesafe.akka', name: 'akka-http-jackson_2.13', version: '[10.+,10.5.2)'
latestDepIastTestImplementation(testFixtures(project(':dd-java-agent:agent-iast')))
latestDepIastTestImplementation project(':dd-java-agent:instrumentation:jackson-core:jackson-core-2.12')

lagomTestImplementation libs.scala211
lagomTestImplementation group: 'com.typesafe.akka', name: 'akka-http_2.11', version: '10.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
1 graphql.*
1 ibm.security.*
1 io.dropwizard.*
2 io.ebean.*
2 io.ebeaninternal.*
1 io.github.lukehutch.fastclasspathscanner.*
1 io.grpc.*
1 io.leangen.geantyref.*
Expand Down
3 changes: 3 additions & 0 deletions dd-java-agent/instrumentation/jackson-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ dependencies {

testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.+'
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
muzzle {
pass {
group = 'com.fasterxml.jackson.core'
module = 'jackson-core'
versions = "[2.12.0, 2.16.0)"
}
}

apply from: "$rootDir/gradle/java.gradle"

addTestSuiteForDir('latestDepTest', 'test')

final jacksonVersion = '2.12.0'
dependencies {
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.15.+'
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.fasterxml.jackson.core.json;

import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer212Helper;

public final class JsonParser212Helper {
private JsonParser212Helper() {}

public static boolean fetchIntern(UTF8StreamJsonParser jsonParser) {
return ByteQuadsCanonicalizer212Helper.fetchIntern(jsonParser._symbols);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.fasterxml.jackson.core.sym;

public final class ByteQuadsCanonicalizer212Helper {
private ByteQuadsCanonicalizer212Helper() {}

public static boolean fetchIntern(ByteQuadsCanonicalizer symbols) {
return symbols._intern;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package datadog.trace.instrumentation.jackson_2_12.core;

import static datadog.trace.agent.tooling.bytebuddy.matcher.ClassLoaderMatchers.hasClassNamed;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.declaresMethod;
import static datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers.extendsClass;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.*;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.*;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.json.JsonParser212Helper;
import com.fasterxml.jackson.core.json.UTF8StreamJsonParser;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.iast.Propagation;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.iast.NamedContext;
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 JsonParserInstrumentation extends InstrumenterModule.Iast
implements Instrumenter.ForTypeHierarchy {

static final String TARGET_TYPE = "com.fasterxml.jackson.core.JsonParser";
static final ElementMatcher.Junction<ClassLoader> VERSION_POST_2_8_0_AND_PRE_2_12_0 =
hasClassNamed("com.fasterxml.jackson.core.StreamReadCapability")
.and(not(hasClassNamed("com.fasterxml.jackson.core.StreamWriteConstraints")));

public JsonParserInstrumentation() {
super("jackson", "jackson-2_12");
}

@Override
public void methodAdvice(MethodTransformer transformer) {
final String className = JsonParserInstrumentation.class.getName();
transformer.applyAdvice(
namedOneOf("getCurrentName", "nextFieldName")
.and(isPublic())
.and(takesNoArguments())
.and(returns(String.class)),
className + "$NameAdvice");
}

@Override
public String hierarchyMarkerType() {
return TARGET_TYPE;
}

@Override
public ElementMatcher<TypeDescription> hierarchyMatcher() {
return declaresMethod(namedOneOf("getCurrentName", "nextFieldName"))
.and(
extendsClass(named(hierarchyMarkerType()))
.and(namedNoneOf("com.fasterxml.jackson.core.base.ParserMinimalBase")));
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return VERSION_POST_2_8_0_AND_PRE_2_12_0;
}

@Override
public Map<String, String> contextStore() {
return singletonMap(TARGET_TYPE, "datadog.trace.bootstrap.instrumentation.iast.NamedContext");
}

@Override
public String[] helperClassNames() {
return new String[] {
"com.fasterxml.jackson.core.json" + ".JsonParser212Helper",
"com.fasterxml.jackson.core.sym" + ".ByteQuadsCanonicalizer212Helper",
};
}

public static class NameAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
@Propagation
public static void onExit(@Advice.This JsonParser jsonParser, @Advice.Return String result) {
if (jsonParser != null
&& result != null
&& jsonParser.getCurrentToken() == JsonToken.FIELD_NAME) {
final ContextStore<JsonParser, NamedContext> store =
InstrumentationContext.get(JsonParser.class, NamedContext.class);
final NamedContext context = NamedContext.getOrCreate(store, jsonParser);
if (jsonParser instanceof UTF8StreamJsonParser
&& JsonParser212Helper.fetchIntern((UTF8StreamJsonParser) jsonParser)) {
context.setCurrentName(result);
return;
}
context.taintName(result);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package datadog.trace.instrumentation.jackson212.core

import com.fasterxml.jackson.databind.ObjectMapper
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.iast.InstrumentationBridge
import datadog.trace.api.iast.SourceTypes
import datadog.trace.api.iast.Taintable
import datadog.trace.api.iast.propagation.PropagationModule
import groovy.json.JsonOutput

import java.nio.charset.Charset

class JsonParserInstrumentationTest extends AgentTestRunner {

private final static String JSON_STRING = '{"root":"root_value","nested":["array_0","array_1"]}'

@Override
protected void configurePreAgent() {
injectSysConfig("dd.iast.enabled", "true")
}

void 'test json parsing (tainted)'() {
given:
final source = new SourceImpl(origin: SourceTypes.REQUEST_BODY, name: 'body', value: JSON_STRING)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)

and:
final reader = new ObjectMapper().readerFor(Map)

when:
final taintedResult = reader.readValue(target) as Map

then:
JsonOutput.toJson(taintedResult) == JSON_STRING
_ * module.taintObjectIfTainted(_, _)
_ * module.findSource(_) >> source
1 * module.taintString(_, 'root', source.origin, 'root', JSON_STRING)
1 * module.taintString(_, 'nested', source.origin, 'nested', JSON_STRING)
0 * _

where:
target << [JSON_STRING]
}

void 'test json parsing (tainted but field names)'() {
given:
final source = new SourceImpl(origin: SourceTypes.REQUEST_BODY, name: 'body', value: JSON_STRING)
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)

and:
final reader = new ObjectMapper()

when:
final taintedResult = reader.readValue(target, Map)

then:
JsonOutput.toJson(taintedResult) == JSON_STRING
_ * module.taintObjectIfTainted(_, _)
_ * module.findSource(_) >> source
0 * _

where:
target << [new ByteArrayInputStream(JSON_STRING.getBytes(Charset.defaultCharset()))]
}

void 'test json parsing (not tainted)'() {
given:
final module = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(module)

and:
final reader = new ObjectMapper().readerFor(Map)

when:
final taintedResult = reader.readValue(target) as Map

then:
JsonOutput.toJson(taintedResult) == JSON_STRING
_ * module.taintObjectIfTainted(_, _)
_ * module.findSource(_) >> null
0 * _

where:
target << testSuite()
}

private static List<Object> testSuite() {
return [JSON_STRING, new ByteArrayInputStream(JSON_STRING.getBytes(Charset.defaultCharset()))]
}

private static class SourceImpl implements Taintable.Source {
byte origin
String name
String value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
muzzle {
pass {
group = 'com.fasterxml.jackson.core'
module = 'jackson-core'
versions = "[2.16.0,)"
}
}

apply from: "$rootDir/gradle/java.gradle"

addTestSuiteForDir('latestDepTest', 'test')

final jacksonVersion = '2.16.0'
dependencies {
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
compileOnly(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion)
testImplementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion)

latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.+'
latestDepTestImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.fasterxml.jackson.core.json;

import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer216Helper;

public final class JsonParser216Helper {
private JsonParser216Helper() {}

public static boolean fetchInterner(UTF8StreamJsonParser jsonParser) {
return ByteQuadsCanonicalizer216Helper.fetchInterner(jsonParser._symbols);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.fasterxml.jackson.core.sym;

public final class ByteQuadsCanonicalizer216Helper {
private ByteQuadsCanonicalizer216Helper() {}

public static boolean fetchInterner(ByteQuadsCanonicalizer symbols) {
return symbols._interner != null;
}
}
Loading