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

Taint gson sources (deserialization) v1.4 and v1.5 #6084

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8ea7e5d
First gson deserialization propagation version, tested for 1.1
jandro996 Oct 11, 2023
73e7f23
Refactor, current instrumentation only works for v1.1
jandro996 Oct 13, 2023
dde6d7a
Gson instrumentation for 1.4 and 1.5
jandro996 Oct 17, 2023
8c62450
Gson instrumentation from 1.6
jandro996 Oct 18, 2023
52e583d
Fix gradle
jandro996 Oct 18, 2023
c5e84cb
Update smoke test
jandro996 Oct 18, 2023
f98d8b8
fix spotless
jandro996 Oct 18, 2023
daeaeaa
fix muzzle
jandro996 Oct 18, 2023
1c24265
Merge branch 'master' into alejandro.gonzalez/Taint_gson_sources_dese…
jandro996 Oct 19, 2023
50813e8
remove unnecessary exclusions
jandro996 Oct 19, 2023
165bce8
Merge branch 'master' into alejandro.gonzalez/Taint_gson_sources_dese…
jandro996 Oct 19, 2023
4205f7c
Merge branch 'master' into alejandro.gonzalez/Taint_gson_sources_dese…
jandro996 Oct 23, 2023
c65ffa2
First gson deserialization propagation version, tested for 1.1
jandro996 Oct 11, 2023
8144934
Refactor, current instrumentation only works for v1.1
jandro996 Oct 13, 2023
83a0d41
Gson instrumentation for 1.4 and 1.5
jandro996 Oct 17, 2023
8999064
Gson instrumentation from 1.6
jandro996 Oct 18, 2023
b508a18
Introduce PII redaction based on keywords
jpbempel Oct 10, 2023
503d337
Throw exception on expression language in any case
jpbempel Oct 16, 2023
7cfac2a
remove unnecessary exclusions
jandro996 Oct 19, 2023
af5be91
Add instrumentation to constructor with InputStream and ReInit methods
jandro996 Oct 23, 2023
64644a2
remove support for version v1.1
jandro996 Oct 23, 2023
a182e4b
use new version in smoke tests
jandro996 Oct 23, 2023
7ed9ebe
remove unnecessary exclusion from v1.1
jandro996 Oct 23, 2023
368750a
make gson deserialization test more readable
jandro996 Oct 23, 2023
4804b2d
add smoke tests with StringReader
jandro996 Oct 23, 2023
7087104
Fix tests
jandro996 Oct 23, 2023
2cf8009
Gson IAST source propagation for version 1.4 and 1.5
jandro996 Oct 23, 2023
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 @@ -192,6 +192,9 @@
0 com.google.common.base.internal.Finalizer
0 com.google.common.util.concurrent.*
2 com.google.gson.*
# Need for IAST: we instrument this class
0 com.google.gson.Gson
0 com.google.gson.JsonParserJavacc
2 com.google.inject.*
# We instrument Runnable there
0 com.google.inject.internal.AbstractBindingProcessor$*
Expand Down
23 changes: 23 additions & 0 deletions dd-java-agent/instrumentation/gson-1.4/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
muzzle {
pass {
group = 'com.google.code.gson'
module = 'gson'
versions = '[1.4, 1.5]'
assertInverse = true
}
}

apply from: "$rootDir/gradle/java.gradle"
apply plugin: 'call-site-instrumentation'

addTestSuiteForDir('latestDepTest', 'test')

dependencies {
compileOnly group: 'com.google.code.gson', name: 'gson', version: '1.4'

testImplementation group: 'com.google.code.gson', name: 'gson', version: '1.4'

testRuntimeOnly project(':dd-java-agent:instrumentation:iast-instrumenter')

latestDepTestImplementation group: 'com.google.code.gson', name: 'gson', version: '1.5'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package datadog.trace.instrumentation.gson;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.google.auto.service.AutoService;
import com.google.gson.JsonPrimitive;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.muzzle.Reference;
import datadog.trace.api.iast.InstrumentationBridge;
import datadog.trace.api.iast.Propagation;
import datadog.trace.api.iast.propagation.PropagationModule;
import net.bytebuddy.asm.Advice;

@AutoService(Instrumenter.class)
public class JsonParserJavaccInstrumentation extends Instrumenter.Iast
implements Instrumenter.ForSingleType {

public JsonParserJavaccInstrumentation() {
super("gson");
}

@Override
public String instrumentedType() {
return "com.google.gson.JsonParserJavacc";
}

@Override
public Reference[] additionalMuzzleReferences() {
return new Reference[] {new Reference.Builder("com.google.gson.JsonParserJavacc").build()};
}

@Override
public void adviceTransformations(AdviceTransformation transformation) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Needs to add more advices:
(InputStream, String)
ReInit(InputStream)
ReInit(StringReader)

transformation.applyAdvice(
isConstructor().and(takesArguments(1)).and(takesArgument(0, named("java.io.Reader"))),
getClass().getName() + "$ConstructAdvice");
transformation.applyAdvice(
isMethod().and(takesArguments(0).and(named("JsonString"))),
getClass().getName() + "$ParseAdvice");
}

public static class ConstructAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
@Propagation
public static void afterInit(
@Advice.This Object self, @Advice.Argument(0) final java.io.Reader input) {
final PropagationModule iastModule = InstrumentationBridge.PROPAGATION;
if (iastModule != null && input != null) {
iastModule.taintIfInputIsTainted(self, input);
}
}
}

public static class ParseAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
@Propagation
public static void afterParse(
@Advice.This Object self, @Advice.Return final JsonPrimitive result) {
final PropagationModule iastModule = InstrumentationBridge.PROPAGATION;
if (iastModule != null && result != null) {
iastModule.taintIfInputIsTainted(result.getAsString(), self);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package datadog.trace.instrumentation.gson

import com.google.gson.Gson
import com.google.gson.JsonParser
import com.google.gson.JsonParserJavacc
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.iast.InstrumentationBridge
import datadog.trace.api.iast.propagation.PropagationModule

class JsonParserJavaccInstrumentationTest extends AgentTestRunner {

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

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

when:
new JsonParser().parse(new StringReader(json))

then:
1 * module.taintIfInputIsTainted(_ as JsonParserJavacc, _ as StringReader)
callsAfterParse * module.taintIfInputIsTainted(_ as String, _ as JsonParserJavacc)
0 * _

where:
json | callsAfterParse
'"Test"' | 1
'1' | 0
'{"name": "nameTest", "value" : "valueTest"}' | 4
'[{"name": "nameTest", "value" : "valueTest"}]' | 4
'[{"name": "nameTest", "value" : "valueTest"}, {"name": "nameTest2", "value" : "valueTest2"}]' | 8
}


static final class TestBean {

private String name

private String value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
1 com.github.benmanes.caffeine.*
1 com.github.jknack.handlebars.*
1 com.google.*
#Need for gson propagation
2 com.google.gson.Gson
1 com.googlecode.*
1 com.hazelcast.*
1 com.hdivsecurity.*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package datadog.trace.instrumentation.java.lang;

import datadog.trace.agent.tooling.csi.CallSite;
import datadog.trace.api.iast.IastCallSites;
import datadog.trace.api.iast.InstrumentationBridge;
import datadog.trace.api.iast.Propagation;
import datadog.trace.api.iast.propagation.PropagationModule;
import java.io.StringReader;
import javax.annotation.Nonnull;

@Propagation
@CallSite(spi = IastCallSites.class)
public class StringReaderCallSite {

@CallSite.After("void java.io.StringReader.<init>(java.lang.String)")
public static StringReader afterInit(
@CallSite.AllArguments @Nonnull final Object[] params,
@CallSite.Return @Nonnull final StringReader result) {
final PropagationModule propagationModule = InstrumentationBridge.PROPAGATION;
if (propagationModule != null) {
propagationModule.taintIfInputIsTainted(result, params[0]);
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package datadog.trace.instrumentation.java.io

import datadog.trace.api.iast.InstrumentationBridge
import datadog.trace.api.iast.propagation.PropagationModule
import foo.bar.TestStringReaderSuite

class StringReaderCallSiteTest extends BaseIoCallSiteTest{

void 'test StringReader.<init>'(){
given:
PropagationModule iastModule = Mock(PropagationModule)
InstrumentationBridge.registerIastModule(iastModule)
final input = 'Test input'

when:
TestStringReaderSuite.init(input)

then:
1 * iastModule.taintIfInputIsTainted(_ as StringReader, input)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package foo.bar;

import java.io.StringReader;

public class TestStringReaderSuite {

public static void init(String input) {
new StringReader(input);
}
}
1 change: 1 addition & 0 deletions dd-smoke-tests/iast-util/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ description = 'iast-smoke-tests-utils'
dependencies {
api project(':dd-smoke-tests')
compileOnly group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.18.RELEASE'
compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.10'
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package datadog.smoketest.springboot.controller;

import com.google.gson.Gson;
import com.google.gson.JsonParser;
import datadog.smoketest.springboot.TestBean;
import ddtest.client.sources.Hasher;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
Expand Down Expand Up @@ -343,6 +346,20 @@ String pathInfo(HttpServletRequest request) {
return String.format("Request.getRequestURI returns %s", pathInfo);
}

@PostMapping("/gson_deserialization")
String gson(@RequestParam("json") String json) {
Gson gson = new Gson();
TestBean testBean = gson.fromJson(json, TestBean.class);
return "Test bean -> name: " + testBean.getName() + ", value: " + testBean.getValue();
}

@PostMapping("/gson_json_parser_deserialization")
String gsonJsonParser(@RequestParam("json") String json) {
StringReader reader = new StringReader(json);
JsonParser.parseReader(reader);
return "Ok";
}

private void withProcess(final Operation<Process> op) {
Process process = null;
try {
Expand Down
1 change: 1 addition & 0 deletions dd-smoke-tests/springboot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
implementation group: 'com.auth0', name: 'java-jwt', version: '4.0.0-beta.0'
implementation group: 'com.auth0', name: 'jwks-rsa', version: '0.21.1'
implementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '9.22'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10'

implementation group: 'org.springframework', name: 'spring-web', version: '1.5.18.RELEASE'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package datadog.smoketest

import groovy.transform.CompileDynamic
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response

Expand All @@ -25,4 +26,41 @@ class IastSpringBootSmokeTest extends AbstractIastSpringBootTest {
it.ranges[0].source.origin == 'http.request.header'
}
}


void 'gson deserialization'() {

given:
final url = "http://localhost:${httpPort}/gson_deserialization"
final body = new FormBody.Builder().add('json', '{"name": "gsonTest", "value" : "valueTest"}').build()
final request = new Request.Builder().url(url).post(body).build()

when:
client.newCall(request).execute()

then:
hasTainted { tainted ->
tainted.value == 'gsonTest' &&
tainted.ranges[0].source.name == 'json' &&
tainted.ranges[0].source.origin == 'http.request.parameter'
}
}

void 'gson json parser deserialization'() {

given:
final url = "http://localhost:${httpPort}/gson_json_parser_deserialization"
final body = new FormBody.Builder().add('json', '{"name": "gsonTest", "value" : "valueTest"}').build()
final request = new Request.Builder().url(url).post(body).build()

when:
client.newCall(request).execute()

then:
hasTainted { tainted ->
tainted.value == 'gsonTest' &&
tainted.ranges[0].source.name == 'json' &&
tainted.ranges[0].source.origin == 'http.request.parameter'
}
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ include ':dd-java-agent:instrumentation:grizzly-2'
include ':dd-java-agent:instrumentation:grizzly-client-1.9'
include ':dd-java-agent:instrumentation:grizzly-http-2.3.20'
include ':dd-java-agent:instrumentation:grpc-1.5'
include ':dd-java-agent:instrumentation:gson-1.4'
include ':dd-java-agent:instrumentation:guava-10'
include ':dd-java-agent:instrumentation:hazelcast-3.6'
include ':dd-java-agent:instrumentation:hazelcast-3.9'
Expand Down
Loading