From 049e52d6f0472ae4ef0165b850a2b7d1fe2ec5e8 Mon Sep 17 00:00:00 2001 From: elsapet Date: Thu, 30 May 2024 09:21:12 +0200 Subject: [PATCH] feat(python): add permissive cross domain policy rule (CWE-942) (#427) --- .../python/django/permissive_allow_origin.yml | 58 +++++++++++++++++++ rules/python/lang/permissive_allow_origin.yml | 52 +++++++++++++++++ .../django/permissive_allow_origin/test.js | 20 +++++++ .../permissive_allow_origin/testdata/main.py | 16 +++++ .../lang/permissive_allow_origin/test.js | 20 +++++++ .../permissive_allow_origin/testdata/main.py | 16 +++++ 6 files changed, 182 insertions(+) create mode 100644 rules/python/django/permissive_allow_origin.yml create mode 100644 rules/python/lang/permissive_allow_origin.yml create mode 100644 tests/python/django/permissive_allow_origin/test.js create mode 100644 tests/python/django/permissive_allow_origin/testdata/main.py create mode 100644 tests/python/lang/permissive_allow_origin/test.js create mode 100644 tests/python/lang/permissive_allow_origin/testdata/main.py diff --git a/rules/python/django/permissive_allow_origin.yml b/rules/python/django/permissive_allow_origin.yml new file mode 100644 index 00000000..c7f5b9bb --- /dev/null +++ b/rules/python/django/permissive_allow_origin.yml @@ -0,0 +1,58 @@ +imports: + - 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: VALUE + string_regex: \A\*\z + - 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: VALUE + string_regex: \A\*\z +languages: + - python +severity: medium +metadata: + description: Permissive Access-Control-Allow-Origin configuration + remediation_message: |- + ## Description + + A permissive Access-Control-Allow-Origin configuration can expose your application to security risks. When this header is set to "*", it means your application's responses can be accessed by any website, potentially leading to unauthorized access to sensitive information. + + ## Remediations + + - **Do not** set the Access-Control-Allow-Origin header to "*". This overly permissive setting can make your application vulnerable to attacks. + ```python + response.headers['Access-Control-Allow-Origin'] = '*' # unsafe + ``` + - **Do** restrict the Access-Control-Allow-Origin header to only allow specific, trusted origins that need access to your application. This minimizes the risk of sensitive data exposure. + ```python + response.headers['Access-Control-Allow-Origin'] = 'myapp.example.com' + ``` + + ## 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: + - 942 + id: python_django_permissive_allow_origin + documentation_url: https://docs.bearer.com/reference/rules/python_django_permissive_allow_origin diff --git a/rules/python/lang/permissive_allow_origin.yml b/rules/python/lang/permissive_allow_origin.yml new file mode 100644 index 00000000..3c4fd639 --- /dev/null +++ b/rules/python/lang/permissive_allow_origin.yml @@ -0,0 +1,52 @@ +imports: + - python_shared_lang_instance + - python_shared_lang_import2 +patterns: + - pattern: $.send_header($, $<...>$$<...>) + filters: + - variable: ALLOW_ORIGIN + string_regex: (?i)\Aaccess-control-allow-origin\z + - variable: VALUE + string_regex: \A\*\z + - 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: Permissive Access-Control-Allow-Origin configuration + remediation_message: |- + ## Description + + A permissive Access-Control-Allow-Origin configuration can expose your application to security risks. When this header is set to "*", it means your application's responses can be accessed by any website, potentially leading to unauthorized access to sensitive information. + + ## Remediations + + - **Do not** set the Access-Control-Allow-Origin header to "*". This overly permissive setting can make your application vulnerable to attacks. + ```python + self.send_header('Access-Control-Allow-Origin', '*') # unsafe + ``` + - **Do** restrict the Access-Control-Allow-Origin header to only allow specific, trusted origins that need access to your application. This minimizes the risk of sensitive data exposure. + ```python + self.send_header('Access-Control-Allow-Origin', 'myapp.example.com'); + ``` + + ## 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: + - 942 + id: python_lang_permissive_allow_origin + documentation_url: https://docs.bearer.com/reference/rules/python_lang_permissive_allow_origin diff --git a/tests/python/django/permissive_allow_origin/test.js b/tests/python/django/permissive_allow_origin/test.js new file mode 100644 index 00000000..0ab81e4b --- /dev/null +++ b/tests/python/django/permissive_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("permissive_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/permissive_allow_origin/testdata/main.py b/tests/python/django/permissive_allow_origin/testdata/main.py new file mode 100644 index 00000000..848ffd63 --- /dev/null +++ b/tests/python/django/permissive_allow_origin/testdata/main.py @@ -0,0 +1,16 @@ +# Use bearer:expected python_django_permissive_allow_origin to flag expected findings + +from django.http import HttpResponse + +response = HttpResponse() +# bearer:expected python_django_permissive_allow_origin +response.headers['access-control-allow-origin'] = '*' + +# bearer:expected python_django_permissive_allow_origin +HttpResponse(headers={"Access-Control-Allow-Origin": "*"}) + +# ok +response = HttpResponse() +response.headers['access-control-allow-origin'] = 'https://my-example-site.com' + +HttpResponse(headers={"Access-Control-Allow-Origin": "https://my-example-site.com" \ No newline at end of file diff --git a/tests/python/lang/permissive_allow_origin/test.js b/tests/python/lang/permissive_allow_origin/test.js new file mode 100644 index 00000000..0ab81e4b --- /dev/null +++ b/tests/python/lang/permissive_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("permissive_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/permissive_allow_origin/testdata/main.py b/tests/python/lang/permissive_allow_origin/testdata/main.py new file mode 100644 index 00000000..7c63ffa5 --- /dev/null +++ b/tests/python/lang/permissive_allow_origin/testdata/main.py @@ -0,0 +1,16 @@ +from http.server import BaseHTTPRequestHandler + +class BadClass(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-type', 'application/json') + # bearer:expected python_lang_permissive_allow_origin + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + +class OkClass(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_header('Access-Control-Allow-Origin', 'https://my-example-site.com') + self.end_headers() \ No newline at end of file