fix(storage/artifacts): block path traversal via artifact_path#3407
Open
Sp1kyss wants to merge 1 commit into
Open
fix(storage/artifacts): block path traversal via artifact_path#3407Sp1kyss wants to merge 1 commit into
Sp1kyss wants to merge 1 commit into
Conversation
FilesystemArtifactStorage and S3ArtifactStorage joined attacker-
controlled artifact_path values to a fixed prefix using `pathlib.Path /
artifact_path`. Two pathlib quirks made that join unsafe:
* `pathlib.Path('/a') / '/etc/passwd'` returns `/etc/passwd` because
the `/` operator silently discards the prefix when the right operand
is absolute.
* `pathlib.Path('/a') / '../etc/passwd'` returns `/a/../etc/passwd`,
which the OS resolves to `/etc/passwd` once the value is passed to
shutil.copy / shutil.rmtree / boto3.
Combined with the SDK's `Run.log_artifact(path, name=...)` API, where
`name` is fully attacker-controlled and propagated to
`upload_artifact(..., artifact_path=name)`, this allowed:
* arbitrary file overwrite (upload destination outside the artifact
root, e.g. /etc/cron.d/, /var/www/...)
* arbitrary directory deletion via delete_artifact -> shutil.rmtree
* arbitrary S3 key write/delete outside the configured prefix
This is a distinct code path from CVE-2024-6396 (`_backup_run`),
CVE-2024-8769 (`LockManager.release_locks`), and the previously-reported
restore_run_backup tar extraction issue.
Fix:
* Add `safe_join` (filesystem) and `safe_join_posix` (S3 keys) helpers
in artifact_storage.py that reject absolute paths, drive letters,
and any `..` segments, and re-validate the resolved filesystem path
stays inside the prefix.
* Switch FilesystemArtifactStorage and S3ArtifactStorage to use them
for upload, download, and delete.
* Add regression tests covering absolute paths, embedded `..`,
delete safety, and the legitimate relative-path case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Both
FilesystemArtifactStorageandS3ArtifactStoragejoined attacker-controlledartifact_pathvalues to a fixed prefix usingpathlib.Path / artifact_path. Two pathlib quirks made that join unsafe:pathlib.Path('/a') / '/etc/passwd'returns/etc/passwd(the/operator silently discards the prefix when the right operand is absolute).pathlib.Path('/a') / '../etc/passwd'returns/a/../etc/passwd, which the OS resolves to/etc/passwdonce the value is passed toshutil.copy/shutil.rmtree/ boto3.The SDK exposes
Run.log_artifact(path, name=...), wherenameis propagated unchanged toupload_artifact(..., artifact_path=name), so a malicious caller can pick the destination filename and escape the configured artifact root.Impact
upload_artifact->shutil.copy(e.g. dropping a file into/etc/cron.d/,/var/www/html/, or any path the Aim process can write to).delete_artifact->shutil.rmtree.This is a distinct code path from previously-reported issues:
_backup_runpath traversal.LockManager.release_lockstraversal.restore_run_backuptar extraction issue.Fix
safe_join(filesystem) andsafe_join_posix(S3 keys) helpers inaim/storage/artifacts/artifact_storage.py. Both reject absolute paths, drive letters, and any..segments.safe_joinadditionally re-validates that the resolved filesystem path stays inside the prefix.FilesystemArtifactStorageandS3ArtifactStorageto use the helpers for upload, download, and delete..., delete safety, and a legitimate relative-path case.Reproducer (before this PR)
`python
from aim.storage.artifacts.filesystem_storage import FilesystemArtifactStorage
storage = FilesystemArtifactStorage('file:///tmp/artifacts')
Writes attacker-controlled content to /etc/cron.d/aim_pwn:
storage.upload_artifact('/path/to/payload', '/etc/cron.d/aim_pwn')
Or via the SDK:
run.log_artifact('/path/to/payload', name='/etc/cron.d/aim_pwn')
`
After this PR the same call raises
ValueErrorbefore any I/O happens.