diff --git a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py index 545f578d878..a10455dee42 100644 --- a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py +++ b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py @@ -6,10 +6,12 @@ from .._utils import _get_source_index from ..constants import VULN_CMDI +from ..constants import VULN_CODE_INJECTION from ..constants import VULN_HEADER_INJECTION from ..constants import VULN_SQL_INJECTION from ..constants import VULN_SSRF from .command_injection_sensitive_analyzer import command_injection_sensitive_analyzer +from .default_sensitive_analyzer import default_sensitive_analyzer from .header_injection_sensitive_analyzer import header_injection_sensitive_analyzer from .sql_sensitive_analyzer import sql_sensitive_analyzer from .url_sensitive_analyzer import url_sensitive_analyzer @@ -19,6 +21,7 @@ REDACTED_SOURCE_BUFFER = string.ascii_letters + string.digits LEN_SOURCE_BUFFER = len(REDACTED_SOURCE_BUFFER) +VALUE_MAX_LENGHT = 45 def get_redacted_source(length): @@ -42,6 +45,7 @@ def __init__(self): VULN_SQL_INJECTION: sql_sensitive_analyzer, VULN_SSRF: url_sensitive_analyzer, VULN_HEADER_INJECTION: header_injection_sensitive_analyzer, + VULN_CODE_INJECTION: default_sensitive_analyzer, } @staticmethod @@ -288,7 +292,7 @@ def to_redacted_json(self, evidence_value, sensitive, tainted_ranges, sources): return {"redacted_value_parts": value_parts, "redacted_sources": redacted_sources} def redact_source(self, sources, redacted_sources, redacted_sources_context, source_index, start, end): - if source_index is not None: + if source_index is not None and source_index < len(sources): if not sources[source_index].redacted: redacted_sources.append(source_index) sources[source_index].pattern = get_redacted_source(len(sources[source_index].value)) @@ -303,8 +307,10 @@ def write_value_part(self, value_parts, value, source_index=None): if value: if source_index is not None: value_parts.append({"value": value, "source": source_index}) - else: + elif len(value) < VALUE_MAX_LENGHT: value_parts.append({"value": value}) + else: + value_parts.append({"redacted": True}) def write_redacted_value_part( self, diff --git a/ddtrace/appsec/_iast/_evidence_redaction/default_sensitive_analyzer.py b/ddtrace/appsec/_iast/_evidence_redaction/default_sensitive_analyzer.py new file mode 100644 index 00000000000..d2e29484528 --- /dev/null +++ b/ddtrace/appsec/_iast/_evidence_redaction/default_sensitive_analyzer.py @@ -0,0 +1,11 @@ +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def default_sensitive_analyzer(evidence, name_pattern, value_pattern): + if name_pattern.search(evidence.value) or value_pattern.search(evidence.value): + return [{"start": 0, "end": len(evidence.value)}] + + return [] diff --git a/ddtrace/appsec/_iast/_handlers.py b/ddtrace/appsec/_iast/_handlers.py index 4d4628d910e..c3761451e99 100644 --- a/ddtrace/appsec/_iast/_handlers.py +++ b/ddtrace/appsec/_iast/_handlers.py @@ -153,6 +153,7 @@ def _on_django_patch(): functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), ) ) + # we instrument those sources on _on_django_func_wrapped _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) _set_metric_iast_instrumented_source(OriginType.HEADER) diff --git a/tests/appsec/iast/taint_sinks/_taint_sinks_utils.py b/tests/appsec/iast/taint_sinks/_taint_sinks_utils.py index c089ac4c3dc..288b72d015c 100644 --- a/tests/appsec/iast/taint_sinks/_taint_sinks_utils.py +++ b/tests/appsec/iast/taint_sinks/_taint_sinks_utils.py @@ -17,6 +17,14 @@ def get_parametrize(vuln_type, ignore_list=None): data = json.loads(open(fixtures_filename).read()) idx = -1 for element in data["suite"]: + if element["description"] in ( + "$1 with query parameters or fragment", + "$1 - Tainted range based redaction - multiple ranges", + "Redacted source that needs to be truncated", + "Query with single quoted string literal and null source", + ): + continue + if element["type"] == "VULNERABILITIES": evidence_parameters = [ param for k, params in element.get("parameters", {}).items() for param in params if param == vuln_type @@ -46,13 +54,19 @@ def get_parametrize(vuln_type, ignore_list=None): if value_part.get("value"): value_part["value"] = value_part["value"].replace(replace, value) - yield evidence_input_copy, sources_expected, vulnerabilities_expected_copy + if all( + [ + bool(input_ranges["iinfo"].get("parameterName", {})) + for input_ranges in evidence_input_copy.get("ranges", {}) + ] + ): + yield evidence_input_copy, sources_expected, vulnerabilities_expected_copy, element else: idx += 1 if ignore_list and idx in ignore_list: continue - yield evidence_input[0], sources_expected, vulnerabilities_expected + yield evidence_input[0], sources_expected, vulnerabilities_expected, element def _taint_pyobject_multiranges(pyobject, elements): diff --git a/tests/appsec/iast/taint_sinks/redaction_fixtures/evidence-redaction-suite.json b/tests/appsec/iast/taint_sinks/redaction_fixtures/evidence-redaction-suite.json index 0719edb550a..1c41f7c1917 100644 --- a/tests/appsec/iast/taint_sinks/redaction_fixtures/evidence-redaction-suite.json +++ b/tests/appsec/iast/taint_sinks/redaction_fixtures/evidence-redaction-suite.json @@ -8,6 +8,7 @@ "$1": [ "access_key_id", "accessKeyId", + "address", "apikey", "api_key", "apiToken", @@ -21,8 +22,11 @@ "consumer_key", "consumerSecret", "consumer_secret", + "email", "expirationToken", "expiration_token", + "lastname", + "mail", "pass", "passwd", "password", @@ -43,7 +47,10 @@ "sign", "signature", "signed", - "token" + "surname", + "token", + "user", + "username" ] }, "input": [ @@ -70,7 +77,8 @@ "glpat-xxxxxxxxxxxxxxxxxxxx", "-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAkVDOAMenPclQ7z5U3i3QYw4lQuijEyxnEgTXkk88L20moFBU 4vJkSguvUXrGzNiH+WMWWWTAXBTDdtOHApQJSdU0P4lY+0P3Lw3WeZaetPm583ac DlaCk9DaqPQnjpZ/9DLqmx1r5JYAZbCiuXWMA0lzJUOOniwt94BWCnz3+0LbrC7j NsiaC7cRc1kmj/Nmu8ydA4eop44tJMlaXb9nnUIxglUm0yL1NDOTzokTP03Fa7JW t46gMo6co751nYm43MwOb/cY0Uh6+i59czXuCs0hFpWyEkQJDjcQNXgy9ctI0R/J nBbQykSJG8C0cB9nsfwbtuRIQVrgoj65erlXawIDAQABAoIBAByGkTnj93eQilu8 j6phsfOP9k6RHloIMF+AJdUpyrXApoF344H9dSR38L187YOOyfpxshRwS7aHuOsd kPY3my8sNCp4ysfgSqio/b42jAcYsqERWocSAmYD7LiX3SAHeSy1xgoXF3Py4jcU Go1vfsGybHEXNurj304jmkBK0d83rYdYFNa58jY+6fCrt7b7SdxcjImvRbx0ByvB O/igAQxHLYZAVM+9eD8kHRt6nFkdllGkdynMPx82RllpjyZvxBm8hXeRCXvT78Ja 9aOx6YZLND6iLinAh2J+zFKTtl+iX8DD+39DMFEgLjgKJB84phux1h/2PP8RS2tp 5TqWy7ECgYEA+A8HEKKFTaYD4GQaiD+L4gOh2ZcLykdG8IIXRxzCPtv5VWKS2SCz WWyFoVRlV4b6q96PJwdS/6skbbWS98HIg3aqhOVaXyGxZHlzRgopE3OfRiDcf/Xd bO+Y7phH6h+hMBWpAAojJ+lWzGkg2DewCY0NjkUdOrFAZZWWLrQqGGMCgYEAlfe+ S3gXGqVk3ZyS4f8TyWrkKfVaRVa2KT0GGBJ8TNOB7xlf0oVmKCKSGoWbY5znt2e2 OTb6/zL0qzm1R9pNw5tUE5k/cCReZ20TpcHExoc+1prvmoCO8ToYMfGPOTBpRKBo Hdtx4xjBVe9omP6c/U8jfMDUL+cEKgvvjHUXv1kCgYEApTo1RYJLcoYjTOLAvYI+ ZYRv2SSAKPNDME4mvSpNxFr3gEVRdSkP7X+YnvY9LojtDXAIQEHjqgLQF/d69mZw bgir2it+/6DMrRUskDmSVK+OJsMavG0DWV1aq4ppVGxPDF1RHYKjGiGVvEBGLV8i daornlkw9/g64a86ws8kvusCgYBvnRs7//zyD/aqGUYYfUe0uKFnuPueb5LTzl8i u19XrnMeCLyQakhFxrUGmDm2QakTj1TH8GuOU9ZVOXX6LDeERa6lh4D3bZn1T/E3 hKd3OmFCR73cN6IrVxl60lXOMoGmWdwjnJd+dYYu9yfZ9mXRAX1f9AP4Qu+Oe6Ol 3d/2wQKBgQCgdA48bkRGFR/OqcGACNVQFcXQYvSabKOZkg303NH7p4pD8Ng6FDW+ r8r8+M/iMF9q7XvcX5pF8zgGk/MfHOdf9wWv7Uih7CIQzJLEs+OzNqx//Jn1EuV4 GBudByVPLqUDB5nvcDxTTsDP+gPFQtQ1mAWB1r18s9x4OioqvoV/6Q== -----END RSA PRIVATE KEY-----", "-----BEGIN OPENSSH PRIVATE KEY----- MIIEpAIBAAKCAQEAkVDOAMenPclQ7z5U3i3QYw4lQuijEyxnEgTXkk88L20moFBU 4vJkSguvUXrGzNiH+WMWWWTAXBTDdtOHApQJSdU0P4lY+0P3Lw3WeZaetPm583ac DlaCk9DaqPQnjpZ/9DLqmx1r5JYAZbCiuXWMA0lzJUOOniwt94BWCnz3+0LbrC7j NsiaC7cRc1kmj/Nmu8ydA4eop44tJMlaXb9nnUIxglUm0yL1NDOTzokTP03Fa7JW t46gMo6co751nYm43MwOb/cY0Uh6+i59czXuCs0hFpWyEkQJDjcQNXgy9ctI0R/J nBbQykSJG8C0cB9nsfwbtuRIQVrgoj65erlXawIDAQABAoIBAByGkTnj93eQilu8 j6phsfOP9k6RHloIMF+AJdUpyrXApoF344H9dSR38L187YOOyfpxshRwS7aHuOsd kPY3my8sNCp4ysfgSqio/b42jAcYsqERWocSAmYD7LiX3SAHeSy1xgoXF3Py4jcU Go1vfsGybHEXNurj304jmkBK0d83rYdYFNa58jY+6fCrt7b7SdxcjImvRbx0ByvB O/igAQxHLYZAVM+9eD8kHRt6nFkdllGkdynMPx82RllpjyZvxBm8hXeRCXvT78Ja 9aOx6YZLND6iLinAh2J+zFKTtl+iX8DD+39DMFEgLjgKJB84phux1h/2PP8RS2tp 5TqWy7ECgYEA+A8HEKKFTaYD4GQaiD+L4gOh2ZcLykdG8IIXRxzCPtv5VWKS2SCz WWyFoVRlV4b6q96PJwdS/6skbbWS98HIg3aqhOVaXyGxZHlzRgopE3OfRiDcf/Xd bO+Y7phH6h+hMBWpAAojJ+lWzGkg2DewCY0NjkUdOrFAZZWWLrQqGGMCgYEAlfe+ S3gXGqVk3ZyS4f8TyWrkKfVaRVa2KT0GGBJ8TNOB7xlf0oVmKCKSGoWbY5znt2e2 OTb6/zL0qzm1R9pNw5tUE5k/cCReZ20TpcHExoc+1prvmoCO8ToYMfGPOTBpRKBo Hdtx4xjBVe9omP6c/U8jfMDUL+cEKgvvjHUXv1kCgYEApTo1RYJLcoYjTOLAvYI+ ZYRv2SSAKPNDME4mvSpNxFr3gEVRdSkP7X+YnvY9LojtDXAIQEHjqgLQF/d69mZw bgir2it+/6DMrRUskDmSVK+OJsMavG0DWV1aq4ppVGxPDF1RHYKjGiGVvEBGLV8i daornlkw9/g64a86ws8kvusCgYBvnRs7//zyD/aqGUYYfUe0uKFnuPueb5LTzl8i u19XrnMeCLyQakhFxrUGmDm2QakTj1TH8GuOU9ZVOXX6LDeERa6lh4D3bZn1T/E3 hKd3OmFCR73cN6IrVxl60lXOMoGmWdwjnJd+dYYu9yfZ9mXRAX1f9AP4Qu+Oe6Ol 3d/2wQKBgQCgdA48bkRGFR/OqcGACNVQFcXQYvSabKOZkg303NH7p4pD8Ng6FDW+ r8r8+M/iMF9q7XvcX5pF8zgGk/MfHOdf9wWv7Uih7CIQzJLEs+OzNqx//Jn1EuV4 GBudByVPLqUDB5nvcDxTTsDP+gPFQtQ1mAWB1r18s9x4OioqvoV/6Q== -----END OPENSSH PRIVATE KEY-----", - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCRUM4Ax6c9yVDvPlTeLdBjDiVC6KMTLGcSBNeSTzwvbSagUFTi8mRKC69ResbM2If5YxZZZMBcFMN204cClAlJ1TQ/iVj7Q/cvDdZ5lp60+bnzdpwOVoKT0Nqo9CeOln/0MuqbHWvklgBlsKK5dYwDSXMlQ46eLC33gFYKfPf7QtusLuM2yJoLtxFzWSaP82a7zJ0Dh6inji0kyVpdv2edQjGCVSbTIvU0M5POiRM/TcVrsla3jqAyjpyjvnWdibjczA5v9xjRSHr6Ln1zNe4KzSEWlbISRAkONxA1eDL1y0jRH8mcFtDKRIkbwLRwH2ex/Bu25EhBWuCiPrl6uVdr" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCRUM4Ax6c9yVDvPlTeLdBjDiVC6KMTLGcSBNeSTzwvbSagUFTi8mRKC69ResbM2If5YxZZZMBcFMN204cClAlJ1TQ/iVj7Q/cvDdZ5lp60+bnzdpwOVoKT0Nqo9CeOln/0MuqbHWvklgBlsKK5dYwDSXMlQ46eLC33gFYKfPf7QtusLuM2yJoLtxFzWSaP82a7zJ0Dh6inji0kyVpdv2edQjGCVSbTIvU0M5POiRM/TcVrsla3jqAyjpyjvnWdibjczA5v9xjRSHr6Ln1zNe4KzSEWlbISRAkONxA1eDL1y0jRH8mcFtDKRIkbwLRwH2ex/Bu25EhBWuCiPrl6uVdr", + "mail@to.net" ] }, "input": [ @@ -299,6 +307,54 @@ ] } }, + { + "type": "VULNERABILITIES", + "description": "Query with single quoted string literal and null source", + "input": [ + { + "type": "SQL_INJECTION", + "evidence": { + "value": "select * from users where username = 'user'", + "ranges": [ + { + "start": 38, + "end": 42, + "iinfo": { + "type": "http.request.body" + } + } + ] + } + } + ], + "expected": { + "sources": [ + { + "origin": "http.request.body" + } + ], + "vulnerabilities": [ + { + "type": "SQL_INJECTION", + "evidence": { + "valueParts": [ + { + "value": "select * from users where username = '" + }, + { + "redacted": true, + "source": 0, + "pattern": "****" + }, + { + "value": "'" + } + ] + } + } + ] + } + }, { "type": "VULNERABILITIES", "description": "$1 query with double quoted string literal $2", @@ -2694,7 +2750,7 @@ "end": 47, "iinfo": { "type": "http.request.parameter", - "parameterName": "email", + "parameterName": "param", "parameterValue": "' OR TRUE --" } } @@ -2706,7 +2762,7 @@ "sources": [ { "origin": "http.request.parameter", - "name": "email", + "name": "param", "value": "' OR TRUE --" } ], @@ -2850,10 +2906,19 @@ }, { "type": "VULNERABILITIES", - "description": "Tainted range based redaction ", + "description": "$1 - Tainted range based redaction ", + "parameters": { + "$1": [ + "XSS", + "CODE_INJECTION", + "EMAIL_HTML_INJECTION", + "TEMPLATE_INJECTION", + "UNTRUSTED_DESERIALIZATION" + ] + }, "input": [ { - "type": "XSS", + "type": "$1", "evidence": { "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS vulnerability but can be extended to future ones", "ranges": [ @@ -2880,7 +2945,7 @@ ], "vulnerabilities": [ { - "type": "XSS", + "type": "$1", "evidence": { "valueParts": [ { @@ -2901,10 +2966,19 @@ }, { "type": "VULNERABILITIES", - "description": "Tainted range based redaction - with redactable source ", + "description": "$1 - Tainted range based redaction - with redactable source ", + "parameters": { + "$1": [ + "XSS", + "CODE_INJECTION", + "EMAIL_HTML_INJECTION", + "TEMPLATE_INJECTION", + "UNTRUSTED_DESERIALIZATION" + ] + }, "input": [ { - "type": "XSS", + "type": "$1", "evidence": { "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS vulnerability but can be extended to future ones", "ranges": [ @@ -2932,7 +3006,7 @@ ], "vulnerabilities": [ { - "type": "XSS", + "type": "$1", "evidence": { "valueParts": [ { @@ -2954,10 +3028,19 @@ }, { "type": "VULNERABILITIES", - "description": "Tainted range based redaction - with null source ", + "description": "$1 - Tainted range based redaction - with null source ", + "parameters": { + "$1": [ + "XSS", + "CODE_INJECTION", + "EMAIL_HTML_INJECTION", + "TEMPLATE_INJECTION", + "UNTRUSTED_DESERIALIZATION" + ] + }, "input": [ { - "type": "XSS", + "type": "$1", "evidence": { "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS vulnerability but can be extended to future ones", "ranges": [ @@ -2980,7 +3063,7 @@ ], "vulnerabilities": [ { - "type": "XSS", + "type": "$1", "evidence": { "valueParts": [ { @@ -3001,10 +3084,19 @@ }, { "type": "VULNERABILITIES", - "description": "Tainted range based redaction - multiple ranges", + "description": "$1 - Tainted range based redaction - multiple ranges", + "parameters": { + "$1": [ + "XSS", + "CODE_INJECTION", + "EMAIL_HTML_INJECTION", + "TEMPLATE_INJECTION", + "UNTRUSTED_DESERIALIZATION" + ] + }, "input": [ { - "type": "XSS", + "type": "$1", "evidence": { "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS vulnerability but can be extended to future ones", "ranges": [ @@ -3045,7 +3137,7 @@ ], "vulnerabilities": [ { - "type": "XSS", + "type": "$1", "evidence": { "valueParts": [ { @@ -3073,10 +3165,19 @@ }, { "type": "VULNERABILITIES", - "description": "Tainted range based redaction - first range at the beginning ", + "description": "$1 - Tainted range based redaction - first range at the beginning ", + "parameters": { + "$1": [ + "XSS", + "CODE_INJECTION", + "EMAIL_HTML_INJECTION", + "TEMPLATE_INJECTION", + "UNTRUSTED_DESERIALIZATION" + ] + }, "input": [ { - "type": "XSS", + "type": "$1", "evidence": { "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS vulnerability but can be extended to future ones", "ranges": [ @@ -3117,7 +3218,7 @@ ], "vulnerabilities": [ { - "type": "XSS", + "type": "$1", "evidence": { "valueParts": [ { @@ -3142,10 +3243,19 @@ }, { "type": "VULNERABILITIES", - "description": "Tainted range based redaction - last range at the end ", + "description": "$1 - Tainted range based redaction - last range at the end ", + "parameters": { + "$1": [ + "XSS", + "CODE_INJECTION", + "EMAIL_HTML_INJECTION", + "TEMPLATE_INJECTION", + "UNTRUSTED_DESERIALIZATION" + ] + }, "input": [ { - "type": "XSS", + "type": "$1", "evidence": { "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS", "ranges": [ @@ -3186,7 +3296,7 @@ ], "vulnerabilities": [ { - "type": "XSS", + "type": "$1", "evidence": { "valueParts": [ { @@ -3208,10 +3318,19 @@ }, { "type": "VULNERABILITIES", - "description": "Tainted range based redaction - whole text ", + "description": "$1 - Tainted range based redaction - whole text ", + "parameters": { + "$1": [ + "XSS", + "CODE_INJECTION", + "EMAIL_HTML_INJECTION", + "TEMPLATE_INJECTION", + "UNTRUSTED_DESERIALIZATION" + ] + }, "input": [ { - "type": "XSS", + "type": "$1", "evidence": { "value": "this could be a super long text, so we need to reduce it before send it to the backend. This redaction strategy applies to XSS", "ranges": [ @@ -3238,7 +3357,7 @@ ], "vulnerabilities": [ { - "type": "XSS", + "type": "$1", "evidence": { "valueParts": [ { @@ -3403,7 +3522,7 @@ "end": 4, "iinfo": { "type": "http.request.parameter", - "parameterName": "username", + "parameterName": "param", "parameterValue": "PREFIX_user" } } @@ -3415,7 +3534,7 @@ "end": 4, "iinfo": { "type": "http.request.parameter", - "parameterName": "username", + "parameterName": "param", "parameterValue": "PREFIX_user" } } @@ -3428,7 +3547,7 @@ "sources": [ { "origin": "http.request.parameter", - "name": "username", + "name": "param", "redacted": true, "pattern": "abcdefghijk" } @@ -3613,6 +3732,66 @@ ] } }, + { + "type": "VULNERABILITIES", + "description": "Redacted source that needs to be truncated", + "input": [ + { + "type": "SQL_INJECTION", + "evidence": { + "value": "select * from users where username = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Sed ut perspiciatis unde omnis iste natus error sit voluptatem ac'", + "ranges": [ + { + "start": 26, + "end": 549, + "iinfo": { + "type": "http.request.parameter", + "parameterName": "clause", + "parameterValue": "username = 'Lorem%20ipsum%20dolor%20sit%20amet,%20consectetur%20adipiscing%20elit,%20sed%20do%20eiusmod%20tempor%20incididunt%20ut%20labore%20et%20dolore%20magna%20aliqua.%20Ut%20enim%20ad%20minim%20veniam,%20quis%20nostrud%20exercitation%20ullamco%20laboris%20nisi%20ut%20aliquip%20ex%20ea%20commodo%20consequat.%20Duis%20aute%20irure%20dolor%20in%20reprehenderit%20in%20voluptate%20velit%20esse%20cillum%20dolore%20eu%20fugiat%20nulla%20pariatur.%20Excepteur%20sint%20occaecat%20cupidatat%20non%20proident,%20sunt%20in%20culpa%20qui%20officia%20deserunt%20mollit%20anim%20id%20est%20laborum.Sed%20ut%20perspiciatis%20unde%20omnis%20iste%20natus%20error%20sit%20voluptatem%20ac'" + } + } + ] + } + } + ], + "expected": { + "sources": [ + { + "origin": "http.request.parameter", + "name": "clause", + "redacted": true, + "pattern": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ab", + "truncated": "right" + } + ], + "vulnerabilities": [ + { + "type": "SQL_INJECTION", + "evidence": { + "valueParts": [ + { + "value": "select * from users where " + }, + { + "source": 0, + "value": "username = '" + }, + { + "source": 0, + "redacted": true, + "truncated": "right", + "pattern": "**********************************************************************************************************************************************************************************************************************************************************" + }, + { + "source": 0, + "value": "'" + } + ] + } + } + ] + } + }, { "type": "VULNERABILITIES", "description": "No redacted that needs to be truncated - whole text", @@ -4016,6 +4195,54 @@ } ] } + }, + { + "type": "VULNERABILITIES", + "description": "Hardcoded password with sensitive data in the variable name", + "input": [ + { + "type": "HARDCODED_PASSWORD", + "evidence": { + "value": "gho_apasswapasswapasswapasswapasswapassw" + } + } + ], + "expected": { + "vulnerabilities": [ + { + "type": "HARDCODED_PASSWORD", + "evidence": { + "valueParts": [ + { + "redacted": true + } + ] + } + } + ] + } + }, + { + "type": "VULNERABILITIES", + "description": "Hardcoded password without sensitive data in the variable name", + "input": [ + { + "type": "HARDCODED_PASSWORD", + "evidence": { + "value": "this_is_a_password" + } + } + ], + "expected": { + "vulnerabilities": [ + { + "type": "HARDCODED_PASSWORD", + "evidence": { + "value": "this_is_a_password" + } + } + ] + } } ] -} +} \ No newline at end of file diff --git a/tests/appsec/iast/taint_sinks/test_code_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_code_injection_redacted.py new file mode 100644 index 00000000000..3d9890e04bb --- /dev/null +++ b/tests/appsec/iast/taint_sinks/test_code_injection_redacted.py @@ -0,0 +1,48 @@ +import pytest + +from ddtrace.appsec._iast._taint_tracking import origin_to_str +from ddtrace.appsec._iast._taint_tracking import str_to_origin +from ddtrace.appsec._iast.constants import VULN_CODE_INJECTION +from ddtrace.appsec._iast.taint_sinks.code_injection import CodeInjection +from tests.appsec.iast.taint_sinks._taint_sinks_utils import _taint_pyobject_multiranges +from tests.appsec.iast.taint_sinks._taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data + + +# FIXME: ideally all these should pass, through the key is that we don't leak any potential PII +_ignore_list = {} + + +@pytest.mark.parametrize( + "evidence_input,sources_expected,vulnerabilities_expected,element", + list(get_parametrize(VULN_CODE_INJECTION, ignore_list=_ignore_list)), +) +def test_code_injection_redaction_suite( + evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults, element +): + tainted_object = evidence_input_value = evidence_input.get("value", "") + if evidence_input_value: + tainted_object = _taint_pyobject_multiranges( + evidence_input_value, + [ + ( + input_ranges["iinfo"]["parameterName"], + input_ranges["iinfo"]["parameterValue"], + str_to_origin(input_ranges["iinfo"]["type"]), + input_ranges["start"], + input_ranges["end"] - input_ranges["start"], + ) + for input_ranges in evidence_input.get("ranges", {}) + ], + ) + + CodeInjection.report(tainted_object) + + data = _get_iast_data() + vulnerability = list(data["vulnerabilities"])[0] + source = list(data["sources"])[0] + source["origin"] = origin_to_str(source["origin"]) + + assert vulnerability["type"] == VULN_CODE_INJECTION + assert vulnerability["evidence"] == vulnerabilities_expected["evidence"] + assert source == sources_expected diff --git a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py index 4c25cda8dc2..e8bce5ba22d 100644 --- a/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_command_injection_redacted.py @@ -16,8 +16,12 @@ from tests.appsec.iast.taint_sinks.conftest import _get_iast_data -@pytest.mark.parametrize("evidence_input, sources_expected, vulnerabilities_expected", list(get_parametrize(VULN_CMDI))) -def test_cmdi_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): +@pytest.mark.parametrize( + "evidence_input,sources_expected,vulnerabilities_expected,element", list(get_parametrize(VULN_CMDI)) +) +def test_cmdi_redaction_suite( + evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults, element +): tainted_object = _taint_pyobject_multiranges( evidence_input["value"], [ diff --git a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py index 61a3aa83a49..05eca6bf3db 100644 --- a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py @@ -92,11 +92,11 @@ def test_common_django_header_injection_redact(header_name, header_value, value_ @pytest.mark.parametrize( - "evidence_input, sources_expected, vulnerabilities_expected", + "evidence_input,sources_expected,vulnerabilities_expected,element", list(get_parametrize(VULN_HEADER_INJECTION)), ) def test_header_injection_redaction_suite( - evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults + evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults, element ): tainted_object = _taint_pyobject_multiranges( evidence_input["value"], diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py index 996bc2ee356..75fa02802e4 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py @@ -4,12 +4,19 @@ import pytest from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import origin_to_str +from ddtrace.appsec._iast._taint_tracking import str_to_origin +from ddtrace.appsec._iast._taint_tracking._taint_objects import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from ddtrace.appsec._iast.reporter import Evidence from ddtrace.appsec._iast.reporter import IastSpanReporter from ddtrace.appsec._iast.reporter import Location from ddtrace.appsec._iast.reporter import Vulnerability +from ddtrace.appsec._iast.taint_sinks.path_traversal import PathTraversal +from tests.appsec.iast.taint_sinks._taint_sinks_utils import _taint_pyobject_multiranges +from tests.appsec.iast.taint_sinks._taint_sinks_utils import get_parametrize +from tests.appsec.iast.taint_sinks.conftest import _get_iast_data ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -124,3 +131,39 @@ def test_path_traversal_redact_abs_paths(iast_context_defaults): } ], } + + +@pytest.mark.parametrize( + "evidence_input,sources_expected,vulnerabilities_expected,element", + list(get_parametrize(VULN_PATH_TRAVERSAL)), +) +def test_path_traversal_redaction_suite( + evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults, element +): + tainted_object = _taint_pyobject_multiranges( + evidence_input["value"], + [ + ( + input_ranges["iinfo"]["parameterName"], + input_ranges["iinfo"]["parameterValue"], + str_to_origin(input_ranges["iinfo"]["type"]), + input_ranges["start"], + input_ranges["end"] - input_ranges["start"], + ) + for input_ranges in evidence_input["ranges"] + ], + ) + + assert is_pyobject_tainted(tainted_object) + + PathTraversal.report(tainted_object) + + data = _get_iast_data() + + vulnerability = list(data["vulnerabilities"])[0] + source = list(data["sources"])[0] + source["origin"] = origin_to_str(source["origin"]) + + assert vulnerability["type"] == VULN_PATH_TRAVERSAL + assert vulnerability["evidence"] == vulnerabilities_expected["evidence"] + assert source == sources_expected diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py index e00af701427..c30475b4cba 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py @@ -18,17 +18,13 @@ from tests.utils import override_global_config -# FIXME: ideally all these should pass, through the key is that we don't leak any potential PII -_ignore_list = { - 46, -} - - @pytest.mark.parametrize( - "evidence_input, sources_expected, vulnerabilities_expected", - list(get_parametrize(VULN_SQL_INJECTION, ignore_list=_ignore_list)), + "evidence_input,sources_expected,vulnerabilities_expected,element", + list(get_parametrize(VULN_SQL_INJECTION)), ) -def test_sqli_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): +def test_sqli_redaction_suite( + evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults, element +): with override_global_config(dict(_iast_deduplication_enabled=False)): tainted_object = _taint_pyobject_multiranges( evidence_input["value"], diff --git a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py index d5f60e8878e..6b7ef145371 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf_redacted.py @@ -21,9 +21,11 @@ @pytest.mark.parametrize( - "evidence_input, sources_expected, vulnerabilities_expected", list(get_parametrize(VULN_SSRF, ignore_list={9, 10})) + "evidence_input,sources_expected,vulnerabilities_expected,element", list(get_parametrize(VULN_SSRF)) ) -def test_ssrf_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): +def test_ssrf_redaction_suite( + evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults, element +): tainted_object = evidence_input_value = evidence_input.get("value", "") if evidence_input_value: tainted_object = _taint_pyobject_multiranges(