From 2a3423d826f6453a28dc368f865632cacf02756a Mon Sep 17 00:00:00 2001 From: tes Date: Tue, 12 May 2026 12:17:48 -0300 Subject: [PATCH] Merge commit from fork file_id values from backup manifests are device controlled and were used directly to construct host filesystem paths without validation, allowing a malicious backup to read or write files outside the designated directories. Validate each file_id with Path.resolve() + is_relative_to() before use as a source or destination path. Unsafe entries are skipped and logged. Fixes GHSA-5h3g-px23-w6vw --- src/mvt/ios/decrypt.py | 7 +++++++ src/mvt/ios/modules/base.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/src/mvt/ios/decrypt.py b/src/mvt/ios/decrypt.py index 620ee7e..ceac3e2 100644 --- a/src/mvt/ios/decrypt.py +++ b/src/mvt/ios/decrypt.py @@ -11,6 +11,7 @@ import os import os.path import shutil import sqlite3 +from pathlib import Path from typing import Optional from iOSbackup import iOSbackup @@ -96,6 +97,9 @@ class DecryptBackup: # This may be a partial backup. Skip files from the manifest # which do not exist locally. source_file_path = os.path.join(self.backup_path, file_id[0:2], file_id) + if not Path(source_file_path).resolve().is_relative_to(Path(self.backup_path).resolve()): + log.warning("Skipping unsafe file_id: %r", file_id) + continue if not os.path.exists(source_file_path): log.debug( "Skipping file %s. File not found in encrypted backup directory.", @@ -104,6 +108,9 @@ class DecryptBackup: continue item_folder = os.path.join(self.dest_path, file_id[0:2]) # type: ignore[arg-type] + if not Path(os.path.join(item_folder, file_id)).resolve().is_relative_to(Path(self.dest_path).resolve()): + log.warning("Skipping unsafe file_id: %r", file_id) + continue if not os.path.exists(item_folder): os.makedirs(item_folder) diff --git a/src/mvt/ios/modules/base.py b/src/mvt/ios/modules/base.py index 3ff5509..58ea22b 100644 --- a/src/mvt/ios/modules/base.py +++ b/src/mvt/ios/modules/base.py @@ -9,6 +9,7 @@ import os import shutil import sqlite3 import subprocess +from pathlib import Path from typing import Iterator, Optional, Union from mvt.common.module import ( @@ -165,6 +166,8 @@ class IOSExtraction(MVTModule): if not self.target_path: return None file_path = os.path.join(self.target_path, file_id[0:2], file_id) + if not Path(file_path).resolve().is_relative_to(Path(self.target_path).resolve()): + return None if os.path.exists(file_path): return file_path