GHSA-MX42-J6WV-PX98
Vulnerability from github – Published: 2026-04-08 00:15 – Updated: 2026-04-10 21:33RustFS 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/finance_dump.bin). - Attacker tenant/user has no permissions on the victim bucket:
- cannot
ListObjects,HeadObject,GetObject, orCopyObjectfrom the victim bucket. - Attacker has minimal permissions only on attacker bucket:
CreateMultipartUpload,UploadPart,UploadPartCopy,CompleteMultipartUpload,AbortMultipartUpload,- and
PutObject/GetObjectfor 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
- abort_multipart_upload() returns Ok(()) without authorization (L435–437)
- complete_multipart_upload() returns Ok(()) without authorization (L442–444)
- upload_part_copy() returns Ok(()) without authorization (L1446–1448)
In contrast, copy_object() correctly enforces authorization:
- source GetObject authorization (L469)
- destination PutObject authorization (L478)
The multipart copy implementation reads the source object directly:
File: rustfs/src/app/multipart_usecase.rs
- store.get_object_reader(&src_bucket, &src_key, ...) (L959–962)
Because upload_part_copy() 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)
rustfscrate 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:
poc_uploadpartcopy_exfil_v3.sh - Captured output:
poc_v3_output.txt - (Optional) Redacted debug log:
upload_part_copy_debug_redacted.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):
docker compose -f docker-compose-simple.yml up -d
````
2. Run the PoC and save output:
```bash
chmod +x poc_uploadpartcopy_exfil_v3.sh
./poc_uploadpartcopy_exfil_v3.sh | tee poc_v3_output.txt
Attachments
Expected Behavior
-
Attacker operations against victim bucket should be denied:
-
ListObjects-> AccessDenied HeadObject-> AccessDeniedGetObject-> AccessDeniedCopyObject-> AccessDeniedUploadPartCopyfrom victim -> attacker multipart should also be denied.
Actual Behavior
- All direct operations against victim are denied (as expected),
- but
UploadPartCopysucceeds, and attacker retrieves the copied object from attacker bucket.
Observed PoC Output
Victim uploads a private object:
- size:
5,242,880bytes - sha256:
fda018db1da9d8f4c1b287c75943384a3b4ede391ec156039b6d94e17d6ad68f
Attacker exfiltrates it via multipart copy:
- stolen size:
5,242,880bytes - 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/finance_dump.bin - response:
HTTP/1.1 200with<CopyPartResult><ETag>...</ETag>...</CopyPartResult>
Fix
Implement authorization checks equivalent to copy_object() for multipart copy paths:
-
upload_part_copy: -
enforce source
GetObjectauthorization onx-amz-copy-source - enforce destination
PutObjectauthorization on the target object -
(recommended) apply the same tag-condition enforcement used by
copy_object()on the source. -
complete_multipart_upload: -
enforce destination
PutObjectauthorization -
abort_multipart_upload: -
enforce appropriate multipart permission (or destination
PutObjectas a safe boundary)
{
"affected": [
{
"package": {
"ecosystem": "crates.io",
"name": "rustfs"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "0.0.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-39360"
],
"database_specific": {
"cwe_ids": [
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-08T00:15:50Z",
"nvd_published_at": "2026-04-07T19:16:46Z",
"severity": "MODERATE"
},
"details": "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.\n\nThis breaks tenant isolation in multi-user / multi-tenant deployments.\n\n## Impact\n**Unauthorized cross-bucket / cross-tenant data exfiltration (Confidentiality: High).**\n\nAn 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.\n\nIn the attached PoC, the attacker successfully exfiltrates a 5MB private object and proves integrity via matching SHA256 and size.\n\n## Threat Model (Realistic)\n- **Victim tenant/user** owns a bucket (e.g., `victim-bucket-*`) and stores private objects (e.g., `private/finance_dump.bin`).\n- **Attacker tenant/user** has **no permissions** on the victim bucket:\n - cannot `ListObjects`, `HeadObject`, `GetObject`, or `CopyObject` from the victim bucket.\n- Attacker has **minimal permissions only on attacker bucket**:\n - `CreateMultipartUpload`, `UploadPart`, `UploadPartCopy`, `CompleteMultipartUpload`, `AbortMultipartUpload`,\n - and `PutObject`/`GetObject` for objects in attacker bucket.\n- Despite this, attacker can exfiltrate victim objects via multipart copy.\n\n## Root Cause Analysis\nThe access control layer fails open for multipart copy-related operations:\n\nFile: `rustfs/src/storage/access.rs`\n- `abort_multipart_upload()` returns `Ok(())` without authorization (L435\u2013437)\n- `complete_multipart_upload()` returns `Ok(())` without authorization (L442\u2013444)\n- `upload_part_copy()` returns `Ok(())` without authorization (L1446\u20131448)\n\nIn contrast, `copy_object()` correctly enforces authorization:\n- source `GetObject` authorization (L469)\n- destination `PutObject` authorization (L478)\n\nThe multipart copy implementation reads the source object directly:\n\nFile: `rustfs/src/app/multipart_usecase.rs`\n- `store.get_object_reader(\u0026src_bucket, \u0026src_key, ...)` (L959\u2013962)\n\nBecause `upload_part_copy()` does not enforce source `GetObject` authorization, the server reads and copies victim data even when the requester lacks permission.\n\n## Affected Versions\n- **Tested vulnerable on:** `main` @ `c1d5106acc3480c275a52344df84633bb6dcd8f0`\n- **Git describe:** `1.0.0-alpha.86-3-gc1d5106a`\n\nThe fail-open authorization behavior for `UploadPartCopy` was introduced in:\n- **Commit:** `09ea11c13` (per `git blame` on `rustfs/src/storage/access.rs:1443-1448`)\n\n**Affected range (recommended wording):**\n- All versions **from** commit `09ea11c13` **through** `c1d5106acc3480c275a52344df84633bb6dcd8f0` (and likely any releases containing those commits) until a fix is applied.\n\n### Package version (Cargo metadata)\n- `rustfs` crate version in this tree: **0.0.5** (`cargo metadata`)\n\n## Proof of Concept (PoC) \u2013 Real Commands + Verified Results\n\n### Files\nPlace the PoC script at the repository root:\n\n- **PoC script:** [`poc_uploadpartcopy_exfil_v3.sh`](https://github.com/user-attachments/files/26006935/poc_uploadpartcopy_exfil_v3.sh)\n- **Captured output:** [`poc_v3_output.txt`](https://github.com/user-attachments/files/26006938/poc_v3_output.txt)\n- *(Optional)* **Redacted debug log:** `upload_part_copy_debug_redacted.log` (Authorization/signature redacted)\n\n### Environment \nRustFS running locally (Docker is simplest), listening on:\n\n- `http://127.0.0.1:9000`\n\nTools:\n- `awscli`, `jq`, `awscurl`\n\n### Steps to Reproduce\n1) Start RustFS (example):\n\n```bash\ndocker compose -f docker-compose-simple.yml up -d\n````\n\n2. Run the PoC and save output:\n\n```bash\nchmod +x poc_uploadpartcopy_exfil_v3.sh\n./poc_uploadpartcopy_exfil_v3.sh | tee poc_v3_output.txt\n```\n\n### Attachments\n\n* [`poc_uploadpartcopy_exfil_v3.sh`](https://github.com/user-attachments/files/26006950/poc_uploadpartcopy_exfil_v3.sh)\n* [`poc_v3_output.txt`](https://github.com/user-attachments/files/26006953/poc_v3_output.txt)\n\n\n### Expected Behavior\n\n* Attacker operations against victim bucket should be denied:\n\n * `ListObjects` -\u003e AccessDenied\n * `HeadObject` -\u003e AccessDenied\n * `GetObject` -\u003e AccessDenied\n * `CopyObject` -\u003e AccessDenied\n* `UploadPartCopy` from victim -\u003e attacker multipart should also be denied.\n\n### Actual Behavior\n\n* All direct operations against victim are denied (as expected),\n* but **`UploadPartCopy` succeeds**, and attacker retrieves the copied object from attacker bucket.\n\n### Observed PoC Output \n\nVictim uploads a private object:\n\n* size: `5,242,880` bytes\n* sha256: `fda018db1da9d8f4c1b287c75943384a3b4ede391ec156039b6d94e17d6ad68f`\n\nAttacker exfiltrates it via multipart copy:\n\n* stolen size: `5,242,880` bytes\n* stolen sha256: `fda018db1da9d8f4c1b287c75943384a3b4ede391ec156039b6d94e17d6ad68f`\n\nProof:\n\n* hashes and sizes match (victim == stolen) -\u003e unauthorized cross-bucket read confirmed.\n\n## Network Evidence (Redacted)\n\nThe debug log shows a successful request with:\n\n* HTTP method: `PUT`\n* destination: `/\u003cattacker-bucket\u003e/\u003cdst-key\u003e?partNumber=1\u0026uploadId=...`\n* header: `x-amz-copy-source: \u003cvictim-bucket\u003e/private/finance_dump.bin`\n* response: `HTTP/1.1 200` with `\u003cCopyPartResult\u003e\u003cETag\u003e...\u003c/ETag\u003e...\u003c/CopyPartResult\u003e`\n\n\n## Fix\n\nImplement authorization checks equivalent to `copy_object()` for multipart copy paths:\n\n* `upload_part_copy`:\n\n * enforce **source** `GetObject` authorization on `x-amz-copy-source`\n * enforce **destination** `PutObject` authorization on the target object\n * (recommended) apply the same tag-condition enforcement used by `copy_object()` on the source.\n\n* `complete_multipart_upload`:\n\n * enforce destination `PutObject` authorization\n\n* `abort_multipart_upload`:\n\n * enforce appropriate multipart permission (or destination `PutObject` as a safe boundary)",
"id": "GHSA-mx42-j6wv-px98",
"modified": "2026-04-10T21:33:31Z",
"published": "2026-04-08T00:15:50Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/rustfs/rustfs/security/advisories/GHSA-mx42-j6wv-px98"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-39360"
},
{
"type": "PACKAGE",
"url": "https://github.com/rustfs/rustfs"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
},
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "RustFS has an authorization bypass in multipart UploadPartCopy enables cross-bucket object exfiltration"
}
Sightings
| Author | Source | Type | Date |
|---|
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.