Skip to content

Commit

Permalink
feat(python): add unsafe reflection rule (CWE-470) (#407)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet authored May 22, 2024
1 parent b22ebc8 commit 38cf721
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 5 deletions.
5 changes: 0 additions & 5 deletions rules/python/lang/code_injection.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ patterns:
- variable: USER_INPUT
detection: python_shared_common_user_input
scope: result
- pattern: getattr($<_>, $<...>$<USER_INPUT>$<...>)
filters:
- variable: USER_INPUT
detection: python_shared_common_user_input
scope: result
- pattern: setattr($<_>, $<_>, $<...>$<USER_INPUT>$<...>)
filters:
- variable: USER_INPUT
Expand Down
58 changes: 58 additions & 0 deletions rules/python/lang/reflection_using_user_input.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
imports:
- python_shared_common_user_input
- python_shared_lang_import1
patterns:
- pattern: getattr($<_>, $<USER_INPUT>$<...>)
filters:
- variable: USER_INPUT
detection: python_shared_common_user_input
scope: result
- pattern: $<GLOBALS>[$<USER_INPUT>]$<...>
filters:
- variable: GLOBALS
detection: python_lang_reflection_using_user_input_globals
scope: result
- variable: USER_INPUT
detection: python_shared_common_user_input
scope: result
- pattern: __import__($<USER_INPUT>)
filters:
- variable: USER_INPUT
detection: python_shared_common_user_input
scope: result
- pattern: $<IMPORT>($<USER_INPUT>)
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
auxiliary:
- id: python_lang_reflection_using_user_input_globals
patterns:
- globals()
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
20 changes: 20 additions & 0 deletions tests/python/lang/reflection_using_user_input/test.js
Original file line number Diff line number Diff line change
@@ -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: []
})
})
})
31 changes: 31 additions & 0 deletions tests/python/lang/reflection_using_user_input/testdata/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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)

my_globals = globals()
# bearer:expected python_lang_reflection_using_user_input
my_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")

0 comments on commit 38cf721

Please sign in to comment.