diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java index 4279b72a1c7..d6d0fd088f7 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java @@ -33,6 +33,7 @@ public class IastRequestContext implements IastContext, HasMetricCollector { @Nullable private volatile String xForwardedProto; @Nullable private volatile String contentType; @Nullable private volatile String authorization; + @Nullable private volatile String referrer; /** * Use {@link IastRequestContext#IastRequestContext(TaintedObjects)} instead as we require more @@ -99,6 +100,15 @@ public void setAuthorization(final String authorization) { this.authorization = authorization; } + @Nullable + public String getReferrer() { + return referrer; + } + + public void setReferrer(final String referrer) { + this.referrer = referrer; + } + public OverheadContext getOverheadContext() { return overheadContext; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java index fe13a05212e..de07b11ca4b 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java @@ -20,6 +20,7 @@ import com.datadog.iast.sink.NoSameSiteCookieModuleImpl; import com.datadog.iast.sink.PathTraversalModuleImpl; import com.datadog.iast.sink.ReflectionInjectionModuleImpl; +import com.datadog.iast.sink.SessionRewritingModuleImpl; import com.datadog.iast.sink.SqlInjectionModuleImpl; import com.datadog.iast.sink.SsrfModuleImpl; import com.datadog.iast.sink.StacktraceLeakModuleImpl; @@ -126,7 +127,8 @@ private static Stream iastModules(final Dependencies dependencies) { new ApplicationModuleImpl(dependencies), new HardcodedSecretModuleImpl(dependencies), new InsecureAuthProtocolModuleImpl(dependencies), - new ReflectionInjectionModuleImpl(dependencies)); + new ReflectionInjectionModuleImpl(dependencies), + new SessionRewritingModuleImpl(dependencies)); } private static void registerRequestStartedCallback( diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java index 92cf9fc8be0..49aec09e4a7 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/RequestEndedHandler.java @@ -50,7 +50,8 @@ private HttpRequestEndModule[] requestEndModules() { return new HttpRequestEndModule[] { InstrumentationBridge.HSTS_MISSING_HEADER_MODULE, InstrumentationBridge.X_CONTENT_TYPE_HEADER_MODULE, - InstrumentationBridge.INSECURE_AUTH_PROTOCOL + InstrumentationBridge.INSECURE_AUTH_PROTOCOL, + InstrumentationBridge.SESSION_REWRITING }; } } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java index 2bb3e52675a..b7249ebc655 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/VulnerabilityType.java @@ -84,6 +84,9 @@ public interface VulnerabilityType { new InjectionTypeImpl( VulnerabilityTypes.REFLECTION_INJECTION, VulnerabilityMarks.REFLECTION_INJECTION_MARK); + VulnerabilityType SESSION_REWRITING = + new VulnerabilityTypeImpl(VulnerabilityTypes.SESSION_REWRITING); + String name(); /** A bit flag to ignore tainted ranges for this vulnerability. Set to 0 if none. */ diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SessionRewritingModuleImpl.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SessionRewritingModuleImpl.java new file mode 100644 index 00000000000..ef6dacb911d --- /dev/null +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/sink/SessionRewritingModuleImpl.java @@ -0,0 +1,55 @@ +package com.datadog.iast.sink; + +import com.datadog.iast.Dependencies; +import com.datadog.iast.IastRequestContext; +import com.datadog.iast.model.Evidence; +import com.datadog.iast.model.VulnerabilityType; +import com.datadog.iast.overhead.Operations; +import datadog.trace.api.gateway.IGSpanInfo; +import datadog.trace.api.iast.IastContext; +import datadog.trace.api.iast.sink.SessionRewritingModule; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import java.util.Map; +import javax.annotation.Nullable; + +public class SessionRewritingModuleImpl extends SinkModuleBase implements SessionRewritingModule { + + private static final String JSESSIONID = ";jsessionid"; + + public SessionRewritingModuleImpl(Dependencies dependencies) { + super(dependencies); + } + + @Override + public void onRequestEnd(IastContext ctx, IGSpanInfo igSpanInfo) { + if (!(ctx instanceof IastRequestContext)) { + return; + } + final IastRequestContext iastRequestContext = (IastRequestContext) ctx; + Map tags = igSpanInfo.getTags(); + String url = (String) tags.get("http.url"); + if (url == null || !url.contains(JSESSIONID)) { + return; + } + if (isIgnorableResponseCode((Integer) tags.get("http.status_code"))) { + return; + } + final AgentSpan span = AgentTracer.activeSpan(); + if (!overheadController.consumeQuota(Operations.REPORT_VULNERABILITY, span)) { + return; + } + String reason = "URL: " + url; + String referrer = iastRequestContext.getReferrer(); + if (referrer != null && !referrer.isEmpty()) { + reason = reason + " Referrer: " + referrer; + } + final Evidence result = new Evidence(reason); + report(span, VulnerabilityType.SESSION_REWRITING, result); + } + + @Override + public boolean isIgnorableResponseCode(@Nullable Integer httpStatus) { + return httpStatus != null && httpStatus >= 400; + } +} diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java index c506bca3f2f..3c1a0079255 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/util/HttpHeader.java @@ -34,6 +34,12 @@ public void addToContext(final IastRequestContext ctx, final String value) { ctx.setAuthorization(value); } }, + REFERRER("Referrer") { + @Override + public void addToContext(final IastRequestContext ctx, final String value) { + ctx.setReferrer(value); + } + }, COOKIE("Cookie"), SET_COOKIE("Set-Cookie"), SET_COOKIE2("Set-Cookie2"), diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy index cb8d1539f10..ce9971970d1 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy @@ -68,6 +68,7 @@ class IastSystemTest extends DDSpecification { 1 * iastContext.getxContentTypeOptions() >> 'nosniff' 1 * iastContext.getStrictTransportSecurity() >> 'max-age=35660' 1 * iastContext.getAuthorization() + 1 * iastContext.getReferrer() 0 * _ noExceptionThrown() } diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SessionRewritingModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SessionRewritingModuleTest.groovy new file mode 100644 index 00000000000..e5047a4ae05 --- /dev/null +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/SessionRewritingModuleTest.groovy @@ -0,0 +1,94 @@ +package com.datadog.iast.sink + +import com.datadog.iast.IastModuleImplTestBase +import com.datadog.iast.Reporter +import com.datadog.iast.RequestEndedHandler +import com.datadog.iast.model.Vulnerability +import com.datadog.iast.model.VulnerabilityType +import datadog.trace.api.gateway.Flow +import datadog.trace.api.iast.InstrumentationBridge +import datadog.trace.api.iast.sink.InsecureAuthProtocolModule +import datadog.trace.api.iast.sink.SessionRewritingModule +import datadog.trace.api.internal.TraceSegment + +class SessionRewritingModuleTest extends IastModuleImplTestBase{ + + private static final REFERRER_URL = "https://localhost:8080/insecure/login.html" + private static final JSESSIONID_URL = REFERRER_URL + ";jsessionid=1A530637289A03B07199A44E8D531427" + private static final EVIDENCE = "URL: "+ JSESSIONID_URL + private static final REFERRER_EVIDENCE = EVIDENCE + " Referrer: " + REFERRER_URL + + private SessionRewritingModule module + + def setup() { + InstrumentationBridge.clearIastModules() + module = new SessionRewritingModuleImpl(dependencies) + InstrumentationBridge.registerIastModule(module) + } + + @Override + protected Reporter buildReporter() { + return Mock(Reporter) + } + + @Override + protected TraceSegment buildTraceSegment() { + return Mock(TraceSegment) + } + + void 'check session rewriting'() { + given: + final handler = new RequestEndedHandler(dependencies) + span.getTags() >> [ + 'http.status_code': status_code, + 'http.url' : url + ] + if(referrer != null){ + ctx.referrer = referrer + } + + + when: + def flow = handler.apply(reqCtx, span) + + then: + flow.getAction() == Flow.Action.Noop.INSTANCE + flow.getResult() == null + 1 * traceSegment.setTagTop("_dd.iast.enabled", 1) + if (expected != null) { + 1 * reporter.report(_, _) >> { args -> assertEvidence(args[1] as Vulnerability, expected) } + } else { + 0 * reporter.report(_, _) + } + + where: + url | referrer | status_code | expected + JSESSIONID_URL | REFERRER_URL | 200 | REFERRER_EVIDENCE + JSESSIONID_URL | null | 200 | EVIDENCE + JSESSIONID_URL | null | 300 | EVIDENCE + JSESSIONID_URL | null | 400 | null + REFERRER_URL | REFERRER_URL | 200 | null + } + + void 'ignore if context is null'(){ + when: + module.onRequestEnd(null, null) + + then: + 0 * _ + } + + + private static void assertVulnerability(final Vulnerability vuln) { + assert vuln != null + assert vuln.getType() == VulnerabilityType.SESSION_REWRITING + assert vuln.getLocation() != null + } + + private static void assertEvidence(final Vulnerability vuln, final String expected) { + assertVulnerability(vuln) + final evidence = vuln.getEvidence() + assert evidence != null + assert evidence.value == expected + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java index cb89379ce91..3501495cfe7 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/InstrumentationBridge.java @@ -16,6 +16,7 @@ import datadog.trace.api.iast.sink.NoSameSiteCookieModule; import datadog.trace.api.iast.sink.PathTraversalModule; import datadog.trace.api.iast.sink.ReflectionInjectionModule; +import datadog.trace.api.iast.sink.SessionRewritingModule; import datadog.trace.api.iast.sink.SqlInjectionModule; import datadog.trace.api.iast.sink.SsrfModule; import datadog.trace.api.iast.sink.StacktraceLeakModule; @@ -65,6 +66,7 @@ public abstract class InstrumentationBridge { public static HardcodedSecretModule HARDCODED_SECRET; public static InsecureAuthProtocolModule INSECURE_AUTH_PROTOCOL; public static ReflectionInjectionModule REFLECTION_INJECTION; + public static SessionRewritingModule SESSION_REWRITING; private static final Map, Field> MODULE_MAP = buildModuleMap(); diff --git a/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java b/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java index b497dea01ea..1b8feaf1986 100644 --- a/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java +++ b/internal-api/src/main/java/datadog/trace/api/iast/VulnerabilityTypes.java @@ -34,6 +34,7 @@ private VulnerabilityTypes() {} public static final byte HARDCODED_SECRET = 25; public static final byte INSECURE_AUTH_PROTOCOL = 26; public static final byte REFLECTION_INJECTION = 27; + public static final byte SESSION_REWRITING = 28; /** * Use for telemetry only, this is a special vulnerability type that is not reported, reported @@ -108,7 +109,8 @@ private VulnerabilityTypes() {} "ADMIN_CONSOLE_ACTIVE", "HARDCODED_SECRET", "INSECURE_AUTH_PROTOCOL", - "REFLECTION_INJECTION" + "REFLECTION_INJECTION", + "SESSION_REWRITING" }; public static String toString(final byte vulnerability) { diff --git a/internal-api/src/main/java/datadog/trace/api/iast/sink/SessionRewritingModule.java b/internal-api/src/main/java/datadog/trace/api/iast/sink/SessionRewritingModule.java new file mode 100644 index 00000000000..4a1dee399e4 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/iast/sink/SessionRewritingModule.java @@ -0,0 +1,3 @@ +package datadog.trace.api.iast.sink; + +public interface SessionRewritingModule extends HttpRequestEndModule {} diff --git a/internal-api/src/test/groovy/datadog/trace/api/iast/VulnerabilityTypesTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/iast/VulnerabilityTypesTest.groovy index 0ffcada012c..f7d37f4ec7f 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/iast/VulnerabilityTypesTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/iast/VulnerabilityTypesTest.groovy @@ -42,5 +42,6 @@ class VulnerabilityTypesTest extends DDSpecification { VulnerabilityTypes.HARDCODED_SECRET | 'HARDCODED_SECRET' VulnerabilityTypes.INSECURE_AUTH_PROTOCOL | 'INSECURE_AUTH_PROTOCOL' VulnerabilityTypes.REFLECTION_INJECTION | 'REFLECTION_INJECTION' + VulnerabilityTypes.SESSION_REWRITING | 'SESSION_REWRITING' } }