Skip to content

Commit e146e2f

Browse files
authored
Add Untrusted Deserialization vulnerability (#7345)
* Added instrumentation bridge and vulnerability types + module interface for Untrusted Deserialization * Added module for UntrustedDeserialization * Added instrumentation for UntrustedDeserialization * Added tests of the module for UntrustedDeserialization + improvements in the instrumentation tests * Added smoke tests
1 parent 418d2df commit e146e2f

14 files changed

Lines changed: 193 additions & 2 deletions

File tree

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.datadog.iast.sink.SsrfModuleImpl;
2626
import com.datadog.iast.sink.StacktraceLeakModuleImpl;
2727
import com.datadog.iast.sink.TrustBoundaryViolationModuleImpl;
28+
import com.datadog.iast.sink.UntrustedDeserializationModuleImpl;
2829
import com.datadog.iast.sink.UnvalidatedRedirectModuleImpl;
2930
import com.datadog.iast.sink.WeakCipherModuleImpl;
3031
import com.datadog.iast.sink.WeakHashModuleImpl;
@@ -147,7 +148,8 @@ private static Stream<IastModule> iastModules(
147148
ApplicationModuleImpl.class,
148149
HardcodedSecretModuleImpl.class,
149150
InsecureAuthProtocolModuleImpl.class,
150-
ReflectionInjectionModuleImpl.class);
151+
ReflectionInjectionModuleImpl.class,
152+
UntrustedDeserializationModuleImpl.class);
151153
if (iast != FULLY_ENABLED) {
152154
modules = modules.filter(IastSystem::isOptOut);
153155
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import static datadog.trace.api.iast.VulnerabilityMarks.SQL_INJECTION_MARK;
1111
import static datadog.trace.api.iast.VulnerabilityMarks.SSRF_MARK;
1212
import static datadog.trace.api.iast.VulnerabilityMarks.TRUST_BOUNDARY_VIOLATION_MARK;
13+
import static datadog.trace.api.iast.VulnerabilityMarks.UNTRUSTED_DESERIALIZATION_MARK;
1314
import static datadog.trace.api.iast.VulnerabilityMarks.UNVALIDATED_REDIRECT_MARK;
1415
import static datadog.trace.api.iast.VulnerabilityMarks.XPATH_INJECTION_MARK;
1516
import static datadog.trace.api.iast.VulnerabilityMarks.XSS_MARK;
@@ -103,6 +104,11 @@ public interface VulnerabilityType {
103104
.hash(VulnerabilityType::serviceHash)
104105
.build();
105106

107+
VulnerabilityType UNTRUSTED_DESERIALIZATION =
108+
type(VulnerabilityTypes.UNTRUSTED_DESERIALIZATION)
109+
.mark(UNTRUSTED_DESERIALIZATION_MARK)
110+
.build();
111+
106112
String name();
107113

108114
/** A bit flag to ignore tainted ranges for this vulnerability. Set to 0 if none. */
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.datadog.iast.sink;
2+
3+
import com.datadog.iast.Dependencies;
4+
import com.datadog.iast.model.VulnerabilityType;
5+
import datadog.trace.api.iast.sink.UntrustedDeserializationModule;
6+
import java.io.InputStream;
7+
import javax.annotation.Nullable;
8+
9+
public class UntrustedDeserializationModuleImpl extends SinkModuleBase
10+
implements UntrustedDeserializationModule {
11+
12+
public UntrustedDeserializationModuleImpl(final Dependencies dependencies) {
13+
super(dependencies);
14+
}
15+
16+
@Override
17+
public void onInputStream(@Nullable InputStream is) {
18+
if (is == null) {
19+
return;
20+
}
21+
checkInjection(VulnerabilityType.UNTRUSTED_DESERIALIZATION, is);
22+
}
23+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.datadog.iast.sink
2+
3+
import com.datadog.iast.IastModuleImplTestBase
4+
import com.datadog.iast.Reporter
5+
import com.datadog.iast.model.Source
6+
import com.datadog.iast.model.Vulnerability
7+
import com.datadog.iast.model.VulnerabilityType
8+
import com.datadog.iast.taint.Ranges
9+
import datadog.trace.api.iast.SourceTypes
10+
import datadog.trace.api.iast.sink.UntrustedDeserializationModule
11+
12+
class UntrustedDeserializationModuleTest extends IastModuleImplTestBase {
13+
14+
private UntrustedDeserializationModule module
15+
16+
def setup() {
17+
module = new UntrustedDeserializationModuleImpl(dependencies)
18+
}
19+
20+
@Override
21+
protected Reporter buildReporter() {
22+
return Mock(Reporter)
23+
}
24+
25+
void 'test null value'() {
26+
when:
27+
module.onInputStream(null)
28+
29+
then:
30+
0 * _
31+
}
32+
33+
void 'test untrusted deserialization detection' () {
34+
setup:
35+
def inputStream = Mock(InputStream)
36+
37+
when:
38+
module.onInputStream(inputStream)
39+
40+
then: 'without tainted input stream'
41+
0 * reporter.report(_, _)
42+
43+
when:
44+
taint(inputStream)
45+
module.onInputStream(inputStream)
46+
47+
then: 'with tainted input stream'
48+
1 * reporter.report(_, { Vulnerability vul -> vul.type == VulnerabilityType.UNTRUSTED_DESERIALIZATION})
49+
}
50+
51+
private void taint(final Object value) {
52+
ctx.getTaintedObjects().taint(value, Ranges.forObject(new Source(SourceTypes.REQUEST_BODY, 'name', value.toString())))
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package datadog.trace.instrumentation.java.lang;
2+
3+
import datadog.trace.agent.tooling.csi.CallSite;
4+
import datadog.trace.api.iast.IastCallSites;
5+
import datadog.trace.api.iast.InstrumentationBridge;
6+
import datadog.trace.api.iast.Sink;
7+
import datadog.trace.api.iast.VulnerabilityTypes;
8+
import datadog.trace.api.iast.sink.UntrustedDeserializationModule;
9+
import java.io.InputStream;
10+
11+
@Sink(VulnerabilityTypes.UNTRUSTED_DESERIALIZATION)
12+
@CallSite(spi = IastCallSites.class)
13+
public class ObjectInputStreamCallSite {
14+
15+
@CallSite.Before("void java.io.ObjectInputStream.<init>(java.io.InputStream)")
16+
public static void beforeConstructorUntrusted(@CallSite.Argument(0) final InputStream is) {
17+
final UntrustedDeserializationModule module = InstrumentationBridge.UNTRUSTED_DESERIALIZATION;
18+
19+
if (module != null) {
20+
try {
21+
module.onInputStream(is);
22+
} catch (Throwable e) {
23+
module.onUnexpectedException("before constructor untrusted threw", e);
24+
}
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package datadog.trace.instrumentation.java.io
2+
3+
import datadog.trace.agent.test.AgentTestRunner
4+
import datadog.trace.api.iast.InstrumentationBridge
5+
import datadog.trace.api.iast.sink.UntrustedDeserializationModule
6+
import foo.bar.TestObjectInputStreamSuite
7+
8+
class ObjectInputStreamCallSiteTest extends AgentTestRunner {
9+
10+
@Override
11+
protected void configurePreAgent() {
12+
injectSysConfig('dd.iast.enabled', 'true')
13+
}
14+
15+
void 'test onInputStream'() {
16+
setup:
17+
final module = Mock(UntrustedDeserializationModule)
18+
InstrumentationBridge.registerIastModule(module)
19+
20+
final InputStream inputStream = new ByteArrayInputStream(new byte[0])
21+
22+
when:
23+
TestObjectInputStreamSuite.init(inputStream)
24+
25+
then:
26+
1 * module.onInputStream(_)
27+
}
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package foo.bar;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.io.ObjectInputStream;
6+
7+
public class TestObjectInputStreamSuite {
8+
9+
public static void init(final InputStream inputStream) {
10+
try {
11+
new ObjectInputStream(inputStream);
12+
} catch (IOException e) {
13+
// Irrelevant
14+
}
15+
}
16+
}

dd-smoke-tests/iast-util/src/main/java/datadog/smoketest/springboot/controller/IastWebController.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
88
import java.io.File;
99
import java.io.IOException;
10+
import java.io.ObjectInputStream;
1011
import java.io.StringReader;
1112
import java.io.UnsupportedEncodingException;
1213
import java.net.HttpCookie;
@@ -394,6 +395,12 @@ public String headerInjectionRedaction(
394395
return "Ok";
395396
}
396397

398+
@GetMapping("/untrusted_deserialization")
399+
public String untrustedDeserialization(HttpServletRequest request) throws IOException {
400+
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
401+
return "OK";
402+
}
403+
397404
private void withProcess(final Operation<Process> op) {
398405
Process process = null;
399406
try {

dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastSpringBootTest.groovy

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,5 +1008,17 @@ abstract class AbstractIastSpringBootTest extends AbstractIastServerSmokeTest {
10081008
}
10091009
}
10101010

1011+
void 'untrusted deserialization for an input stream'() {
1012+
setup:
1013+
final url = "http://localhost:${httpPort}/untrusted_deserialization"
1014+
final request = new Request.Builder().url(url).get().build()
1015+
1016+
when:
1017+
client.newCall(request).execute()
1018+
1019+
then:
1020+
hasVulnerability { vul -> vul.type == 'UNTRUSTED_DESERIALIZATION' }
1021+
}
1022+
10111023

10121024
}

internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import datadog.trace.api.iast.sink.SsrfModule;
2121
import datadog.trace.api.iast.sink.StacktraceLeakModule;
2222
import datadog.trace.api.iast.sink.TrustBoundaryViolationModule;
23+
import datadog.trace.api.iast.sink.UntrustedDeserializationModule;
2324
import datadog.trace.api.iast.sink.UnvalidatedRedirectModule;
2425
import datadog.trace.api.iast.sink.WeakCipherModule;
2526
import datadog.trace.api.iast.sink.WeakHashModule;
@@ -65,6 +66,7 @@ public abstract class InstrumentationBridge {
6566
public static HardcodedSecretModule HARDCODED_SECRET;
6667
public static InsecureAuthProtocolModule INSECURE_AUTH_PROTOCOL;
6768
public static ReflectionInjectionModule REFLECTION_INJECTION;
69+
public static UntrustedDeserializationModule UNTRUSTED_DESERIALIZATION;
6870

6971
private static final Map<Class<? extends IastModule>, Field> MODULE_MAP = buildModuleMap();
7072

0 commit comments

Comments
 (0)