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
91 changes: 91 additions & 0 deletions bandit/plugins/zipfile_unsafe_extractall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#
# SPDX-License-Identifier: Apache-2.0
#
r"""
======================================
B203: Test for zipfile.extractall
======================================

This plugin detects usage of ``zipfile.ZipFile.extractall()`` without
path validation, which may allow Zip Slip attacks (CWE-22).

An attacker can craft a ZIP archive with entries containing path traversal
sequences (e.g. ``../../etc/cron.d/evil``) that, when extracted without
validation, write files outside the intended destination directory.

Severity is set as follows:

* ``zipfile.extractall()`` with no validation - HIGH
* ``zipfile.extractall(path=?)`` with a variable path but no member check - MEDIUM

Use a manual extraction loop that validates each member path before
extracting:

.. code-block:: python

import zipfile, os

def safe_extract(zf, dest):
dest = os.path.realpath(dest)
for member in zf.namelist():
member_path = os.path.realpath(os.path.join(dest, member))
if not member_path.startswith(dest + os.sep):
raise ValueError("Zip Slip detected: %s" % member)
zf.extract(member, dest)

:Example:

.. code-block:: none

>> Issue: [B203:zipfile_unsafe_extractall] zipfile.extractall used
without path validation. Possible Zip Slip (path traversal) attack.
Severity: High Confidence: High
CWE: CWE-22 (https://cwe.mitre.org/data/definitions/22.html)
Location: examples/zipfile_extractall.py:5

.. seealso::

- https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile.extractall
- https://security.snyk.io/research/zip-slip-vulnerability
- https://cwe.mitre.org/data/definitions/22.html

.. versionadded:: 1.8.0

"""
import bandit
from bandit.core import issue
from bandit.core import test_properties as test


@test.test_id("B203")
@test.checks("Call")
def zipfile_unsafe_extractall(context):
if all(
[
context.is_module_imported_exact("zipfile"),
"extractall" in context.call_function_name,
]
):
if "members" in context.call_keywords:
return bandit.Issue(
severity=bandit.MEDIUM,
confidence=bandit.MEDIUM,
cwe=issue.Cwe.PATH_TRAVERSAL,
text=(
"zipfile.extractall used with a members argument but "
"without verified path validation. Ensure each member "
"path is checked against the destination directory to "
"prevent Zip Slip attacks (CWE-22)."
),
)
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.HIGH,
cwe=issue.Cwe.PATH_TRAVERSAL,
text=(
"zipfile.extractall used without path validation. "
"A crafted ZIP archive may extract files outside the "
"intended directory (Zip Slip, CWE-22). Use a manual "
"extraction loop with path validation instead."
),
)
24 changes: 24 additions & 0 deletions examples/zipfile_extractall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import zipfile
import tempfile
import os

# B203: HIGH — sem validação nenhuma
with zipfile.ZipFile("archive.zip") as zf:
zf.extractall(tempfile.mkdtemp())

# B203: HIGH — path especificado mas sem validar membros
dest = "/tmp/output"
with zipfile.ZipFile("archive.zip") as zf:
zf.extractall(path=dest)

# B203: MEDIUM — members passado mas sem validação de path confirmada
with zipfile.ZipFile("archive.zip") as zf:
zf.extractall(path=dest, members=zf.namelist())

def safe_extract(zf, destination):
destination = os.path.realpath(destination)
for member in zf.namelist():
member_path = os.path.realpath(os.path.join(destination, member))
if not member_path.startswith(destination + os.sep):
raise ValueError("Zip Slip detected: %s" % member)
zf.extract(member, destination)
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ bandit.plugins =

#bandit/plugins/tarfile_unsafe_members.py
tarfile_unsafe_members = bandit.plugins.tarfile_unsafe_members:tarfile_unsafe_members
zipfile_unsafe_extractall = bandit.plugins.zipfile_unsafe_extractall:zipfile_unsafe_extractall

#bandit/plugins/pytorch_load.py
pytorch_load = bandit.plugins.pytorch_load:pytorch_load
Expand Down