Skip to content

Commit

Permalink
Added NoHttpOnlyCookie Module and refactored InstrumentationBridge
Browse files Browse the repository at this point in the history
  • Loading branch information
DDJavierSantos authored Jun 14, 2023
1 parent ce8a316 commit ec18d0e
Show file tree
Hide file tree
Showing 29 changed files with 368 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.datadog.iast.sink.CommandInjectionModuleImpl;
import com.datadog.iast.sink.InsecureCookieModuleImpl;
import com.datadog.iast.sink.LdapInjectionModuleImpl;
import com.datadog.iast.sink.NoHttpOnlyCookieModuleImpl;
import com.datadog.iast.sink.PathTraversalModuleImpl;
import com.datadog.iast.sink.SqlInjectionModuleImpl;
import com.datadog.iast.sink.SsrfModuleImpl;
Expand Down Expand Up @@ -94,6 +95,7 @@ private static Stream<IastModule> iastModules() {
new LdapInjectionModuleImpl(),
new PropagationModuleImpl(),
new InsecureCookieModuleImpl(),
new NoHttpOnlyCookieModuleImpl(),
new SsrfModuleImpl(),
new UnvalidatedRedirectModuleImpl(),
new WeakRandomnessModuleImpl());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public interface VulnerabilityType {
VulnerabilityType WEAK_CIPHER = new VulnerabilityTypeImpl(VulnerabilityTypes.WEAK_CIPHER);
VulnerabilityType WEAK_HASH = new VulnerabilityTypeImpl(VulnerabilityTypes.WEAK_HASH);
VulnerabilityType INSECURE_COOKIE = new VulnerabilityTypeImpl(VulnerabilityTypes.INSECURE_COOKIE);
VulnerabilityType NO_HTTPONLY_COOKIE =
new VulnerabilityTypeImpl(VulnerabilityTypes.NO_HTTPONLY_COOKIE);
InjectionType SQL_INJECTION = new InjectionTypeImpl(VulnerabilityTypes.SQL_INJECTION, ' ');
InjectionType COMMAND_INJECTION =
new InjectionTypeImpl(VulnerabilityTypes.COMMAND_INJECTION, ' ');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,26 @@
import com.datadog.iast.model.Evidence;
import com.datadog.iast.model.VulnerabilityType;
import com.datadog.iast.overhead.Operations;
import datadog.trace.api.Config;
import datadog.trace.api.iast.sink.InsecureCookieModule;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import java.net.HttpCookie;
import java.util.List;
import javax.annotation.Nonnull;

public class InsecureCookieModuleImpl extends SinkModuleBase implements InsecureCookieModule {

private Config config;

@Override
public void registerDependencies(@Nonnull Dependencies dependencies) {
super.registerDependencies(dependencies);
config = dependencies.getConfig();
}

@Override
public void onCookie(String cookieName, boolean secure) {
if (!secure && cookieName != null && cookieName.length() > 0) {
public void onCookie(
@Nonnull final String name,
final String value,
final boolean isSecure,
final boolean isHttpOnly,
final String sameSite) {
if (!isSecure) {
final AgentSpan span = AgentTracer.activeSpan();
if (!overheadController.consumeQuota(Operations.REPORT_VULNERABILITY, span)) {
return;
}
report(span, VulnerabilityType.INSECURE_COOKIE, new Evidence(cookieName));
}
}

@Override
public void onCookieHeader(String value) {
if (null == value) {
return;
}
try {
List<HttpCookie> cookies = HttpCookie.parse(value);
for (HttpCookie cookie : cookies) {
if (!cookie.getSecure()) {
final AgentSpan span = AgentTracer.activeSpan();
if (!overheadController.consumeQuota(Operations.REPORT_VULNERABILITY, span)) {
return;
}
report(span, VulnerabilityType.INSECURE_COOKIE, new Evidence(cookie.getName()));
return;
}
}
} catch (IllegalArgumentException e) {
return;
}
}

@Override
public void onHeader(String name, String value) {
if ("Set-Cookie".equalsIgnoreCase(name)) {
onCookieHeader(value);
report(span, VulnerabilityType.INSECURE_COOKIE, new Evidence(name));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.datadog.iast.sink;

import com.datadog.iast.model.Evidence;
import com.datadog.iast.model.VulnerabilityType;
import com.datadog.iast.overhead.Operations;
import datadog.trace.api.iast.sink.NoHttpOnlyCookieModule;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import javax.annotation.Nonnull;

public class NoHttpOnlyCookieModuleImpl extends SinkModuleBase implements NoHttpOnlyCookieModule {

@Override
public void onCookie(
@Nonnull final String name,
final String value,
final boolean isSecure,
final boolean isHttpOnly,
final String sameSite) {
if (!isHttpOnly) {
final AgentSpan span = AgentTracer.activeSpan();
if (!overheadController.consumeQuota(Operations.REPORT_VULNERABILITY, span)) {
return;
}
report(span, VulnerabilityType.NO_HTTPONLY_COOKIE, new Evidence(name));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
public class UnvalidatedRedirectModuleImpl extends SinkModuleBase
implements UnvalidatedRedirectModule {

private static final String LOCATION_HEADER = "Location";

@Override
public void onRedirect(final @Nullable String value) {
if (!canBeTainted(value)) {
Expand Down Expand Up @@ -72,8 +74,8 @@ public void onURIRedirect(@Nullable URI uri) {
}

@Override
public void onHeader(String name, String value) {
if ("Location".equalsIgnoreCase(name)) {
public void onHeader(@Nonnull final String name, final String value) {
if (value != null && LOCATION_HEADER.equalsIgnoreCase(name)) {
onRedirect(value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.datadog.iast.model.Vulnerability
import com.datadog.iast.model.VulnerabilityType
import datadog.trace.api.gateway.RequestContext
import datadog.trace.api.gateway.RequestContextSlot
import datadog.trace.api.iast.InstrumentationBridge
import datadog.trace.bootstrap.instrumentation.api.AgentSpan
import groovy.transform.CompileDynamic

Expand Down Expand Up @@ -34,12 +33,12 @@ class InsecureCookieModuleTest extends IastModuleImplTestBase {
}
}

void 'report insecure cookie with InsecureCookieModule.onCookieHeader'() {
void 'report insecure cookie with InsecureCookieModule.onCookie'() {
given:
Vulnerability savedVul

when:
module.onCookieHeader(cookieValue)
module.onCookie('user-id', '7', false, false, null)

then:
1 * tracer.activeSpan() >> span
Expand All @@ -63,7 +62,7 @@ class InsecureCookieModuleTest extends IastModuleImplTestBase {
Vulnerability savedVul

when:
module.onCookie(cookieName, false)
module.onCookie(cookieName, cookieValue, isSecure, false, null)

then:
1 * tracer.activeSpan() >> span
Expand All @@ -78,34 +77,16 @@ class InsecureCookieModuleTest extends IastModuleImplTestBase {
}

where:
cookieName | expected
"user-id" | "user-id"
}

void 'cases where nothing is reported during InsecureCookieModule.onCookie'() {

when:
module.onCookie(cookieValue, isSecure)

then:
0 * tracer.activeSpan()
0 * overheadController._
0 * reporter._

where:
cookieValue | isSecure
"user-id" | true
null | true
null | false
"" | false
"" | true
cookieName | cookieValue | isSecure | expected
"user-id" | "7" | false | "user-id"
}


void 'cases where nothing is reported during InsecureCookieModule.onCookieHeader'() {
void 'cases where nothing is not reported during InsecureCookieModuleTest.onCookie'() {
given:
final cookie = HttpCookie.parse(cookieValue).first()

when:
module.onCookieHeader(cookieValue)
module.onCookie(cookie.name, cookie.value, cookie.secure, cookie.httpOnly, null)

then:
0 * tracer.activeSpan()
Expand All @@ -114,30 +95,25 @@ class InsecureCookieModuleTest extends IastModuleImplTestBase {

where:
cookieValue | _
null | _
"user-id=7; Secure" | _
"user-id7; Secure" | _
"user-id=7;Secure" | _
"blah" | _
"" | _
}

void 'if onHeader receives a cookie header call onCookieHeader'(final String headerName, final int expected) {
setup:
final icm = Spy(InsecureCookieModuleImpl)
InstrumentationBridge.registerIastModule(icm)
void 'insecure cookie is not reported with InsecureCookieModule.onCookie'() {
given:
final cookie = new HttpCookie(cookieName, cookieValue)
cookie.secure = isSecure

when:
icm.onHeader(headerName, "value")
module.onCookie(cookie.name, cookie.value, cookie.secure, cookie.httpOnly, null)

then:
expected * icm.onCookieHeader("value")

0 * tracer.activeSpan() >> span
0 * overheadController.consumeQuota(_, _) >> true
0 * reporter.report(_, _ as Vulnerability)

where:
headerName | expected
"blah" | 0
"Set-Cookie" | 1
"set-cookie" | 1
cookieName | cookieValue | isSecure
"user-id" | "7" | true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package com.datadog.iast.sink

import com.datadog.iast.IastModuleImplTestBase
import com.datadog.iast.IastRequestContext
import com.datadog.iast.model.Vulnerability
import com.datadog.iast.model.VulnerabilityType
import datadog.trace.api.gateway.RequestContext
import datadog.trace.api.gateway.RequestContextSlot
import datadog.trace.bootstrap.instrumentation.api.AgentSpan
import groovy.transform.CompileDynamic

@CompileDynamic
class NoHttpCookieModuleTest extends IastModuleImplTestBase {

private List<Object> objectHolder

private IastRequestContext ctx

private NoHttpOnlyCookieModuleImpl module

private AgentSpan span

def setup() {
module = registerDependencies(new NoHttpOnlyCookieModuleImpl())
objectHolder = []
ctx = new IastRequestContext()
final reqCtx = Mock(RequestContext) {
getData(RequestContextSlot.IAST) >> ctx
}
span = Mock(AgentSpan) {
getSpanId() >> 123456
getRequestContext() >> reqCtx
}
}

void 'report NoHttp cookie with InsecureCookieModule.onCookie'() {
given:
Vulnerability savedVul
final cookie = HttpCookie.parse(cookieValue).first()

when:
module.onCookie(cookie.name, cookie.value, cookie.secure, cookie.httpOnly, null)

then:
1 * tracer.activeSpan() >> span
1 * overheadController.consumeQuota(_, _) >> true
1 * reporter.report(_, _ as Vulnerability) >> { savedVul = it[1] }
with(savedVul) {
type == VulnerabilityType.NO_HTTPONLY_COOKIE
location != null
with(evidence) {
value == expected
}
}

where:
cookieValue | expected
"user-id=7" | "user-id"
}

void 'report insecure cookie with NoHttpOnlyCookieModule.onCookie'() {
given:
Vulnerability savedVul
final cookie = new HttpCookie(cookieName, cookieValue)
cookie.httpOnly = isHttpOnly

when:
module.onCookie(cookie.name, cookie.value, cookie.secure, cookie.httpOnly, null)

then:
1 * tracer.activeSpan() >> span
1 * overheadController.consumeQuota(_, _) >> true
1 * reporter.report(_, _ as Vulnerability) >> { savedVul = it[1] }
with(savedVul) {
type == VulnerabilityType.NO_HTTPONLY_COOKIE
location != null
with(evidence) {
value == expected
}
}

where:
cookieName | cookieValue | isHttpOnly | expected
"user-id" | "7" | false | "user-id"
}

void 'cases where nothing is reported during NoHttpModuleCookie.onCookie'() {
given:
final cookie = HttpCookie.parse(cookieValue).first()

when:
module.onCookie(cookie.name, cookie.value, cookie.secure, cookie.httpOnly, null)

then:
0 * tracer.activeSpan()
0 * overheadController._
0 * reporter._

where:
cookieValue | _
"user-id=7; HttpOnly" | _
"user-id=7;HttpOnly" | _
}

void 'insecure no http only is not reported with NoHttpOnlyCookieModule.onCookie'() {
given:
final cookie = new HttpCookie(cookieName, cookieValue)
cookie.httpOnly = isHttpOnly

when:
module.onCookie(cookie.name, cookie.value, cookie.secure, cookie.httpOnly, null)

then:
0 * tracer.activeSpan() >> span
0 * overheadController.consumeQuota(_, _) >> true
0 * reporter.report(_, _ as Vulnerability)

where:
cookieName | cookieValue | isHttpOnly
"user-id" | "7" | true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static void onExit(
if (null != headerValue) {
String value = headerValue.toString();
if (value.length() > 0) {
InstrumentationBridge.onHeader(headerName, value);
InstrumentationBridge.RESPONSE_HEADER_MODULE.onHeader(headerName, value);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static void onExit(
if (null != headerValue) {
String value = headerValue.toString();
if (value.length() > 0) {
InstrumentationBridge.onHeader(headerName, value);
InstrumentationBridge.RESPONSE_HEADER_MODULE.onHeader(headerName, value);
}
}
}
Expand Down
Loading

0 comments on commit ec18d0e

Please sign in to comment.