Skip to content

Commit

Permalink
feat(python): os command injection (CWE-78) (#398)
Browse files Browse the repository at this point in the history
  • Loading branch information
elsapet authored May 16, 2024
1 parent 083f8e5 commit 31e12b2
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
141 changes: 141 additions & 0 deletions rules/python/lang/os_command_injection.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
imports:
- python_shared_common_external_input
- python_shared_lang_import1
patterns:
- pattern: $<OS>($<...>$<EXTERNAL_INPUT>$<...>)
filters:
- variable: OS
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [os]
- variable: NAME
values:
- system
- popen
- popen2
- popen3
- popen4
- variable: EXTERNAL_INPUT
detection: python_shared_common_external_input
scope: result
- pattern: getattr($<OS>, "system")($<...>$<EXTERNAL_INPUT>$<...>)
filters:
- variable: OS
detection: python_lang_os_command_injection_external_os_module
- variable: EXTERNAL_INPUT
detection: python_shared_common_external_input
scope: result
- pattern: $<SUBPROCESS>($<EXTERNAL_INPUT>$<...>)
filters:
- variable: SUBPROCESS
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [subprocess]
- variable: NAME
values:
- call
- check_call
- check_output
- run
- Popen
- variable: EXTERNAL_INPUT
detection: python_shared_common_external_input
scope: result
- pattern: $<OS>($<_>, $<...>$<EXTERNAL_INPUT>$<...>, $<...>)
filters:
- variable: OS
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [os]
- variable: NAME
values:
- spawnl
- spawnle
- spawnlp
- spawnlpe
- spawnv
- spawnve
- spawnvp
- spawnvpe
- posix_spawn
- posix_spawnp
- startfile
- variable: EXTERNAL_INPUT
detection: python_shared_common_external_input
scope: result
- pattern: $<OS>($<_>, $<BASH>, ["-c", $<...>$<EXTERNAL_INPUT>$<...>], $<...>)
filters:
- variable: OS
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [os]
- variable: NAME
values:
- spawnv
- spawnve
- spawnvp
- spawnvp
- spawnvpe
- posix_spawn
- posix_spawnp
- variable: BASH
regex: (.*)(sh|bash|ksh|csh|tcsh|zsh)
- variable: EXTERNAL_INPUT
detection: python_shared_common_external_input
scope: result
- pattern: $<OS>.$<METHOD>($<_>, $<BASH>, "-c", $<...>$<EXTERNAL_INPUT>$<...>, $<...>)
filters:
- variable: OS
detection: python_shared_lang_import1
scope: cursor
filters:
- variable: MODULE1
values: [os]
- variable: NAME
values:
- spawnl
- spawnle
- spawnlp
- spawnlpe
- variable: BASH
regex: (.*)(sh|bash|ksh|csh|tcsh|zsh)
- variable: EXTERNAL_INPUT
detection: python_shared_common_external_input
scope: result
auxiliary:
- id: python_lang_os_command_injection_external_os_module
patterns:
- os
- __import__("os")
- import $<!>os
- import os as $<!>$<_>
languages:
- python
severity: critical
metadata:
description: Unsanitized user input in OS command
remediation_message: |-
## Description
Directly incorporating external or user-defined input into an OS command exposes the system to possible command injection attacks. This vulnerability allows attackers to execute unauthorized commands on the operating system, potentially leading to a compromise of system integrity.
## Remediations
- **Do not** use OS commands that include dynamic input directly. Instead, explore safer alternatives such as libraries or built-in functions that achieve the same goal without executing system commands.
- **Do** use hardcoded values for any input that is incorporated into OS commands. This approach minimizes the risk by ensuring only predefined inputs are used, thus preventing attackers from injecting malicious commands. Use safe lists or dictionaries if you need to be dynamic.
## References
- [OWASP command injection explained](https://owasp.org/www-community/attacks/Command_Injection)
cwe_id:
- 78
id: python_lang_os_command_injection
documentation_url: https://docs.bearer.com/reference/rules/python_lang_os_command_injection
20 changes: 20 additions & 0 deletions tests/python/lang/os_command_injection/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("os_command_injection", () => {
const testCase = "main.py"

const results = invoke(testCase)

expect(results).toEqual({
Missing: [],
Extra: []
})
})
})
31 changes: 31 additions & 0 deletions tests/python/lang/os_command_injection/testdata/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Use bearer:expected python_lang_os_command_injection to flag expected findings

my_os = __import__("os")
# bearer:expected python_lang_os_command_injection
getattr(my_os, "system")(input())

import os
import subprocess as subproc
def bad():
unsafe = input("what hack today?")
# bearer:expected python_lang_os_command_injection
subproc.run([unsafe, "exit 0"], shell=True, capture_output=True)
# bearer:expected python_lang_os_command_injection
os.system(unsafe)

# bearer:expected python_lang_os_command_injection
subproc.check_output(
unsafe,
stderr=subprocess.STDOUT,
shell=True)

def bad2():
unsafe = sys.argv[1]
# bearer:expected python_lang_os_command_injection
os.spawnlp(os.P_WAIT, unsafe)
# bearer:expected python_lang_os_command_injection
os.spawnve(os.P_WAIT, "/bin/bash", ["-c", unsafe], os.environ)

def ok():
os.spawnve(os.P_WAIT, "/bin/ls", ["-a"], os.environ)
subproc.run(["dir"], shell=False)

0 comments on commit 31e12b2

Please sign in to comment.