diff --git a/manifests/cpp.yml b/manifests/cpp.yml index 10d7f95601..d2a2e98602 100644 --- a/manifests/cpp.yml +++ b/manifests/cpp.yml @@ -54,8 +54,8 @@ tests/: test_path_parameter.py: irrelevant (ASM is not implemented in C++) test_uri.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++) - test_libddwaf.py: irrelevant (ASM is not implemented in C++) test_shi.py: irrelevant (ASM is not implemented in C++) test_sqli.py: irrelevant (ASM is not implemented in C++) test_ssrf.py: irrelevant (ASM is not implemented in C++) diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index ebedcc09d8..e6dfdc437a 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -148,6 +148,19 @@ tests/: test_uri.py: TestURI: irrelevant rasp/: + test_cmdi.py: + Test_Cmdi_BodyJson: missing_feature + Test_Cmdi_BodyUrlEncoded: missing_feature + Test_Cmdi_BodyXml: missing_feature + Test_Cmdi_Capability: missing_feature + Test_Cmdi_Mandatory_SpanTags: missing_feature + Test_Cmdi_Optional_SpanTags: missing_feature + Test_Cmdi_Rules_Version: missing_feature + Test_Cmdi_StackTrace: missing_feature + Test_Cmdi_Telemetry: missing_feature + Test_Cmdi_Telemetry_Variant_Tag: missing_feature + Test_Cmdi_UrlQuery: missing_feature + Test_Cmdi_Waf_Version: missing_feature test_lfi.py: Test_Lfi_BodyJson: v2.51.0 Test_Lfi_BodyUrlEncoded: v2.51.0 @@ -160,8 +173,7 @@ tests/: Test_Lfi_StackTrace: v2.51.0 Test_Lfi_Telemetry: v2.51.0 Test_Lfi_UrlQuery: v2.51.0 - test_libddwaf.py: - Test_Libddwaf_Version: v3.4.1 + Test_Lfi_Waf_Version: v3.4.1 test_shi.py: Test_Shi_BodyJson: v3.2.0 Test_Shi_BodyUrlEncoded: v3.2.0 @@ -172,7 +184,9 @@ tests/: Test_Shi_Rules_Version: v3.5.0 Test_Shi_StackTrace: v3.2.0 Test_Shi_Telemetry: v3.3.0 + Test_Shi_Telemetry_Variant_Tag: missing_feature Test_Shi_UrlQuery: v3.2.0 + Test_Shi_Waf_Version: v3.4.1 test_sqli.py: Test_Sqli_BodyJson: v2.54.0 Test_Sqli_BodyUrlEncoded: v2.54.0 @@ -184,6 +198,7 @@ tests/: Test_Sqli_StackTrace: v2.54.0 Test_Sqli_Telemetry: v2.54.0 Test_Sqli_UrlQuery: v2.54.0 + Test_Sqli_Waf_Version: v3.4.1 test_ssrf.py: Test_Ssrf_BodyJson: v2.51.0 Test_Ssrf_BodyUrlEncoded: v2.51.0 @@ -195,6 +210,7 @@ tests/: Test_Ssrf_StackTrace: v2.51.0 Test_Ssrf_Telemetry: v2.51.0 Test_Ssrf_UrlQuery: v2.51.0 + Test_Ssrf_Waf_Version: v3.4.1 waf/: test_addresses.py: Test_BodyJson: v2.8.0 diff --git a/manifests/golang.yml b/manifests/golang.yml index 5d3a554ccf..7e40c11bc0 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -156,8 +156,8 @@ tests/: test_uri.py: TestURI: missing_feature rasp/: + test_cmdi.py: missing_feature test_lfi.py: missing_feature - test_libddwaf.py: missing_feature test_shi.py: irrelevant (there is no equivalent to system(3) in go) test_sqli.py: Test_Sqli_BodyJson: v1.66.0 @@ -170,6 +170,7 @@ tests/: Test_Sqli_StackTrace: v1.66.0 Test_Sqli_Telemetry: missing_feature Test_Sqli_UrlQuery: v1.66.0 + Test_Sqli_Waf_Version: missing_feature test_ssrf.py: Test_Ssrf_BodyJson: v1.65.1 Test_Ssrf_BodyUrlEncoded: v1.65.1 @@ -181,6 +182,7 @@ tests/: Test_Ssrf_StackTrace: v1.65.1 Test_Ssrf_Telemetry: missing_feature Test_Ssrf_UrlQuery: v1.65.1 + Test_Ssrf_Waf_Version: missing_feature waf/: test_addresses.py: Test_BodyJson: v1.37.0 diff --git a/manifests/java.yml b/manifests/java.yml index dba8a769cf..a3c924f419 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -563,6 +563,7 @@ tests/: vertx3: missing_feature vertx4: missing_feature rasp/: + test_cmdi.py: missing_feature test_lfi.py: Test_Lfi_BodyJson: '*': v1.40.0 @@ -605,8 +606,7 @@ tests/: '*': v1.40.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) spring-boot-payara: missing_feature (APPSEC-54966) - test_libddwaf.py: - Test_Libddwaf_Version: + Test_Lfi_Waf_Version: '*': v1.40.0 spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_shi.py: irrelevant (Not support in Java) @@ -664,6 +664,9 @@ tests/: spring-boot-payara: missing_feature (APPSEC-54966) vertx3: v1.40.0 # issue in context propagation in 1.39.0 vertx4: v1.40.0 # issue in context propagation in 1.39.0 + Test_Sqli_Waf_Version: + '*': v1.40.0 + spring-boot-3-native: missing_feature (GraalVM. Tracing support only) test_ssrf.py: Test_Ssrf_BodyJson: '*': v1.39.0 @@ -716,6 +719,9 @@ tests/: spring-boot-payara: missing_feature (APPSEC-54966) vertx3: missing_feature (APPSEC-55781) vertx4: missing_feature (APPSEC-55781) + Test_Ssrf_Waf_Version: + '*': v1.40.0 + spring-boot-3-native: missing_feature (GraalVM. Tracing support only) waf/: test_addresses.py: Test_BodyJson: diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 377d69d9d9..4fe9cc00f4 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -273,6 +273,7 @@ tests/: test_uri.py: TestURI: missing_feature rasp/: + test_cmdi.py: missing_feature test_lfi.py: Test_Lfi_BodyJson: '*': *ref_5_24_0 @@ -297,8 +298,7 @@ tests/: Test_Lfi_UrlQuery: '*': *ref_5_24_0 nextjs: missing_feature - test_libddwaf.py: - Test_Libddwaf_Version: *ref_5_25_0 + Test_Lfi_Waf_Version: *ref_5_25_0 test_shi.py: Test_Shi_BodyJson: '*': *ref_5_25_0 @@ -317,9 +317,11 @@ tests/: Test_Shi_Telemetry: '*': *ref_5_25_0 nextjs: missing_feature + Test_Shi_Telemetry_Variant_Tag: missing_feature Test_Shi_UrlQuery: '*': *ref_5_25_0 nextjs: missing_feature + Test_Shi_Waf_Version: *ref_5_25_0 test_sqli.py: Test_Sqli_BodyJson: '*': *ref_5_23_0 @@ -341,6 +343,7 @@ tests/: Test_Sqli_UrlQuery: '*': *ref_5_23_0 nextjs: missing_feature + Test_Sqli_Waf_Version: *ref_5_25_0 test_ssrf.py: Test_Ssrf_BodyJson: '*': *ref_5_20_0 @@ -362,6 +365,7 @@ tests/: Test_Ssrf_UrlQuery: '*': *ref_5_20_0 nextjs: missing_feature + Test_Ssrf_Waf_Version: *ref_5_25_0 waf/: test_addresses.py: Test_BodyJson: diff --git a/manifests/php.yml b/manifests/php.yml index 356224a0e8..0d1b27737c 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -147,8 +147,8 @@ tests/: test_uri.py: TestURI: missing_feature rasp/: + test_cmdi.py: missing_feature test_lfi.py: missing_feature - test_libddwaf.py: missing_feature test_shi.py: missing_feature test_sqli.py: missing_feature test_ssrf.py: missing_feature diff --git a/manifests/python.yml b/manifests/python.yml index 8bba17a4c8..be7da4ea74 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -236,6 +236,7 @@ tests/: test_uri.py: TestURI: missing_feature rasp/: + test_cmdi.py: missing_feature test_lfi.py: Test_Lfi_BodyJson: v2.10.0 Test_Lfi_BodyUrlEncoded: v2.10.0 @@ -248,8 +249,7 @@ tests/: Test_Lfi_StackTrace: v2.10.0 Test_Lfi_Telemetry: v2.10.0 Test_Lfi_UrlQuery: v2.10.0 - test_libddwaf.py: - Test_Libddwaf_Version: v2.15.0 + Test_Lfi_Waf_Version: v2.15.0 test_shi.py: Test_Shi_BodyJson: v2.11.0-rc2 Test_Shi_BodyUrlEncoded: v2.11.0-rc2 @@ -260,7 +260,9 @@ tests/: Test_Shi_Rules_Version: v2.15.0 Test_Shi_StackTrace: v2.11.0-rc2 Test_Shi_Telemetry: v2.11.0-rc2 + Test_Shi_Telemetry_Variant_Tag: missing_feature Test_Shi_UrlQuery: v2.11.0-rc2 + Test_Shi_Waf_Version: v2.15.0 test_sqli.py: Test_Sqli_BodyJson: v2.10.0 Test_Sqli_BodyUrlEncoded: v2.10.0 @@ -272,6 +274,7 @@ tests/: Test_Sqli_StackTrace: v2.10.0 Test_Sqli_Telemetry: v2.10.0 Test_Sqli_UrlQuery: v2.10.0 + Test_Sqli_Waf_Version: v2.15.0 test_ssrf.py: Test_Ssrf_BodyJson: v2.10.0 Test_Ssrf_BodyUrlEncoded: v2.10.0 @@ -283,6 +286,7 @@ tests/: Test_Ssrf_StackTrace: v2.10.0 Test_Ssrf_Telemetry: v2.10.0 Test_Ssrf_UrlQuery: v2.10.0 + Test_Ssrf_Waf_Version: v2.15.0 waf/: test_addresses.py: Test_BodyJson: diff --git a/manifests/ruby.yml b/manifests/ruby.yml index ea2e17afc8..244c2a6f63 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -148,8 +148,8 @@ tests/: test_uri.py: TestURI: missing_feature rasp/: + test_cmdi.py: missing_feature test_lfi.py: missing_feature - test_libddwaf.py: missing_feature test_shi.py: missing_feature test_sqli.py: missing_feature test_ssrf.py: missing_feature diff --git a/tests/appsec/rasp/rasp_ruleset.json b/tests/appsec/rasp/rasp_ruleset.json index 164ed87d97..658edea7fb 100644 --- a/tests/appsec/rasp/rasp_ruleset.json +++ b/tests/appsec/rasp/rasp_ruleset.json @@ -228,6 +228,55 @@ "on_match": [ "stack_trace", "block" ] - } + }, + { + "id": "rasp-932-110", + "name": "OS command injection exploit", + "enabled": true, + "tags": { + "type": "command_injection", + "category": "vulnerability_trigger", + "cwe": "77", + "capec": "1000/152/248/88", + "confidence": "0", + "module": "rasp" + }, + "conditions": [ + { + "parameters": { + "resource": [ + { + "address": "server.sys.exec.cmd" + } + ], + "params": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ] + }, + "operator": "cmdi_detector" + } + ], + "transformers": [], + "on_match": [ + "stack_trace", "block" + ] + } ] } diff --git a/tests/appsec/rasp/test_cmdi.py b/tests/appsec/rasp/test_cmdi.py new file mode 100644 index 0000000000..e859d33f3a --- /dev/null +++ b/tests/appsec/rasp/test_cmdi.py @@ -0,0 +1,223 @@ +# 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, weblog, interfaces, scenarios, rfc +from utils.dd_constants import Capabilities +from tests.appsec.rasp.utils import ( + validate_span_tags, + validate_stack_traces, + find_series, + validate_metric_variant, + Base_Rules_Version, + Base_WAF_Version, +) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_UrlQuery: + """Shell Injection through query parameters""" + + def setup_cmdi_get(self): + self.r = weblog.get("/rasp/cmdi", params={"command": "/bin/evilCommand"}) + + def test_cmdi_get(self): + assert self.r.status_code == 403 + + interfaces.library.assert_rasp_attack( + self.r, + "rasp-932-110", + { + "resource": {"address": "server.sys.exec.cmd", "value": "/bin/evilCommand",}, + "params": {"address": "server.request.query", "value": "/bin/evilCommand",}, + }, + ) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_BodyUrlEncoded: + """Shell Injection through a url-encoded body parameter""" + + def setup_cmdi_post_urlencoded(self): + self.r = weblog.post("/rasp/cmdi", data={"command": "/bin/evilCommand"}) + + def test_cmdi_post_urlencoded(self): + assert self.r.status_code == 403 + + interfaces.library.assert_rasp_attack( + self.r, + "rasp-932-110", + { + "resource": {"address": "server.sys.exec.cmd", "value": "/bin/evilCommand",}, + "params": {"address": "server.request.body", "value": "/bin/evilCommand",}, + }, + ) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_BodyXml: + """Shell Injection through an xml body parameter""" + + def setup_cmdi_post_xml(self): + data = "/bin/evilCommand" + self.r = weblog.post("/rasp/cmdi", data=data, headers={"Content-Type": "application/xml"}) + + def test_cmdi_post_xml(self): + assert self.r.status_code == 403 + + interfaces.library.assert_rasp_attack( + self.r, + "rasp-932-110", + { + "resource": {"address": "server.sys.exec.cmd", "value": "/bin/evilCommand",}, + "params": {"address": "server.request.body", "value": "/bin/evilCommand",}, + }, + ) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_BodyJson: + """Shell Injection through a json body parameter""" + + def setup_cmdi_post_json(self): + """AppSec detects attacks in JSON body values""" + self.r = weblog.post("/rasp/cmdi", json={"command": "/bin/evilCommand"}) + + def test_cmdi_post_json(self): + assert self.r.status_code == 403 + + interfaces.library.assert_rasp_attack( + self.r, + "rasp-932-110", + { + "resource": {"address": "server.sys.exec.cmd", "value": "/bin/evilCommand",}, + "params": {"address": "server.request.body", "value": "/bin/evilCommand",}, + }, + ) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_span_tags +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_Mandatory_SpanTags: + """Validate span tag generation on exploit attempts""" + + def setup_cmdi_span_tags(self): + self.r = weblog.get("/rasp/cmdi", params={"command": "/bin/evilCommand"}) + + def test_cmdi_span_tags(self): + validate_span_tags(self.r, expected_metrics=["_dd.appsec.rasp.duration"]) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_span_tags +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_Optional_SpanTags: + """Validate span tag generation on exploit attempts""" + + def setup_cmdi_span_tags(self): + self.r = weblog.get("/rasp/cmdi", params={"command": "/bin/evilCommand"}) + + def test_cmdi_span_tags(self): + validate_span_tags( + self.r, expected_metrics=["_dd.appsec.rasp.duration_ext", "_dd.appsec.rasp.rule.eval",], + ) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_stack_trace +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_StackTrace: + """Validate stack trace generation on exploit attempts""" + + def setup_cmdi_stack_trace(self): + self.r = weblog.get("/rasp/cmdi", params={"command": "/bin/evilCommand"}) + + def test_cmdi_stack_trace(self): + assert self.r.status_code == 403 + validate_stack_traces(self.r) + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_command_injection +@scenarios.appsec_rasp +class Test_Cmdi_Telemetry: + """Validate Telemetry data on exploit attempts""" + + def setup_cmdi_telemetry(self): + self.r = weblog.get("/rasp/cmdi", params={"command": "/bin/evilCommand"}) + + def test_cmdi_telemetry(self): + assert self.r.status_code == 403 + + series_eval = find_series(True, "appsec", "rasp.rule.eval") + assert series_eval + assert any(validate_metric_variant("rasp.rule.eval", "command_injection", "exec", s) for s in series_eval), [ + s.get("tags") for s in series_eval + ] + + series_match = find_series(True, "appsec", "rasp.rule.match") + assert series_match + assert any(validate_metric_variant("rasp.rule.match", "command_injection", "exec", s) for s in series_match), [ + s.get("tags") for s in series_match + ] + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_shell_injection +@scenarios.appsec_rasp +class Test_Cmdi_Telemetry_Variant_Tag: + """Validate Telemetry data variant tag on exploit attempts""" + + def setup_cmdi_telemetry(self): + self.r = weblog.get("/rasp/cmdi", params={"command": "/bin/evilCommand"}) + + def test_cmdi_telemetry(self): + assert self.r.status_code == 403 + + series_eval = find_series(True, "appsec", "rasp.rule.eval") + assert series_eval + assert any(validate_metric_variant("rasp.rule.eval", "command_injection", "exec", s) for s in series_eval), [ + s.get("tags") for s in series_eval + ] + + series_match = find_series(True, "appsec", "rasp.rule.match") + assert series_match + assert any(validate_metric_variant("rasp.rule.match", "command_injection", "shell", s) for s in series_match), [ + s.get("tags") for s in series_match + ] + + +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_command_injection +@scenarios.remote_config_mocked_backend_asm_dd +class Test_Cmdi_Capability: + """Validate that ASM_RASP_CMDI (37) capability is sent""" + + def test_cmdi_capability(self): + interfaces.library.assert_rc_capability(Capabilities.ASM_RASP_CMDI) + + +@features.rasp_command_injection +class Test_Cmdi_Rules_Version(Base_Rules_Version): + """Test cmdi min rules version""" + + min_version = "1.13.3" + + +@features.rasp_local_file_inclusion +class Test_Cmdi_Waf_Version(Base_WAF_Version): + """Test cmdi WAF version""" + + min_version = "1.21.0" diff --git a/tests/appsec/rasp/test_lfi.py b/tests/appsec/rasp/test_lfi.py index 33eb7eb34f..3388c9a7e8 100644 --- a/tests/appsec/rasp/test_lfi.py +++ b/tests/appsec/rasp/test_lfi.py @@ -12,6 +12,7 @@ validate_metric, RC_CONSTANTS, Base_Rules_Version, + Base_WAF_Version, ) @@ -267,3 +268,10 @@ class Test_Lfi_Rules_Version(Base_Rules_Version): """Test lfi min rules version""" min_version = "1.13.3" + + +@features.rasp_local_file_inclusion +class Test_Lfi_Waf_Version(Base_WAF_Version): + """Test lfi WAF version""" + + min_version = "1.20.1" diff --git a/tests/appsec/rasp/test_libddwaf.py b/tests/appsec/rasp/test_libddwaf.py deleted file mode 100644 index fcfbbda0da..0000000000 --- a/tests/appsec/rasp/test_libddwaf.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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 -from tests.appsec.rasp.utils import find_series, validate_metric_tag_version - - -@features.rasp_local_file_inclusion -@features.rasp_sql_injection -@features.rasp_shell_injection -@features.rasp_server_side_request_forgery -class Test_Libddwaf_Version: - """Test libddwaf version""" - - def test_min_version(self): - """Checks data in waf.init metric to verify waf version""" - min_version = [1, 20, 1] - - series = find_series(True, "appsec", "waf.init") - assert series - assert any(validate_metric_tag_version("waf_version", min_version, s) for s in series) diff --git a/tests/appsec/rasp/test_shi.py b/tests/appsec/rasp/test_shi.py index 31efbb5266..b88c329530 100644 --- a/tests/appsec/rasp/test_shi.py +++ b/tests/appsec/rasp/test_shi.py @@ -9,7 +9,9 @@ validate_stack_traces, find_series, validate_metric, + validate_metric_variant, Base_Rules_Version, + Base_WAF_Version, ) @@ -173,6 +175,31 @@ def test_shi_telemetry(self): ] +@rfc("https://docs.google.com/document/d/1DDWy3frMXDTAbk-BfnZ1FdRwuPx6Pl7AWyR4zjqRFZw") +@features.rasp_shell_injection +@scenarios.appsec_rasp +class Test_Shi_Telemetry_Variant_Tag: + """Validate Telemetry data variant tag on exploit attempts""" + + def setup_shi_telemetry(self): + self.r = weblog.get("/rasp/shi", params={"list_dir": "$(cat /etc/passwd 1>&2 ; echo .)"}) + + def test_shi_telemetry(self): + assert self.r.status_code == 403 + + series_eval = find_series(True, "appsec", "rasp.rule.eval") + assert series_eval + assert any(validate_metric_variant("rasp.rule.eval", "command_injection", "shell", s) for s in series_eval), [ + s.get("tags") for s in series_eval + ] + + series_match = find_series(True, "appsec", "rasp.rule.match") + assert series_match + assert any(validate_metric_variant("rasp.rule.match", "command_injection", "shell", s) for s in series_match), [ + s.get("tags") for s in series_match + ] + + @rfc("https://docs.google.com/document/d/1gCXU3LvTH9en3Bww0AC2coSJWz1m7HcavZjvMLuDCWg/edit#heading=h.giijrtyn1fdx") @features.rasp_shell_injection @scenarios.remote_config_mocked_backend_asm_dd @@ -188,3 +215,10 @@ class Test_Shi_Rules_Version(Base_Rules_Version): """Test shi min rules version""" min_version = "1.13.1" + + +@features.rasp_local_file_inclusion +class Test_Shi_Waf_Version(Base_WAF_Version): + """Test shi WAF version""" + + min_version = "1.20.1" diff --git a/tests/appsec/rasp/test_sqli.py b/tests/appsec/rasp/test_sqli.py index 5f95dc88fb..ae5fab44a4 100644 --- a/tests/appsec/rasp/test_sqli.py +++ b/tests/appsec/rasp/test_sqli.py @@ -10,6 +10,7 @@ find_series, validate_metric, Base_Rules_Version, + Base_WAF_Version, ) @@ -192,3 +193,10 @@ class Test_Sqli_Rules_Version(Base_Rules_Version): """Test Sqli min rules version""" min_version = "1.13.2" + + +@features.rasp_local_file_inclusion +class Test_Sqli_Waf_Version(Base_WAF_Version): + """Test sqli WAF version""" + + min_version = "1.20.1" diff --git a/tests/appsec/rasp/test_ssrf.py b/tests/appsec/rasp/test_ssrf.py index 9fd7c16ea5..1dee9e7c54 100644 --- a/tests/appsec/rasp/test_ssrf.py +++ b/tests/appsec/rasp/test_ssrf.py @@ -10,6 +10,7 @@ find_series, validate_metric, Base_Rules_Version, + Base_WAF_Version, ) @@ -200,3 +201,10 @@ class Test_Ssrf_Rules_Version(Base_Rules_Version): """Test ssrf min rules version""" min_version = "1.13.2" + + +@features.rasp_local_file_inclusion +class Test_Ssrf_Waf_Version(Base_WAF_Version): + """Test ssrf WAF version""" + + min_version = "1.20.1" diff --git a/tests/appsec/rasp/utils.py b/tests/appsec/rasp/utils.py index 9657550752..bb17a89495 100644 --- a/tests/appsec/rasp/utils.py +++ b/tests/appsec/rasp/utils.py @@ -93,6 +93,16 @@ def validate_metric(name, type, metric): ) +def validate_metric_variant(name, type, variant, metric): + return ( + metric.get("metric") == name + and metric.get("type") == "count" + and f"rule_type:{type}" in metric.get("tags", ()) + and f"rule_variant:{variant}" in metric.get("tags", ()) + and any(s.startswith("waf_version:") for s in metric.get("tags", ())) + ) + + def validate_metric_tag_version(tag_prefix, min_version, metric): for tag in metric["tags"]: if tag.startswith(tag_prefix + ":"): @@ -154,3 +164,17 @@ def test_min_version(self): series = find_series(True, "appsec", "waf.init") assert series assert any(validate_metric_tag_version("event_rules_version", min_version_array, s) for s in series) + + +class Base_WAF_Version: + """Test libddwaf version""" + + min_version = "1.20.1" + + def test_min_version(self): + """Checks data in waf.init metric to verify waf version""" + + min_version_array = list(map(int, self.min_version.split("."))) + series = find_series(True, "appsec", "waf.init") + assert series + assert any(validate_metric_tag_version("waf_version", min_version_array, s) for s in series) diff --git a/utils/_features.py b/utils/_features.py index 7db6614618..af1ae5a484 100644 --- a/utils/_features.py +++ b/utils/_features.py @@ -2387,6 +2387,16 @@ def rasp_shell_injection(test_object): pytest.mark.features(feature_id=318)(test_object) return test_object + @staticmethod + def rasp_command_injection(test_object): + """ + Appsec RASP rule : command injection + + https://feature-parity.us1.prod.dog/#/?feature=342 + """ + pytest.mark.features(feature_id=342)(test_object) + return test_object + @staticmethod def debugger_exception_replay(test_object): """ diff --git a/utils/build/docker/dotnet/weblog/Controllers/RaspController.cs b/utils/build/docker/dotnet/weblog/Controllers/RaspController.cs index 307775b633..ed29e14c18 100644 --- a/utils/build/docker/dotnet/weblog/Controllers/RaspController.cs +++ b/utils/build/docker/dotnet/weblog/Controllers/RaspController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Data.Sqlite; using System; +using System.ComponentModel; using System.Data.SqlClient; using System.Diagnostics; using System.Xml.Serialization; @@ -46,25 +47,65 @@ public IActionResult shiPostForm([FromForm] Model data) public IActionResult shiPostJson([FromBody] Model data) { return ExecuteCommandInternal("ls " + data.List_dir); - } - - private IActionResult ExecuteCommandInternal(string commandLine) + } + + [HttpGet("cmdi")] + public IActionResult cmdiGet(string command) { - if (!string.IsNullOrEmpty(commandLine)) - { - ProcessStartInfo startInfo = new ProcessStartInfo(); - startInfo.FileName = commandLine; - startInfo.UseShellExecute = true; - var result = Process.Start(startInfo); + return ExecuteCommandInternal(command, false); + } + + [XmlRoot("command")] + public class CmdiModel + { + [XmlText] + public string Value { get; set; } + } - return Content($"Process launched."); + [HttpPost("cmdi")] + [Consumes("application/xml")] + public IActionResult cmdiPostXml([FromBody] CmdiModel data) + { + return ExecuteCommandInternal(data.Value, false); + } + + [HttpPost("cmdi")] + [Consumes("application/x-www-form-urlencoded")] + public IActionResult cmdiPostForm([FromForm] Model data) + { + return ExecuteCommandInternal(data.Command, false); + } + + [HttpPost("cmdi")] + [Consumes("application/json")] + public IActionResult cmdiPostJson([FromBody] Model data) + { + return ExecuteCommandInternal(data.Command, false); + } + + private IActionResult ExecuteCommandInternal(string commandLine, bool useShell = true) + { + try + { + if (!string.IsNullOrEmpty(commandLine)) + { + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.FileName = commandLine; + startInfo.UseShellExecute = useShell; + var result = Process.Start(startInfo); + return Content($"Process launched."); + } + else + { + return Content("No process name was provided"); + } } - else + catch (Win32Exception) { - return Content("No process name was provided"); + return Content("Non existing file."); } } - + [HttpGet("lfi")] public IActionResult lfiGet(string file) { @@ -73,7 +114,7 @@ public IActionResult lfiGet(string file) var result = System.IO.File.ReadAllText(file); return Content(result); } - catch (System.IO.FileNotFoundException ex) + catch (System.IO.FileNotFoundException) { return Content("File not found"); } @@ -147,18 +188,18 @@ public IActionResult SsrfPostJson([FromBody] Model data) var result = new System.Net.Http.HttpClient().GetStringAsync(("http://" + data.Domain)).Result; return Content(result); } - + [HttpGet("sqli")] public IActionResult SqliGet(string user_id) { - if (!string.IsNullOrEmpty(user_id)) - { - return Content(SqlQuery(user_id)); - } - else - { - return BadRequest("No params provided"); - } + if (!string.IsNullOrEmpty(user_id)) + { + return Content(SqlQuery(user_id)); + } + else + { + return BadRequest("No params provided"); + } } [XmlRoot("user_id")] @@ -186,29 +227,29 @@ public IActionResult SqliPostXml([FromBody] SqliModel data) [Consumes("application/x-www-form-urlencoded")] public IActionResult SqliPostForm([FromForm] Model data) { - if (!string.IsNullOrEmpty(data.User_id)) - { - return Content(SqlQuery(data.User_id)); - } - else - { - return BadRequest("No params provided"); - } + if (!string.IsNullOrEmpty(data.User_id)) + { + return Content(SqlQuery(data.User_id)); + } + else + { + return BadRequest("No params provided"); + } } - [HttpPost("sqli")] + [HttpPost("sqli")] [Consumes("application/json")] public IActionResult SqliPostJson([FromBody] Model data) { - if (!string.IsNullOrEmpty(data.User_id)) - { - return Content(SqlQuery(data.User_id)); - } - else - { - return BadRequest("No params provided"); - } + if (!string.IsNullOrEmpty(data.User_id)) + { + return Content(SqlQuery(data.User_id)); + } + else + { + return BadRequest("No params provided"); + } } private string SqlQuery(string user) diff --git a/utils/build/docker/dotnet/weblog/Models/Model.cs b/utils/build/docker/dotnet/weblog/Models/Model.cs index 44944bd160..0f65ff9d15 100644 --- a/utils/build/docker/dotnet/weblog/Models/Model.cs +++ b/utils/build/docker/dotnet/weblog/Models/Model.cs @@ -17,6 +17,7 @@ public class Model public string? Domain { get; set; } public string? User_id { get; set; } public string? List_dir { get; set; } + public string? Command { get; set; } public override string ToString() => $"value {Value}, value2 {Value2}"; } diff --git a/utils/dd_constants.py b/utils/dd_constants.py index 83c063c41d..9ea7deb6d7 100644 --- a/utils/dd_constants.py +++ b/utils/dd_constants.py @@ -63,3 +63,4 @@ class Capabilities(IntEnum): ASM_SESSION_FINGERPRINT = 33 ASM_NETWORK_FINGERPRINT = 34 ASM_HEADER_FINGERPRINT = 35 + ASM_RASP_CMDI = 37