Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
/dd-java-agent/instrumentation/snakeyaml-1.33/ @DataDog/asm-java
/dd-java-agent/instrumentation/velocity-1.5/ @DataDog/asm-java
/dd-java-agent/instrumentation/freemarker/ @DataDog/asm-java
/dd-java-agent/instrumentation/beanshell-2.0/ @DataDog/asm-java
/dd-java-agent/instrumentation/datadog/asm/ @DataDog/asm-java
/dd-smoke-tests/apm-tracing-disabled/ @DataDog/asm-java
/dd-smoke-tests/appsec/ @DataDog/asm-java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.datadog.iast.propagation.StringModuleImpl;
import com.datadog.iast.securitycontrol.IastSecurityControlTransformer;
import com.datadog.iast.sink.ApplicationModuleImpl;
import com.datadog.iast.sink.CodeInjectionModuleImpl;
import com.datadog.iast.sink.CommandInjectionModuleImpl;
import com.datadog.iast.sink.EmailInjectionModuleImpl;
import com.datadog.iast.sink.HardcodedSecretModuleImpl;
Expand Down Expand Up @@ -187,7 +188,8 @@ private static Stream<IastModule> iastModules(
InsecureAuthProtocolModuleImpl.class,
ReflectionInjectionModuleImpl.class,
UntrustedDeserializationModuleImpl.class,
EmailInjectionModuleImpl.class);
EmailInjectionModuleImpl.class,
CodeInjectionModuleImpl.class);
if (iast != FULLY_ENABLED) {
modules = modules.filter(IastSystem::isOptOut);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.datadog.iast.model;

import static com.datadog.iast.util.CRCUtils.update;
import static datadog.trace.api.iast.VulnerabilityMarks.CODE_INJECTION_MARK;
import static datadog.trace.api.iast.VulnerabilityMarks.COMMAND_INJECTION_MARK;
import static datadog.trace.api.iast.VulnerabilityMarks.EMAIL_HTML_INJECTION_MARK;
import static datadog.trace.api.iast.VulnerabilityMarks.HEADER_INJECTION_MARK;
Expand Down Expand Up @@ -171,6 +172,9 @@ public interface VulnerabilityType {
.excludedSources(Builder.DB_EXCLUDED)
.build();

VulnerabilityType CODE_INJECTION =
type(VulnerabilityTypes.CODE_INJECTION).mark(CODE_INJECTION_MARK).build();

/* All vulnerability types that have a mark. Should be updated if new vulnerabilityType with mark is added */
VulnerabilityType[] MARKED_VULNERABILITIES = {
SQL_INJECTION,
Expand All @@ -185,7 +189,8 @@ public interface VulnerabilityType {
HEADER_INJECTION,
REFLECTION_INJECTION,
UNTRUSTED_DESERIALIZATION,
EMAIL_HTML_INJECTION
EMAIL_HTML_INJECTION,
CODE_INJECTION
};

String name();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.datadog.iast.sink;

import static com.datadog.iast.taint.Tainteds.canBeTainted;

import com.datadog.iast.Dependencies;
import com.datadog.iast.model.VulnerabilityType;
import datadog.trace.api.iast.sink.CodeInjectionModule;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import javax.annotation.Nonnull;

public class CodeInjectionModuleImpl extends SinkModuleBase implements CodeInjectionModule {

public CodeInjectionModuleImpl(final Dependencies dependencies) {
super(dependencies);
}

@Override
public void onEval(@Nonnull Reader reader) {
// IAST propagates taint only to StringReader and InputStreamReader.
if (!(reader instanceof StringReader) && !(reader instanceof InputStreamReader)) {
return;
}
checkInjection(VulnerabilityType.CODE_INJECTION, reader);
}

@Override
public void onEval(@Nonnull String script) {
if (!canBeTainted(script)) {
return;
}
checkInjection(VulnerabilityType.CODE_INJECTION, script);
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a new .groovy test file, we should try to write them in Java/JUnit 5 (see docs/how_to_test_with_junit.md), or add the tag: override groovy enforcement label if an exception is needed

Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.datadog.iast.sink

import com.datadog.iast.IastModuleImplTestBase
import com.datadog.iast.Reporter
import com.datadog.iast.model.Range
import com.datadog.iast.model.Source
import com.datadog.iast.model.Vulnerability
import com.datadog.iast.model.VulnerabilityType
import com.datadog.iast.taint.Ranges
import datadog.trace.api.iast.SourceTypes
import datadog.trace.api.iast.VulnerabilityMarks
import datadog.trace.api.iast.sink.CodeInjectionModule

class CodeInjectionModuleTest extends IastModuleImplTestBase {

private CodeInjectionModule module

def setup() {
module = new CodeInjectionModuleImpl(dependencies)
}

@Override
protected Reporter buildReporter() {
return Mock(Reporter)
}

void 'test null or empty script is ignored'() {
when: 'cast disambiguates the onEval(String) / onEval(Reader) overloads for a null argument'
module.onEval(script as String)

then:
0 * _

where:
script | _
null | _
'' | _
}

void 'test code injection detection on string'() {
when:
module.onEval(script)

then: 'report is not called if no active span'
tracer.activeSpan() >> null
0 * reporter.report(_, _)

when:
module.onEval(script)

then: 'report is not called if the script is not tainted'
tracer.activeSpan() >> span
0 * reporter.report(_, _)

when:
taint(script)
module.onEval(script)

then: 'report is called when the script is tainted'
tracer.activeSpan() >> span
1 * reporter.report(span, { Vulnerability vul -> vul.type == VulnerabilityType.CODE_INJECTION })

where:
script = '2 + 2'
}

void 'test code injection detection on StringReader'() {
given:
final reader = new StringReader('2 + 2')

when:
module.onEval(reader)

then: 'report is not called if the reader is not tainted'
tracer.activeSpan() >> span
0 * reporter.report(_, _)

when:
taint(reader)
module.onEval(reader)

then: 'report is called when the reader is tainted'
tracer.activeSpan() >> span
1 * reporter.report(span, { Vulnerability vul -> vul.type == VulnerabilityType.CODE_INJECTION })
}

void 'test code injection detection on InputStreamReader'() {
given:
final reader = new InputStreamReader(new ByteArrayInputStream('2 + 2'.bytes))

when:
module.onEval(reader)

then: 'report is not called if the reader is not tainted'
tracer.activeSpan() >> span
0 * reporter.report(_, _)

when:
taint(reader)
module.onEval(reader)

then: 'report is called when the reader is tainted'
tracer.activeSpan() >> span
1 * reporter.report(span, { Vulnerability vul -> vul.type == VulnerabilityType.CODE_INJECTION })

cleanup:
reader.close()
}

void 'test unsupported Reader type is ignored even when tainted'() {
given: 'only StringReader and InputStreamReader are inspected (see CodeInjectionModuleImpl.onEval)'
final reader = new CharArrayReader('2 + 2'.toCharArray())
taint(reader)

when:
module.onEval(reader)

then:
0 * _

cleanup:
reader.close()
}

void 'if all ranges of the tainted script have the code injection mark it is not reported'() {
given:
final script = '2 + 2'
final Range[] ranges = [
new Range(0, 1, new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'), VulnerabilityMarks.CODE_INJECTION_MARK)
]
ctx.getTaintedObjects().taint(script, ranges)

when:
module.onEval(script)

then:
tracer.activeSpan() >> span
0 * reporter.report(_, _)
}

void 'if all ranges of the tainted reader have the code injection mark it is not reported'() {
given:
final Range[] ranges = [
new Range(0, 1, new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', 'value'), VulnerabilityMarks.CODE_INJECTION_MARK)
]
ctx.getTaintedObjects().taint(reader, ranges)

when:
module.onEval(reader)

then:
tracer.activeSpan() >> span
0 * reporter.report(_, _)

cleanup:
reader.close()

where:
reader << [
new StringReader('2 + 2'),
new InputStreamReader(new ByteArrayInputStream('2 + 2'.bytes))
]
}

private taint(final Object value) {
ctx.getTaintedObjects().taint(value, Ranges.forObject(new Source(SourceTypes.REQUEST_PARAMETER_VALUE, 'name', value.toString())))
}
}
40 changes: 40 additions & 0 deletions dd-java-agent/instrumentation/beanshell-2.0/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
muzzle {
// BeanShell ships under two coordinates over its lifetime: the original org.beanshell:bsh
// (up to 2.0b5) and the later org.apache-extras.beanshell:bsh (2.0b5, 2.0b6). Cover both.
// 2.0b4 is the supported floor.
pass {
group = 'org.beanshell'
module = 'bsh'
versions = '[2.0b4,]'
assertInverse = true
Comment thread
claponcet marked this conversation as resolved.
}
pass {
group = 'org.apache-extras.beanshell'
module = 'bsh'
versions = '[2.0b4,]'
assertInverse = true
}
}

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

addTestSuiteForDir('latestDepTest', 'test')

// latestDepTest extends test (see gradle/test-suites.gradle), which drags in the org.beanshell:bsh
// 2.0b4 floor. Drop that coordinate here so its duplicate bsh.* classes don't collide with — or
// shadow on the classpath — the org.apache-extras.beanshell:bsh artifact we actually want to validate.
configurations.latestDepTestImplementation {
exclude group: 'org.beanshell', module: 'bsh'
}

dependencies {
compileOnly group: 'org.beanshell', name: 'bsh', version: '2.0b4'

// Unit/instrumentation tests run against the supported floor (2.0b4); latestDepTest re-runs the
// same suite against the newest published version to guard against future regressions.
testImplementation group: 'org.beanshell', name: 'bsh', version: '2.0b4'

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

latestDepTestImplementation group: 'org.apache-extras.beanshell', name: 'bsh', version: '+'
Comment thread
claponcet marked this conversation as resolved.
}
Loading
Loading