Skip to content
Merged
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
4 changes: 4 additions & 0 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ tests/:
Test_HardcodedSecretsExtended: missing_feature
test_header_injection.py:
TestHeaderInjection: v2.46.0
TestHeaderInjectionExclusionAccessControlAllow: missing_feature
TestHeaderInjectionExclusionContentEncoding: missing_feature
TestHeaderInjectionExclusionPragma: missing_feature
TestHeaderInjectionExclusionTransferEncoding: missing_feature
test_hsts_missing_header.py:
Test_HstsMissingHeader: v2.44.0
test_insecure_auth_protocol.py:
Expand Down
4 changes: 4 additions & 0 deletions manifests/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ tests/:
Test_HardcodedSecretsExtended: missing_feature
test_header_injection.py:
TestHeaderInjection: missing_feature
TestHeaderInjectionExclusionAccessControlAllow: missing_feature
TestHeaderInjectionExclusionContentEncoding: missing_feature
TestHeaderInjectionExclusionPragma: missing_feature
TestHeaderInjectionExclusionTransferEncoding: missing_feature
test_hsts_missing_header.py:
Test_HstsMissingHeader: missing_feature
test_insecure_auth_protocol.py:
Expand Down
4 changes: 4 additions & 0 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ tests/:
spring-boot-undertow: v1.27.0
spring-boot-wildfly: v1.27.0
uds-spring-boot: v1.27.0
TestHeaderInjectionExclusionAccessControlAllow: missing_feature
TestHeaderInjectionExclusionContentEncoding: missing_feature
TestHeaderInjectionExclusionPragma: missing_feature
TestHeaderInjectionExclusionTransferEncoding: missing_feature
test_hsts_missing_header.py:
Test_HstsMissingHeader:
'*': v1.20.0
Expand Down
13 changes: 13 additions & 0 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ refs:
- &ref_5_23_0 '>=5.23.0 || ^4.47.0'
- &ref_5_24_0 '>=5.24.0 || ^4.48.0'
- &ref_5_25_0 '>=5.25.0 || ^4.49.0'
- &ref_5_26_0 '>=5.26.0 || ^4.50.0'

tests/:
apm_tracing_e2e/:
Expand Down Expand Up @@ -96,6 +97,18 @@ tests/:
TestHeaderInjection:
'*': *ref_4_21_0
nextjs: missing_feature
TestHeaderInjectionExclusionAccessControlAllow:
'*': *ref_5_26_0
nextjs: missing_feature
TestHeaderInjectionExclusionContentEncoding:
'*': *ref_5_26_0
nextjs: missing_feature
TestHeaderInjectionExclusionPragma:
'*': *ref_5_26_0
nextjs: missing_feature
TestHeaderInjectionExclusionTransferEncoding:
'*': *ref_5_26_0
nextjs: missing_feature
test_hsts_missing_header.py:
Test_HstsMissingHeader:
'*': *ref_4_8_0
Expand Down
4 changes: 4 additions & 0 deletions manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ tests/:
Test_HardcodedSecretsExtended: missing_feature
test_header_injection.py:
TestHeaderInjection: missing_feature
TestHeaderInjectionExclusionAccessControlAllow: missing_feature
TestHeaderInjectionExclusionContentEncoding: missing_feature
TestHeaderInjectionExclusionPragma: missing_feature
TestHeaderInjectionExclusionTransferEncoding: missing_feature
test_hsts_missing_header.py:
Test_HstsMissingHeader: missing_feature
test_insecure_auth_protocol.py:
Expand Down
4 changes: 4 additions & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ tests/:
TestHeaderInjection:
'*': v2.10.0
fastapi: missing_feature
TestHeaderInjectionExclusionAccessControlAllow: missing_feature
TestHeaderInjectionExclusionContentEncoding: missing_feature
TestHeaderInjectionExclusionPragma: missing_feature
TestHeaderInjectionExclusionTransferEncoding: missing_feature
test_hsts_missing_header.py:
Test_HstsMissingHeader: missing_feature
test_insecure_auth_protocol.py:
Expand Down
4 changes: 4 additions & 0 deletions manifests/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ tests/:
Test_HardcodedSecretsExtended: missing_feature
test_header_injection.py:
TestHeaderInjection: missing_feature
TestHeaderInjectionExclusionAccessControlAllow: missing_feature
TestHeaderInjectionExclusionContentEncoding: missing_feature
TestHeaderInjectionExclusionPragma: missing_feature
TestHeaderInjectionExclusionTransferEncoding: missing_feature
test_hsts_missing_header.py:
Test_HstsMissingHeader: missing_feature
test_insecure_auth_protocol.py:
Expand Down
80 changes: 78 additions & 2 deletions tests/appsec/iast/sink/test_header_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,48 @@
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2021 Datadog, Inc.

from utils import context, features, missing_feature
from ..utils import BaseSinkTest
from utils import context, features, missing_feature, weblog
from ..utils import BaseSinkTest, assert_iast_vulnerability


class _BaseTestHeaderInjectionReflectedExclusion:
origin_header: None
reflected_header: None
headers: None

exclusion_request: None
no_exclusion_request: None

def setup_no_exclusion(self):
assert self.origin_header is not None, f"Please set {self}.origin_header"
assert isinstance(self.origin_header, str), f"Please set {self}.origin_header"
assert self.reflected_header is not None, f"Please set {self}.reflected_header"
assert isinstance(self.reflected_header, str), f"Please set {self}.reflected_header"

self.no_exclusion_request = weblog.get(
path="/iast/header_injection/reflected/no-exclusion",
params={"origin": self.origin_header, "reflected": self.reflected_header},
)

def test_no_exclusion(self):
assert_iast_vulnerability(
request=self.no_exclusion_request, vulnerability_count=1, vulnerability_type="HEADER_INJECTION",
)

def setup_exclusion(self):
assert self.origin_header is not None, f"Please set {self}.origin_header"
assert isinstance(self.origin_header, str), f"Please set {self}.origin_header"
assert self.reflected_header is not None, f"Please set {self}.reflected_header"
assert isinstance(self.reflected_header, str), f"Please set {self}.reflected_header"

self.exclusion_request = weblog.get(
path="/iast/header_injection/reflected/exclusion",
params={"origin": self.origin_header, "reflected": self.reflected_header},
headers=self.headers,
)

def test_exclusion(self):
BaseSinkTest.assert_no_iast_event(self.exclusion_request)


@features.iast_sink_header_injection
Expand All @@ -25,3 +65,39 @@ def test_telemetry_metric_instrumented_sink(self):
@missing_feature(context.library < "java@1.22.0", reason="Metrics not implemented")
def test_telemetry_metric_executed_sink(self):
super().test_telemetry_metric_executed_sink()


@features.iast_sink_header_injection
class TestHeaderInjectionExclusionAccessControlAllow(_BaseTestHeaderInjectionReflectedExclusion):
"""Verify Header injection Access-Control-Allow-* reflexion exclusion"""

origin_header = "x-custom-header"
reflected_header = "access-control-allow-origin"
headers = {"x-custom-header": "allowed-origin"}


@features.iast_sink_header_injection
class TestHeaderInjectionExclusionContentEncoding(_BaseTestHeaderInjectionReflectedExclusion):
"""Verify Header injection Content-Encoding reflexion exclusion"""

origin_header = "accept-encoding"
reflected_header = "content-encoding"
headers = {"accept-encoding": "foo, bar"}


@features.iast_sink_header_injection
class TestHeaderInjectionExclusionPragma(_BaseTestHeaderInjectionReflectedExclusion):
"""Verify Header injection Pragma reflexion exclusion"""

origin_header = "cache-control"
reflected_header = "pragma"
headers = {"cache-control": "cacheControlValue"}


@features.iast_sink_header_injection
class TestHeaderInjectionExclusionTransferEncoding(_BaseTestHeaderInjectionReflectedExclusion):
"""Verify Header injection Transfer-Encoding reflexion exclusion"""

origin_header = "accept-encoding"
reflected_header = "transfer-encoding"
headers = {"accept-encoding": "foo, bar"}
34 changes: 34 additions & 0 deletions utils/build/docker/nodejs/express4-typescript/iast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,40 @@ function initSinkRoutes (app: Express): void {
res.send(`OK:${token}`)
})

app.get('/iast/header_injection/reflected/exclusion', ({ headers, query }: Request, res: Response): void => {
const reflectedHeaderName: string = `${query.reflected}`
const originHeaderName: string = `${query.origin}`
res.setHeader(reflectedHeaderName, `${headers[originHeaderName]}`)
res.send('OK')
})

app.get('/iast/header_injection/reflected/no-exclusion', ({ query }: Request, res: Response): void => {
// There is a reason for this: to avoid vulnerabilities deduplication,
// which caused the non-exclusion test to fail for all tests after the first one,
// since they are all in the same location (the hash is calculated based on the location).

const reflectedHeaderName: string = `${query.reflected}`
const originHeaderName: string = `${query.origin}`
switch (reflectedHeaderName) {
case 'pragma':
res.setHeader(reflectedHeaderName, originHeaderName)
break
case 'transfer-encoding':
res.setHeader(reflectedHeaderName, originHeaderName)
break
case 'content-encoding':
res.setHeader(reflectedHeaderName, originHeaderName)
break
case 'access-control-allow-origin':
res.setHeader(reflectedHeaderName, originHeaderName)
break
default:
res.setHeader(reflectedHeaderName, originHeaderName)
break
}
res.send('OK')
})

app.post('/iast/header_injection/test_insecure', (req: Request, res: Response): void => {
res.setHeader('testheader', req.body.test)
res.send('OK')
Expand Down
30 changes: 30 additions & 0 deletions utils/build/docker/nodejs/express4/iast/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,36 @@ function initRoutes (app, tracer) {
res.send(`OK:${token}`)
})

app.get('/iast/header_injection/reflected/exclusion', (req, res) => {
res.setHeader(req.query.reflected, req.headers[req.query.origin])
res.send('OK')
})

app.get('/iast/header_injection/reflected/no-exclusion', (req, res) => {
// There is a reason for this: to avoid vulnerabilities deduplication,
// which caused the non-exclusion test to fail for all tests after the first one,
// since they are all in the same location (the hash is calculated based on the location).

switch (req.query.reflected) {
case 'pragma':
res.setHeader(req.query.reflected, req.query.origin)
break
case 'transfer-encoding':
res.setHeader(req.query.reflected, req.query.origin)
break
case 'content-encoding':
res.setHeader(req.query.reflected, req.query.origin)
break
case 'access-control-allow-origin':
res.setHeader(req.query.reflected, req.query.origin)
break
default:
res.setHeader(req.query.reflected, req.query.origin)
break
}
res.send('OK')
})

app.post('/iast/header_injection/test_insecure', (req, res) => {
res.setHeader('testheader', req.body.test)
res.send('OK')
Expand Down