From 218f303ce00323d264b4c83683e8a2d4322bc28f Mon Sep 17 00:00:00 2001 From: elsapet Date: Thu, 16 May 2024 15:29:09 +0200 Subject: [PATCH] feat(python): add unsafe reflection rule (CWE-470) --- rules/python/lang/code_injection.yml | 5 -- .../lang/reflection_using_user_input.yml | 51 +++++++++++++++++++ .../lang/reflection_using_user_input/test.js | 20 ++++++++ .../testdata/main.py | 27 ++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 rules/python/lang/reflection_using_user_input.yml create mode 100644 tests/python/lang/reflection_using_user_input/test.js create mode 100644 tests/python/lang/reflection_using_user_input/testdata/main.py diff --git a/rules/python/lang/code_injection.yml b/rules/python/lang/code_injection.yml index f3fd7b39..d5e75242 100644 --- a/rules/python/lang/code_injection.yml +++ b/rules/python/lang/code_injection.yml @@ -7,11 +7,6 @@ patterns: - variable: USER_INPUT detection: python_shared_common_user_input scope: result - - pattern: getattr($<_>, $<...>$$<...>) - filters: - - variable: USER_INPUT - detection: python_shared_common_user_input - scope: result - pattern: setattr($<_>, $<_>, $<...>$$<...>) filters: - variable: USER_INPUT diff --git a/rules/python/lang/reflection_using_user_input.yml b/rules/python/lang/reflection_using_user_input.yml new file mode 100644 index 00000000..0783649b --- /dev/null +++ b/rules/python/lang/reflection_using_user_input.yml @@ -0,0 +1,51 @@ +imports: + - python_shared_common_user_input + - python_shared_lang_import1 +patterns: + - pattern: getattr($<_>, $$<...>) + filters: + - variable: USER_INPUT + detection: python_shared_common_user_input + scope: result + - pattern: globals()[$]$<...> + filters: + - variable: USER_INPUT + detection: python_shared_common_user_input + scope: result + - pattern: __import__($) + filters: + - variable: USER_INPUT + detection: python_shared_common_user_input + scope: result + - pattern: $($) + filters: + - variable: IMPORT + detection: python_shared_lang_import1 + scope: cursor + filters: + - variable: MODULE1 + values: [importlib] + - variable: NAME + values: [import_module] + - variable: USER_INPUT + detection: python_shared_common_user_input + scope: result +languages: + - python +severity: high +metadata: + description: Usage of external input in code reflection + remediation_message: | + ## Description + + Using external input for dynamic class loading or code execution through reflection poses a significant security risk. This practice can be exploited by attackers to load harmful classes or execute malicious methods, potentially resulting in remote code execution and other severe security threats. + + ## Remediations + + - **Do not** - wherever possible - use external input with code reflection. Avoiding this practice altogether significantly lowers the risk of executing unauthorized or malicious code. + - **Do** limit the allowed class names and method names to a predefined safelist. This approach restricts the scope of classes and methods that can be dynamically accessed, reducing the risk of unauthorized actions. + - **Do** sanitize external input by removing special and unexpected characters that could facilitate code injection attacks. Characters such as single or double quotation marks and backslashes are particularly risky and should be filtered out. + cwe_id: + - 470 + id: python_lang_reflection_using_user_input + documentation_url: https://docs.bearer.com/reference/rules/python_lang_reflection_using_user_input diff --git a/tests/python/lang/reflection_using_user_input/test.js b/tests/python/lang/reflection_using_user_input/test.js new file mode 100644 index 00000000..8bf0c905 --- /dev/null +++ b/tests/python/lang/reflection_using_user_input/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("unsafe_reflection", () => { + 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/reflection_using_user_input/testdata/main.py b/tests/python/lang/reflection_using_user_input/testdata/main.py new file mode 100644 index 00000000..a0a3295e --- /dev/null +++ b/tests/python/lang/reflection_using_user_input/testdata/main.py @@ -0,0 +1,27 @@ +def bad(num1, num2): + print("Enter the operation: add, subtract, multiply") + operation = input() + + # bearer:expected python_lang_reflection_using_user_input + result = globals()[operation](num1, num2) + +def bad2(): + user_action = input("what hack today? get_username or get_password") + # bearer:expected python_lang_reflection_using_user_input + getattr(current_user, user_action) + +import importlib + +def bad3(): + user_module = input("which bad module to import today?") + # bearer:expected python_lang_reflection_using_user_input + __import__(user_module) + + # bearer:expected python_lang_reflection_using_user_input + importlib.import_module(user_module) + +def ok(num1, num2): + result = globals()["hard_coded_operation"](num1, num2) + getattr(current_user, "known_action") + __import__("safe_module") + importlib.import_module("safe_module") \ No newline at end of file