CVE-2026-97731 Overview
CVE-2026-97731 affects MinIO through commit 7aac2a2 and pgsty/silo before commit 1233254. The server fails to verify that every x-amz-* header present on a request also appears in the client-supplied X-Amz-SignedHeaders list. An attacker holding a presigned PUT URL scoped to one object can append an x-amz-copy-source header to that unmodified URL. MinIO then dispatches a server-side CopyObject executed under the signer's identity, reading any object the signing key can reach. The flaw maps to [CWE-347: Improper Verification of Cryptographic Signature].
Critical Impact
A write grant to a single object becomes a read primitive across every bucket the signing key can access, bypassing presigned URL scoping entirely.
Affected Products
- MinIO through commit 7aac2a2 (the minio/minio GitHub repository was archived in April 2026)
- pgsty/silo before commit 1233254
- Any downstream distribution packaging the vulnerable extractSignedHeaders() logic
Discovery Timeline
- 2026-09-25 - CVE-2026-97731 published to NVD
- 2026-09-30 - Last updated in NVD database
Technical Details for CVE-2026-97731
Vulnerability Analysis
The defect lives in extractSignedHeaders() within cmd/signature-v4-utils.go. The function iterates only the header names claimed by the client in X-Amz-SignedHeaders and never enumerates the headers that actually arrived on the wire. Any header outside the claimed list is neither included in the canonical request hash nor rejected. Amazon S3 rejects the equivalent request with HTTP 403 AccessDenied, so MinIO diverges from the reference implementation.
Request dispatch in cmd/api-router.go routes to CopyObject whenever x-amz-copy-source is present, regardless of whether that header was signed. The combination lets an attacker repurpose a narrowly scoped presigned PUT URL into a cross-object read executed as the key holder.
Root Cause
The signature verifier trusts the client's declaration of which headers to hash. It never cross-references the actual request header set, so an attacker can smuggle semantically meaningful x-amz-* headers past SigV4 validation. The design assumes the signed-headers list is authoritative when it must instead be treated as a claim to be enforced against observed headers.
Attack Vector
An attacker who legitimately receives a presigned URL scoped to writing one object (PUT bucket/object) replays that URL unchanged. The attacker adds an x-amz-copy-source: other-bucket/sensitive-object header. MinIO validates the original signature against the claimed signed headers, then routes the request as CopyObject because x-amz-copy-source is present. The server copies the referenced source object into the destination the attacker controls, running under the signer's permissions.
// Patch excerpt from cmd/object-handlers.go
// The synthesized AmzObjectTagging header must be injected only
// AFTER signature verification, because the fixed SigV4 verifier
// now rejects any unsigned x-amz-* request headers.
tagsStr := tags.String()
logger.GetReqInfo(ctx).BucketName = bucket
logger.GetReqInfo(ctx).ObjectName = object
if s3Error := authenticateRequest(ctx, r, policy.PutObjectTaggingAction); s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return
}
// Set this such that authorization policies can be applied on the object
// tags. This is derived from the request body, so it must be injected only
// after signature verification: the SigV4 verifier now rejects unsigned
// x-amz-* request headers, and this synthesized header is never signed.
r.Header.Set(xhttp.AmzObjectTagging, tagsStr)
Source: GitHub Commit 1233254309b
Detection Methods for CVE-2026-97731
Indicators of Compromise
- Presigned requests containing x-amz-copy-source where that header name is absent from the request's X-Amz-SignedHeaders parameter.
- CopyObject operations originating from access keys that have only ever issued presigned PutObject URLs.
- Access log entries showing cross-bucket reads sourced from clients that normally interact with a single destination bucket.
Detection Strategies
- Parse MinIO HTTP access logs and diff the set of x-amz-* headers on each request against the comma-separated X-Amz-SignedHeaders value in the query string or Authorization header.
- Alert on any CopyObject call made through a presigned URL, since legitimate presigned flows typically target GetObject or PutObject.
- Correlate signer identity against the bucket referenced in x-amz-copy-source to flag reads outside the signer's expected scope.
Monitoring Recommendations
- Enable MinIO audit logging and forward events to a SIEM for signed-header anomaly rules.
- Track per-access-key operation profiles and alert on new CopyObject verbs appearing on keys previously limited to PutObject.
- Monitor egress volume from object storage for sudden spikes correlated with CopyObject activity.
How to Mitigate CVE-2026-97731
Immediate Actions Required
- Upgrade pgsty/silo to a build that includes commit 1233254309b15571f101b2b26d531951ceaeef1e or later.
- Rotate any MinIO access keys that have issued presigned URLs since the vulnerable code was deployed.
- Revoke outstanding presigned URLs by invalidating their underlying credentials where feasible.
- Audit object storage access logs for CopyObject requests whose signed-headers list omits x-amz-copy-source.
Patch Information
The fix in commit 1233254309b modifies the SigV4 verifier to reject requests containing any x-amz-* header that was not enumerated in X-Amz-SignedHeaders. The patch also reorders header injection in cmd/object-handlers.go so that server-synthesized headers such as AmzObjectTagging are only set after signature verification completes. Because the upstream minio/minio GitHub repository was archived in April 2026, operators remaining on MinIO must either migrate to a maintained fork such as pgsty/silo or front the service with a reverse proxy that enforces header-signing parity. See the Silo Security Advisory SN-2026-011 for full remediation guidance.
Workarounds
- Place a reverse proxy in front of MinIO that strips any x-amz-* header not listed in the request's X-Amz-SignedHeaders value.
- Scope all MinIO access keys with least-privilege IAM policies so that a compromised signer cannot read beyond its intended bucket.
- Shorten presigned URL expirations and prefer per-request credentials over long-lived keys.
# Example nginx snippet enforcing header-signing parity in front of MinIO
# Reject requests that carry x-amz-copy-source without declaring it signed.
location / {
if ($http_x_amz_copy_source != "") {
set $need_copy_source 1;
}
if ($arg_X-Amz-SignedHeaders !~* "x-amz-copy-source") {
set $need_copy_source "${need_copy_source}0";
}
if ($need_copy_source = "10") {
return 403;
}
proxy_pass http://minio_upstream;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.