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
86 changes: 86 additions & 0 deletions python/lang/security/audit/tarfile-unsafe-extraction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import tarfile
import zipfile


def unsafe_context_manager(archive_path, dest):
with tarfile.open(archive_path) as tar:
# ruleid: tarfile-unsafe-extraction
tar.extractall(dest)


def unsafe_assigned(archive_path, dest):
tar = tarfile.open(archive_path)
# ruleid: tarfile-unsafe-extraction
tar.extractall(path=dest)
tar.close()


def unsafe_chained(archive_path, dest):
# ruleid: tarfile-unsafe-extraction
tarfile.open(archive_path).extractall(dest)


def unsafe_single_member(archive_path, dest, member):
with tarfile.open(archive_path) as tar:
# ruleid: tarfile-unsafe-extraction
tar.extract(member, dest)


def unsafe_downloaded_cache(url, dest):
# models CVE-2026-43637: extracting an untrusted downloaded archive
local = download(url)
with tarfile.open(local) as tar:
# ruleid: tarfile-unsafe-extraction
tar.extractall(dest)


# ----- safe: extraction filter present (Python 3.12+) -----

def safe_data_filter(archive_path, dest):
with tarfile.open(archive_path) as tar:
# ok: tarfile-unsafe-extraction
tar.extractall(dest, filter="data")


def safe_data_filter_symbol(archive_path, dest):
with tarfile.open(archive_path) as tar:
# ok: tarfile-unsafe-extraction
tar.extractall(path=dest, filter=tarfile.data_filter)


def safe_member_filter(archive_path, dest, member):
with tarfile.open(archive_path) as tar:
# ok: tarfile-unsafe-extraction
tar.extract(member, dest, filter="tar")


def safe_chained_filter(archive_path, dest):
# ok: tarfile-unsafe-extraction
tarfile.open(archive_path).extractall(dest, filter="data")


# ----- safe: not a tarfile object (zipfile sanitizes '..' since 2.7.4) -----

def zip_extract(archive_path, dest):
with zipfile.ZipFile(archive_path) as zf:
# ok: tarfile-unsafe-extraction
zf.extractall(dest)


# ----- typed helper parameter (archive opened by the caller) -----

def unsafe_typed_helper(tar: tarfile.TarFile, dest):
# ruleid: tarfile-unsafe-extraction
tar.extractall(dest)


from tarfile import TarFile

def unsafe_typed_helper_bare(tar: TarFile, dest, member):
# ruleid: tarfile-unsafe-extraction
tar.extract(member, dest)


def safe_typed_helper(tar: tarfile.TarFile, dest):
# ok: tarfile-unsafe-extraction
tar.extractall(dest, filter="data")
68 changes: 68 additions & 0 deletions python/lang/security/audit/tarfile-unsafe-extraction.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
rules:
- id: tarfile-unsafe-extraction
patterns:
- pattern-either:
- patterns:
- pattern-inside: |
with tarfile.open(...) as $TAR:
...
- pattern-either:
- pattern: $TAR.extractall(...)
- pattern: $TAR.extract(...)
- patterns:
- pattern-inside: |
$TAR = tarfile.open(...)
...
- pattern-either:
- pattern: $TAR.extractall(...)
- pattern: $TAR.extract(...)
- pattern: tarfile.open(...).extractall(...)
- pattern: tarfile.open(...).extract(...)
- patterns:
- pattern-inside: |
def $FUNC(..., $TAR: tarfile.TarFile, ...):
...
- pattern-either:
- pattern: $TAR.extractall(...)
- pattern: $TAR.extract(...)
- patterns:
- pattern-inside: |
def $FUNC(..., $TAR: TarFile, ...):
...
- pattern-either:
- pattern: $TAR.extractall(...)
- pattern: $TAR.extract(...)
- pattern-not: $ANY.extractall(..., filter=$F, ...)
- pattern-not: $ANY.extract(..., filter=$F, ...)
- pattern-not: tarfile.open(...).extractall(..., filter=$F, ...)
- pattern-not: tarfile.open(...).extract(..., filter=$F, ...)
message: >-
Extracting a tar archive with `extractall()`/`extract()` and no `filter=`
argument does not contain extracted paths. A crafted archive can use
`../` sequences, absolute paths, or symlink/hardlink members to write
files outside the destination directory (CVE-2007-4559 "tarslip" class;
e.g. CVE-2026-43637). If the archive is attacker-influenced (downloaded,
uploaded, or fetched from a cache), this is arbitrary file write and can
lead to code execution. On Python 3.12+ pass `filter='data'`
(or `filter=tarfile.data_filter`); otherwise validate every member so its
resolved path stays within the destination and reject absolute paths,
`..`, and links before extracting.
metadata:
owasp:
- A01:2021 - Broken Access Control
- A05:2021 - Security Misconfiguration
cwe:
- "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')"
category: security
technology:
- python
references:
- https://docs.python.org/3/library/tarfile.html#extraction-filters
- https://nvd.nist.gov/vuln/detail/CVE-2007-4559
subcategory:
- audit
likelihood: MEDIUM
impact: HIGH
confidence: MEDIUM
languages: [python]
severity: WARNING