From 867150a098ee86d2dd6335af28c8d7ec64b6d1bc Mon Sep 17 00:00:00 2001 From: elsapet Date: Thu, 30 May 2024 09:21:24 +0200 Subject: [PATCH] feat(python): insecure allow origin (CWE-346) (#425) --- rules/python/django/insecure_allow_origin.yml | 66 +++++++++++++++++++ rules/python/lang/insecure_allow_origin.yml | 58 ++++++++++++++++ .../django/insecure_allow_origin/test.js | 20 ++++++ .../insecure_allow_origin/testdata/main.py | 8 +++ .../python/lang/insecure_allow_origin/test.js | 20 ++++++ .../insecure_allow_origin/testdata/main.py | 9 +++ 6 files changed, 181 insertions(+) create mode 100644 rules/python/django/insecure_allow_origin.yml create mode 100644 rules/python/lang/insecure_allow_origin.yml create mode 100644 tests/python/django/insecure_allow_origin/test.js create mode 100644 tests/python/django/insecure_allow_origin/testdata/main.py create mode 100644 tests/python/lang/insecure_allow_origin/test.js create mode 100644 tests/python/lang/insecure_allow_origin/testdata/main.py diff --git a/rules/python/django/insecure_allow_origin.yml b/rules/python/django/insecure_allow_origin.yml new file mode 100644 index 00000000..2be0d322 --- /dev/null +++ b/rules/python/django/insecure_allow_origin.yml @@ -0,0 +1,66 @@ +imports: + - python_shared_common_user_input + - python_shared_django_http_response + - python_shared_lang_import2 +patterns: + - pattern: $.headers[$] = $<...>$$<...> + filters: + - variable: RESPONSE + detection: python_shared_django_http_response + scope: cursor + - variable: ALLOW_ORIGIN + string_regex: (?i)\Aaccess-control-allow-origin\z + - variable: USER_INPUT + detection: python_shared_common_user_input + scope: result + - pattern: | + $($<...> headers={$<...>$: $<...>$$<...>} $<...>) + filters: + - variable: RESPONSE + detection: python_shared_lang_import2 + scope: cursor + filters: + - variable: MODULE1 + values: [django] + - variable: MODULE2 + values: [http] + - variable: NAME + values: [HttpResponse] + - variable: ALLOW_ORIGIN + string_regex: (?i)\Aaccess-control-allow-origin\z + - variable: USER_INPUT + detection: python_shared_common_user_input + scope: result +languages: + - python +severity: medium +metadata: + description: Unsanitized user input in Access-Control-Allow-Origin + remediation_message: |- + ## Description + + Using unverified user-defined input to set the Access-Control-Allow-Origin header can result in unauthorized access to sensitive data. This vulnerability exposes your application to potential security risks by allowing attackers to specify origins that can access resources. + + ## Remediations + + - **Do not** use user input to define the `Access-Control-Allow-Origin` header without validation. This practice can inadvertently grant access to sensitive information. + ```python + response.headers['Access-Control-Allow-Origin'] = request.GET["my_origin"] # unsafe + ``` + - **Do** validate user input if it must be used to set the `Access-Control-Allow-Origin` header. Ensure that only trusted origins are allowed by implementing a safelist of approved origins. + ```python + allowed_origins = ['http://www.example.com', 'https://www.secure.example.com'] + + user_origin = request.GET["my_origin"] + + if user_origin in allowed_origins: + response.headers['Access-Control-Allow-Origin'] = user_origin + ``` + + ## References + + - [OWASP Origin & Access-Control-Allow-Origin](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/07-Testing_Cross_Origin_Resource_Sharing) + cwe_id: + - 346 + id: python_django_insecure_allow_origin + documentation_url: https://docs.bearer.com/reference/rules/python_django_insecure_allow_origin diff --git a/rules/python/lang/insecure_allow_origin.yml b/rules/python/lang/insecure_allow_origin.yml new file mode 100644 index 00000000..593cdff1 --- /dev/null +++ b/rules/python/lang/insecure_allow_origin.yml @@ -0,0 +1,58 @@ +imports: + - python_shared_common_user_input + - python_shared_lang_instance +patterns: + - pattern: $.send_header($, $<...>$$<...>) + filters: + - variable: ALLOW_ORIGIN + string_regex: (?i)\Aaccess-control-allow-origin\z + - variable: USER_INPUT + detection: python_shared_common_user_input + scope: result + - variable: HANDLER + detection: python_shared_lang_instance + scope: cursor + filters: + - variable: CLASS + detection: python_shared_lang_import2 + scope: cursor + filters: + - variable: MODULE1 + values: [http] + - variable: MODULE2 + values: [server] + - variable: NAME + values: [BaseHTTPRequestHandler] +languages: + - python +severity: medium +metadata: + description: Unsanitized user input in Access-Control-Allow-Origin + remediation_message: |- + ## Description + + Using unverified user-defined input to set the Access-Control-Allow-Origin header can result in unauthorized access to sensitive data. This vulnerability exposes your application to potential security risks by allowing attackers to specify origins that can access resources. + + ## Remediations + + - **Do not** use user input to define the `Access-Control-Allow-Origin` header without validation. This practice can inadvertently grant access to sensitive information. + ```python + self.send_header('Access-Control-Allow-Origin', external_input) # unsafe + ``` + - **Do** validate user input if it must be used to set the `Access-Control-Allow-Origin` header. Ensure that only trusted origins are allowed by implementing a safelist of approved origins. + ```python + allowed_origins = ['http://www.example.com', 'https://www.secure.example.com'] + + user_origin = input("Enter origin: ") + + if user_origin in allowed_origins: + self.send_header('Access-Control-Allow-Origin', user_origin) + ``` + + ## References + + - [OWASP Origin & Access-Control-Allow-Origin](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/07-Testing_Cross_Origin_Resource_Sharing) + cwe_id: + - 346 + id: python_lang_insecure_allow_origin + documentation_url: https://docs.bearer.com/reference/rules/python_lang_insecure_allow_origin diff --git a/tests/python/django/insecure_allow_origin/test.js b/tests/python/django/insecure_allow_origin/test.js new file mode 100644 index 00000000..191e494a --- /dev/null +++ b/tests/python/django/insecure_allow_origin/test.js @@ -0,0 +1,20 @@ +const { + createNewInvoker, + getEnvironment, +} = require("../../../helper.js") +const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) + +describe(ruleId, () => { + const invoke = createNewInvoker(ruleId, ruleFile, testBase) + + test("insecure_allow_origin", () => { + const testCase = "main.py" + + const results = invoke(testCase) + + expect(results).toEqual({ + Missing: [], + Extra: [] + }) + }) +}) \ No newline at end of file diff --git a/tests/python/django/insecure_allow_origin/testdata/main.py b/tests/python/django/insecure_allow_origin/testdata/main.py new file mode 100644 index 00000000..419e600f --- /dev/null +++ b/tests/python/django/insecure_allow_origin/testdata/main.py @@ -0,0 +1,8 @@ +from django.http import HttpResponse + +response = HttpResponse() +# bearer:expected python_django_insecure_allow_origin +response.headers['access-control-allow-origin'] = request.GET["my_origin"] + +# bearer:expected python_django_insecure_allow_origin +HttpResponse(headers={"Access-Control-Allow-Origin": request.GET["my_origin"]}) \ No newline at end of file diff --git a/tests/python/lang/insecure_allow_origin/test.js b/tests/python/lang/insecure_allow_origin/test.js new file mode 100644 index 00000000..191e494a --- /dev/null +++ b/tests/python/lang/insecure_allow_origin/test.js @@ -0,0 +1,20 @@ +const { + createNewInvoker, + getEnvironment, +} = require("../../../helper.js") +const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) + +describe(ruleId, () => { + const invoke = createNewInvoker(ruleId, ruleFile, testBase) + + test("insecure_allow_origin", () => { + const testCase = "main.py" + + const results = invoke(testCase) + + expect(results).toEqual({ + Missing: [], + Extra: [] + }) + }) +}) \ No newline at end of file diff --git a/tests/python/lang/insecure_allow_origin/testdata/main.py b/tests/python/lang/insecure_allow_origin/testdata/main.py new file mode 100644 index 00000000..8ec5a773 --- /dev/null +++ b/tests/python/lang/insecure_allow_origin/testdata/main.py @@ -0,0 +1,9 @@ +from http.server import BaseHTTPRequestHandler + +class SomeClass(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-type', 'application/json') + # bearer:expected python_lang_insecure_allow_origin + self.send_header('Access-Control-Allow-Origin', input()) + self.end_headers()