Skip to content

Commit

Permalink
Add file permission check for pathlib chmod
Browse files Browse the repository at this point in the history
This extends the existing implementation for detecting
bad file permissions to account for calls to pathlib
module functions in addition to those from the os module.

The pathlib chmod and lchmod functions are really just
wrappers around the os module equivalents. However, since
they are class methods, the pre-existing logic in the
code did not consider the corresponding pathlib function calls.

Note that the filename is not easily parsable in the case of pathlib.

Resolves PyCQA#1042
  • Loading branch information
costaparas committed Aug 16, 2023
1 parent c420d1d commit 91b2179
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 31 deletions.
2 changes: 1 addition & 1 deletion bandit/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


def _get_attr_qual_name(node, aliases):
"""Get a the full name for the attribute node.
"""Get the full name for the attribute node.
This will resolve a pseudo-qualified name for the attribute
rooted at node as long as all the deeper nodes are Names or
Expand Down
74 changes: 46 additions & 28 deletions bandit/plugins/general_bad_file_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,28 @@
.. code-block:: none
>> Issue: Probable insecure usage of temp file/directory.
Severity: Medium Confidence: Medium
>> Issue: Chmod setting a permissive mask 0o664 on file (/etc/passwd).
Severity: Medium Confidence: High
CWE: CWE-732 (https://cwe.mitre.org/data/definitions/732.html)
Location: ./examples/os-chmod.py:15
14 os.chmod('/etc/hosts', 0o777)
15 os.chmod('/tmp/oh_hai', 0x1ff)
16 os.chmod('/etc/passwd', stat.S_IRWXU)
Location: ./examples/os-chmod.py:8
7 os.chmod('/etc/passwd', 0o7)
8 os.chmod('/etc/passwd', 0o664)
9 os.chmod('/etc/passwd', 0o777)
>> Issue: Chmod setting a permissive mask 0777 on file (key_file).
>> Issue: Chmod setting a permissive mask 0777 on file (keyfile).
Severity: High Confidence: High
CWE: CWE-732 (https://cwe.mitre.org/data/definitions/732.html)
Location: ./examples/os-chmod.py:17
16 os.chmod('/etc/passwd', stat.S_IRWXU)
17 os.chmod(key_file, 0o777)
18
17 os.chmod(keyfile, 0o777)
18 os.chmod('~/hidden_exec', stat.S_IXGRP)
>> Issue: Chmod setting a permissive mask 0o666 on file (NOT PARSED).
Severity: High Confidence: High
CWE: CWE-732 (https://cwe.mitre.org/data/definitions/732.html)
Location: ./examples/pathlib-chmod.py:5
4 p1 = pathlib.Path(filename)
5 p1.chmod(0o666)
.. seealso::
Expand All @@ -52,6 +59,9 @@
.. versionchanged:: 1.7.5
Added checks for S_IWGRP and S_IXOTH
.. versionchanged:: 1.7.6
Added check for pathlib chmod
""" # noqa: E501
import stat

Expand All @@ -73,27 +83,35 @@ def _stat_is_dangerous(mode):
@test.test_id("B103")
def set_bad_file_permissions(context):
if "chmod" in context.call_function_name:
if context.call_args_count == 2:
if (
context.call_function_name_qual.startswith("os.")
and context.call_args_count == 2
): # os chmod
filename = context.get_call_arg_at_position(0)
mode = context.get_call_arg_at_position(1)
elif context.call_args_count == 1: # pathlib chmod
filename = None
mode = context.get_call_arg_at_position(0)
else:
return

if (
if (
mode is not None
and isinstance(mode, int)
and _stat_is_dangerous(mode)
):
# world writable is an HIGH, group executable is a MEDIUM
if mode & stat.S_IWOTH:
sev_level = bandit.HIGH
else:
sev_level = bandit.MEDIUM

filename = context.get_call_arg_at_position(0)
if filename is None:
filename = "NOT PARSED"
return bandit.Issue(
severity=sev_level,
confidence=bandit.HIGH,
cwe=issue.Cwe.INCORRECT_PERMISSION_ASSIGNMENT,
text="Chmod setting a permissive mask %s on file (%s)."
% (oct(mode), filename),
)
):
# world writable is an HIGH, group executable is a MEDIUM
if mode & stat.S_IWOTH:
sev_level = bandit.HIGH
else:
sev_level = bandit.MEDIUM

if filename is None:
filename = "NOT PARSED"
return bandit.Issue(
severity=sev_level,
confidence=bandit.HIGH,
cwe=issue.Cwe.INCORRECT_PERMISSION_ASSIGNMENT,
text="Chmod setting a permissive mask %s on file (%s)."
% (oct(mode), filename),
)
2 changes: 2 additions & 0 deletions examples/os-chmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
os.chmod(keyfile, 0o777)
os.chmod('~/hidden_exec', stat.S_IXGRP)
os.chmod('~/hidden_exec', stat.S_IXOTH)
os.fchmod(keyfile, 0o777)
os.lchmod('symlink', 0o777)
9 changes: 9 additions & 0 deletions examples/pathlib-chmod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import pathlib

filename = 'foobar'
p1 = pathlib.Path(filename)
p1.chmod(0o666)

symlink = 'link'
p2 = pathlib.Path(symlink)
p2.lchmod(0o777)
12 changes: 10 additions & 2 deletions tests/functional/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,19 @@ def test_subdirectory_okay(self):
}
self.check_example("init-py-test/subdirectory-okay.py", expect)

def test_pathlib_chmod(self):
"""Test setting file permissions."""
expect = {
"SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 2},
"CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 0, "HIGH": 2},
}
self.check_example("pathlib-chmod.py", expect)

def test_os_chmod(self):
"""Test setting file permissions."""
expect = {
"SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 4, "HIGH": 8},
"CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 1, "HIGH": 11},
"SEVERITY": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 4, "HIGH": 10},
"CONFIDENCE": {"UNDEFINED": 0, "LOW": 0, "MEDIUM": 1, "HIGH": 13},
}
self.check_example("os-chmod.py", expect)

Expand Down

0 comments on commit 91b2179

Please sign in to comment.