Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IAST Security Control tests for java springboot #3576

Merged
merged 5 commits into from
Jan 13, 2025
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
44 changes: 44 additions & 0 deletions docs/weblog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,50 @@ A POST request which will receive the following JSON body:

An empty GET request that will execute two database queries, one to get a username and another to do a vulnerable SELECT using the obtained username.

### POST /iast/sc/*

These group of endpoints should trigger vulnerabilities detected by IAST with untrusted data coming from certain sources although the data is validated or sanitized by a configured security control

#### POST /iast/sc/s/configured

A post request using a parameter with a value that triggers a vulnerability. The value should be sanitized by a sanitizer security control configured for this vulnerability.

#### POST /sc/s/not-configured

A post request using a parameter with a value that triggers a vulnerability. The value should be sanitized by a sanitizer security control that is not configured for this vulnerability.

#### POST /sc/s/all

A post request using a parameter with a value that triggers a vulnerability. The value should be sanitized by a sanitizer security control configured for all vulnerabilities.

#### POST /sc/iv/configured

A post request using a parameter with a value that triggers a vulnerability. The value should be validated by an input validator security control configured for this vulnerability.

#### POST /sc/iv/not-configured

A post request using a parameter with a value that triggers a vulnerability. The value should be validated by an input validator security control that is not configured for this vulnerability.

#### POST /sc/iv/all

A post request using a parameter with a value that triggers a vulnerability. The value should be validated by an input validator security control configured for all vulnerabilities.

#### POST /sc/iv/overloaded/secure

A post request using two parameters that triggers a vulnerability. The values should be validated by an input validator security control with an overloaded method configured for all vulnerabilities.

#### POST /sc/iv/overloaded/insecure

A post request using two parameters that triggers a vulnerability. The values should be validated by an input validator security control with an overloaded method configured for other method signature.

#### POST /sc/s/overloaded/secure

A post request using a parameter with a value that triggers a vulnerability. The value should be sanitized by a sanitizer security control with an overloaded method configured for all vulnerabilities.

#### POST /sc/s/overloaded/insecure

A post request using a parameter with a value that triggers a vulnerability. The value should be sanitized by a sanitizer security control with an overloaded method configured for other method signature.

### GET /make_distant_call

This endpoint accept a mandatory parameter `url`. It'll make a call to these url, and should returns a JSON response :
Expand Down
1 change: 1 addition & 0 deletions manifests/cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ tests/:
test_path_parameter.py: irrelevant (ASM is not implemented in C++)
test_sql_row.py: irrelevant (ASM is not implemented in C++)
test_uri.py: irrelevant (ASM is not implemented in C++)
test_security_controls.py: irrelevant (ASM is not implemented in C++)
rasp/:
test_cmdi.py: irrelevant (ASM is not implemented in C++)
test_lfi.py: irrelevant (ASM is not implemented in C++)
Expand Down
2 changes: 2 additions & 0 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ tests/:
TestSqlRow: missing_feature
test_uri.py:
TestURI: irrelevant
test_security_controls.py:
TestSecurityControls: missing_feature
rasp/:
test_cmdi.py:
Test_Cmdi_BodyJson: v3.7.0
Expand Down
2 changes: 2 additions & 0 deletions manifests/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ tests/:
TestSqlRow: missing_feature
test_uri.py:
TestURI: missing_feature
test_security_controls.py:
TestSecurityControls: missing_feature
rasp/:
test_cmdi.py: missing_feature
test_lfi.py: missing_feature
Expand Down
6 changes: 6 additions & 0 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,12 @@ tests/:
spring-boot-3-native: missing_feature (GraalVM. Tracing support only)
vertx3: missing_feature
vertx4: missing_feature
test_security_controls.py:
TestSecurityControls:
'*': v1.45.0
play: missing_feature
ratpack: missing_feature
spring-boot-3-native: missing_feature (GraalVM. Tracing support only)
rasp/:
test_cmdi.py:
Test_Cmdi_BodyJson:
Expand Down
2 changes: 2 additions & 0 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,8 @@ tests/:
nextjs: missing_feature
test_uri.py:
TestURI: missing_feature
test_security_controls.py:
TestSecurityControls: missing_feature
rasp/:
test_cmdi.py:
Test_Cmdi_BodyJson:
Expand Down
2 changes: 2 additions & 0 deletions manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ tests/:
TestSqlRow: missing_feature
test_uri.py:
TestURI: missing_feature
test_security_controls.py:
TestSecurityControls: missing_feature
rasp/:
test_cmdi.py: missing_feature
test_lfi.py: missing_feature
Expand Down
2 changes: 2 additions & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ tests/:
TestSqlRow: missing_feature
test_uri.py:
TestURI: missing_feature
test_security_controls.py:
TestSecurityControls: missing_feature
rasp/:
test_cmdi.py:
Test_Cmdi_BodyJson: missing_feature
Expand Down
2 changes: 2 additions & 0 deletions manifests/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ tests/:
TestSqlRow: missing_feature
test_uri.py:
TestURI: missing_feature
test_security_controls.py:
TestSecurityControls: missing_feature
rasp/:
test_cmdi.py: missing_feature
test_lfi.py: missing_feature
Expand Down
141 changes: 141 additions & 0 deletions tests/appsec/iast/test_security_controls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Unless explicitly stated otherwise all files in this repository are licensed under the the Apache License Version 2.0.
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2021 Datadog, Inc.

from utils import features, rfc, weblog, interfaces
from tests.appsec.iast.utils import BaseSinkTest, assert_iast_vulnerability


@features.iast_security_controls
@rfc("https://docs.google.com/document/d/1j1hp87-2wJnXUGADZxzLnvKJmaF_Gd6ZR1hPS3LVguQ/edit?pli=1&tab=t.0")
class TestSecurityControls:
@staticmethod
def assert_iast_is_enabled(request):
product_enabled = False
for _, _, span in interfaces.library.get_spans(request=request):
# Check if the product is enabled in meta
meta = span["meta"]
if "_dd.iast.json" in meta:
product_enabled = True
break
# Check if the product is enabled in meta_struct
meta_struct = span["meta_struct"]
if meta_struct and meta_struct.get("vulnerability"):
product_enabled = True
break
assert product_enabled, "IAST is not available"

def setup_iast_is_enabled(self):
self.check_r = weblog.post("/iast/sc/iv/not-configured", data={"param": "param"})

def setup_vulnerability_suppression_with_an_input_validator_configured_for_a_specific_vulnerability(self):
self.setup_iast_is_enabled()
self.r = weblog.post("/iast/sc/iv/configured", data={"param": "param"})

def test_vulnerability_suppression_with_an_input_validator_configured_for_a_specific_vulnerability(self):
self.assert_iast_is_enabled(self.check_r)
BaseSinkTest.assert_no_iast_event(self.r, "COMMAND_INJECTION")

def setup_no_vulnerability_suppression_with_an_input_validator_configured_for_a_different_vulnerability(self):
self.setup_iast_is_enabled()
self.r = weblog.post("/iast/sc/iv/not-configured", data={"param": "param"})

def test_no_vulnerability_suppression_with_an_input_validator_configured_for_a_different_vulnerability(self):
self.assert_iast_is_enabled(self.check_r)
assert_iast_vulnerability(
request=self.r,
vulnerability_count=1,
vulnerability_type="SQL_INJECTION",
)

def setup_vulnerability_suppression_with_an_input_validator_configured_for_all_vulnerabilities(self):
self.setup_iast_is_enabled()
self.r = weblog.post("/iast/sc/iv/all", data={"param": "param"})

def test_vulnerability_suppression_with_an_input_validator_configured_for_all_vulnerabilities(self):
self.assert_iast_is_enabled(self.check_r)
BaseSinkTest.assert_no_iast_event(self.r, "SQL_INJECTION")

def setup_vulnerability_suppression_with_an_input_validator_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.setup_iast_is_enabled()
self.r = weblog.post("iast/sc/iv/overloaded/secure", data={"user": "usr1", "password": "pass"})

def test_vulnerability_suppression_with_an_input_validator_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.assert_iast_is_enabled(self.check_r)
BaseSinkTest.assert_no_iast_event(self.r, "SQL_INJECTION")

def setup_no_vulnerability_suppression_with_an_input_validator_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.setup_iast_is_enabled()
self.r = weblog.post("iast/sc/iv/overloaded/insecure", data={"user": "usr1", "password": "pass"})

def test_no_vulnerability_suppression_with_an_input_validator_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.assert_iast_is_enabled(self.check_r)
assert_iast_vulnerability(
request=self.r,
vulnerability_count=1,
vulnerability_type="SQL_INJECTION",
)

def setup_vulnerability_suppression_with_a_sanitizer_configured_for_a_specific_vulnerability(self):
self.setup_iast_is_enabled()
self.r = weblog.post("/iast/sc/s/configured", data={"param": "param"})

def test_vulnerability_suppression_with_a_sanitizer_configured_for_a_specific_vulnerability(self):
self.assert_iast_is_enabled(self.check_r)
BaseSinkTest.assert_no_iast_event(self.r, "COMMAND_INJECTION")

def setup_no_vulnerability_suppression_with_a_sanitizer_configured_for_a_different_vulnerability(self):
self.setup_iast_is_enabled()
self.r = weblog.post("/iast/sc/s/not-configured", data={"param": "param"})

def test_no_vulnerability_suppression_with_a_sanitizer_configured_for_a_different_vulnerability(self):
self.assert_iast_is_enabled(self.check_r)
assert_iast_vulnerability(
request=self.r,
vulnerability_count=1,
vulnerability_type="SQL_INJECTION",
)

def setup_vulnerability_suppression_with_a_sanitizer_configured_for_all_vulnerabilities(self):
self.setup_iast_is_enabled()
self.r = weblog.post("/iast/sc/s/all", data={"param": "param"})

def test_vulnerability_suppression_with_a_sanitizer_configured_for_all_vulnerabilities(self):
self.assert_iast_is_enabled(self.check_r)
BaseSinkTest.assert_no_iast_event(self.r, "SQL_INJECTION")

def setup_vulnerability_suppression_with_a_sanitizer_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.setup_iast_is_enabled()
self.r = weblog.post("iast/sc/s/overloaded/secure", data={"param": "param"})

def test_vulnerability_suppression_with_a_sanitizer_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.assert_iast_is_enabled(self.check_r)
BaseSinkTest.assert_no_iast_event(self.r, "COMMAND_INJECTION")

def setup_no_vulnerability_suppression_with_a_sanitizer_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.setup_iast_is_enabled()
self.r = weblog.post("iast/sc/s/overloaded/insecure", data={"param": "param"})

def test_no_vulnerability_suppression_with_a_sanitizer_configured_for_an_overloaded_method_with_specific_signature(
self,
):
self.assert_iast_is_enabled(self.check_r)
assert_iast_vulnerability(
request=self.r,
vulnerability_count=1,
vulnerability_type="COMMAND_INJECTION",
)
30 changes: 30 additions & 0 deletions utils/_context/_scenarios/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@
from .endtoend import EndToEndScenario


# When Security Controls configuration is set, tracers must instrument all the designated methods in the
# configuration as security controls.
# RFC(https://docs.google.com/document/d/1j1hp87-2wJnXUGADZxzLnvKJmaF_Gd6ZR1hPS3LVguQ/edit?pli=1&tab=t.0)

_iast_security_controls_map = {
"cpp": "TODO",
"dotnet": "TODO",
"golang": "TODO",
"java": (
"SANITIZER:COMMAND_INJECTION:com.datadoghq.system_tests.iast.utils.SecurityControlUtil:sanitize;"
"SANITIZER:*:com.datadoghq.system_tests.iast.utils.SecurityControlUtil:sanitizeForAllVulns;"
"SANITIZER:*:com.datadoghq.system_tests.iast.utils.SecurityControlUtil:overloadedSanitize:java.lang.String;"
"INPUT_VALIDATOR:COMMAND_INJECTION:com.datadoghq.system_tests.iast.utils.SecurityControlUtil:validate;"
"INPUT_VALIDATOR:*:com.datadoghq.system_tests.iast.utils.SecurityControlUtil:validateForAllVulns;"
"INPUT_VALIDATOR:*:com.datadoghq.system_tests.iast.utils.SecurityControlUtil:"
"overloadedValidation:java.lang.Object,java.lang.String,java.lang.String:1,2"
),
"nodejs": "TODO",
"php": "TODO",
"python": "TODO",
"ruby": "TODO",
}


class DefaultScenario(EndToEndScenario):
def __init__(self, name: str):
super().__init__(
Expand All @@ -18,3 +42,9 @@ def __init__(self, name: str):
scenario_groups=[ScenarioGroup.ESSENTIALS],
doc="Default scenario, spawn tracer, the Postgres databases and agent, and run most of exisiting tests",
)

def configure(self, config):
super().configure(config)
library = self.weblog_container.image.labels["system-tests-library"]
value = _iast_security_controls_map[library]
self.weblog_container.environment["DD_IAST_SECURITY_CONTROLS_CONFIGURATION"] = value
9 changes: 9 additions & 0 deletions utils/_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -2293,5 +2293,14 @@ def agent_host_ipv6(test_object):
pytest.mark.features(feature_id=347)(test_object)
return test_object

@staticmethod
def iast_security_controls(test_object):
"""IAST: Security Controls

https://feature-parity.us1.prod.dog/#/?feature=343
"""
pytest.mark.features(feature_id=343)(test_object)
return test_object


features = _Features()
Loading
Loading