Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions bandit/plugins/tarfile_unsafe_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
B202: Test for tarfile.extractall
=================================

This plugin will look for usage of ``tarfile.extractall()``
This plugin will look for usage of ``tarfile.extractall()`` and
``tarfile.extract()``

Severity are set as follows:

* ``tarfile.extractall(members=function(tarfile))`` - LOW
* ``tarfile.extractall(members=?)`` - member is not a function - MEDIUM
* ``tarfile.extractall()`` - members from the archive is trusted - HIGH
* ``tarfile.extract()`` - no member validation - HIGH

Use ``tarfile.extractall(members=function_name)`` and define a function
that will inspect each member. Discard files that contain a directory
Expand All @@ -38,6 +40,7 @@
.. seealso::

- https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall
- https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extract
- https://docs.python.org/3/library/tarfile.html#tarfile.TarInfo

.. versionadded:: 1.7.5
Expand All @@ -53,7 +56,7 @@
from bandit.core import test_properties as test


def exec_issue(level, members=""):
def exec_issue_extractall(level, members=""):
if level == bandit.LOW:
return bandit.Issue(
severity=bandit.LOW,
Expand Down Expand Up @@ -83,6 +86,17 @@ def exec_issue(level, members=""):
)


def exec_issue_extract():
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.HIGH,
cwe=issue.Cwe.PATH_TRAVERSAL,
text="tarfile.extract used without member validation. "
"Untarred members should be inspected for directory traversal "
"sequences such as '../' and dangerous file types.",
)
Comment on lines +89 to +97
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback



def get_members_value(context):
for keyword in context.node.keywords:
if keyword.arg == "members":
Expand All @@ -104,18 +118,23 @@ def is_filter_data(context):
@test.test_id("B202")
@test.checks("Call")
def tarfile_unsafe_members(context):
if all(
[
context.is_module_imported_exact("tarfile"),
"extractall" in context.call_function_name,
]
):
if not context.is_module_imported_exact("tarfile"):
return None

if "extractall" in context.call_function_name:
if "filter" in context.call_keywords and is_filter_data(context):
return None
if "members" in context.call_keywords:
members = get_members_value(context)
if "Function" in members:
return exec_issue(bandit.LOW, members)
return exec_issue_extractall(bandit.LOW, members)
else:
return exec_issue(bandit.MEDIUM, members)
return exec_issue(bandit.HIGH)
return exec_issue_extractall(bandit.MEDIUM, members)
return exec_issue_extractall(bandit.HIGH)

if "extract" in context.call_function_name:
if "filter" in context.call_keywords and is_filter_data(context):
return None
return exec_issue_extract()
Comment on lines +124 to +138
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback


return None
18 changes: 17 additions & 1 deletion examples/tarfile_extractall.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ def unsafe_archive_handler(filename):
tar.close()


def unsafe_extract_handler(filename):
target_dir = tempfile.mkdtemp()
tar = tarfile.open(filename)
for member in tar.getmembers():
tar.extract(member, path=target_dir)
tar.close()


def managed_members_archive_handler(filename):
tar = tarfile.open(filename)
tar.extractall(path=tempfile.mkdtemp(), members=members_filter(tar))
Expand All @@ -27,6 +35,14 @@ def filter_fully_trusted_archive_handler(filename):
tar.close()


def filter_data_extract_handler(filename):
target_dir = tempfile.mkdtemp()
tar = tarfile.open(filename)
for member in tar.getmembers():
tar.extract(member, path=target_dir, filter="data")
tar.close()


def list_members_archive_handler(filename):
tar = tarfile.open(filename)
tar.extractall(path=tempfile.mkdtemp(), members=[])
Expand Down Expand Up @@ -58,4 +74,4 @@ def members_filter(tarfile):
unsafe_archive_handler(filename)
managed_members_archive_handler(filename)
filter_data_archive_handler(filename)
filter_fully_trusted_archive_handler(filename)
filter_fully_trusted_archive_handler(filename)
4 changes: 2 additions & 2 deletions tests/functional/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,8 +867,8 @@ def test_snmp_security_check(self):
def test_tarfile_unsafe_members(self):
"""Test insecure usage of tarfile."""
expect = {
"SEVERITY": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 2},
"CONFIDENCE": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 2},
"SEVERITY": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 3},
"CONFIDENCE": {"UNDEFINED": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 3},
}
self.check_example("tarfile_extractall.py", expect)

Expand Down