S3 and MinIO Bucket Misconfigurations and Content-Type XSS: Cloud Storage Exposure in CTEM
Public S3 and MinIO buckets remain one of the fastest paths from cloud misconfiguration to data theft and stored XSS on trusted domains. This CTEM guide covers anonymous access, content-type confusion, detection telemetry, and how Trusteed CTEM turns raw bucket exposure into validated, prioritized findings.

S3 and MinIO Bucket Misconfigurations and Content-Type XSS: Cloud Storage Exposure in CTEM
TL;DR
- Public object storage is usually discussed as a data-loss problem. It is just as often an execution problem: if the bucket is served from a domain your users trust, an attacker can get the browser to render markup from that origin.
- Two misconfiguration families dominate: anonymous access (open bucket policies, legacy ACLs, MinIO anonymous presets) and content-type confusion (attacker-controlled
Content-Typemetadata that turns a stored object into rendered HTML, XHTML, or SVG). - Content-type XSS chains into session theft, phishing with a valid TLS certificate, JavaScript bundle overwrite, and SEO poisoning — often without touching a single line of application code.
- Trusteed CTEM keeps object storage in the same continuous exposure graph as the domains, services, and web/API endpoints that reference it, so bucket findings are validated against real reachability and business context instead of dumped into an analyst queue.
What is S3 and MinIO bucket misconfiguration?
Amazon S3 and S3-compatible stores such as MinIO are object storage: a bucket holds keys, keys hold objects, and every object carries metadata — including a Content-Type the browser will trust when that object is served over HTTP. A bucket misconfiguration is any combination of policy, identity, and metadata settings that lets someone who should not have access read, list, write, or reinterpret your objects.
In practice, four problems recur:
- Anonymous read. A bucket policy with
Principal: "*"ands3:GetObject, a public ACL on individual objects, or a MinIO anonymous preset set todownload. - Anonymous list.
s3:ListBucketgranted to everyone. Read access to one known key is a leak; list access turns it into a searchable index of everything you store. - Anonymous write.
s3:PutObject(or MinIO'spublicpreset, which grants read and write) lets an attacker plant objects, overwrite assets, and delete data. - Metadata confusion. The upload path does not constrain
Content-Type, so an.html,.svg,.xhtml, or.jsobject is served back with a renderable or executable MIME type.
Content-type XSS is the stored-XSS variant that follows from that last item. Instead of injecting a payload into a template, the attacker uploads a file with a renderable MIME type and gets the storage origin — which your users, and sometimes your own front end, treat as trusted — to deliver it. No server-side vulnerability is required. The bucket is the injection point.
Why it matters now
The storage estate is growing faster than the controls around it. Data lakes, ML pipelines, log archives, CI build artifacts, customer uploads, database exports, and Terraform state all land in buckets — and most buckets are created by teams who never open a cloud console. Every new bucket is a new exposure candidate.
Several shifts make this worse rather than better:
- Trusted-domain delivery. Buckets are usually fronted by
cdn.,static.,assets., orfiles.hostnames, frequently on the same registrable domain as the application. Cookies scoped to.example.comare sent to those hostnames, phishing filters treat them as legitimate, and the TLS certificate is valid. - Write is the new read. Read access leaks data; write access enables supply chain attacks. Replace the JavaScript bundle your customers load, or plant an HTML page that harvests credentials on a domain they already trust.
- Automation removed the skill barrier. Bucket discovery is a script, not a research project. Certificate transparency logs, JavaScript bundles, mobile app strings, DNS records, and predictable naming conventions all point at storage endpoints.
- Regulatory weight. Customer PII, payment data, health records, and source code in a world-readable bucket is a breach notification event, not a configuration ticket.
- Presigned URLs age badly. Signed URLs generated with long expirations and passed through logs, browsers, and chat tools effectively become public access objects.
How attacks / risks work

1. Discovery. Attackers start from data you already publish: DNS CNAMEs pointing at *.s3.amazonaws.com or a storage gateway, hostnames in certificate transparency logs, URLs inside JavaScript bundles and mobile binaries, error messages that leak bucket names, and response headers such as Server: MinIO or x-amz-bucket-region. Common naming patterns (company-backups, company-dev, company-tfstate) are brute-forceable at negligible cost.
2. Access verification. A HEAD request confirms the bucket exists and is reachable. A GET with ?list-type=2&max-keys=1000 confirms whether listing is anonymous. Individual object reads confirm whether content is exposed. All of it is unauthenticated, quiet, and indistinguishable from ordinary CDN traffic unless you are logging it.
3. Metadata abuse. When the upload path does not pin the MIME type, an object can be stored as text/html, application/xhtml+xml, or image/svg+xml. Two variants matter:
- Direct storage. The object is served with the stored
Content-Type, and the browser renders it inline. - Response-header override. S3-compatible APIs allow a signed GET to override response headers —
response-content-typeandresponse-content-disposition— so an object that normally downloads can be made to render, in the response the browser actually sees.
If the same origin also serves the application (path-based routing, a reverse proxy, or a shared hostname), the injected document inherits that origin's authority: it can read localStorage, call same-origin APIs with the victim's cookies, register a broader service worker scope, and slip past CSP rules that allow self.
4. MinIO specifics. MinIO's footguns are operational rather than conceptual. The console (typically port 9001) and API (typically port 9000) are frequently exposed to the internet; quickstart documentation normalizes minioadmin:minioadmin root credentials; and the mc anonymous presets make it a one-line change to grant download, upload, or public (read and write) access to an entire bucket. Because MinIO is self-hosted, it rarely appears in cloud-native posture tooling.
5. Impact. Anonymous read exposes backups, .env files, terraform.tfstate, private keys, and customer data. Anonymous write enables asset replacement and destructive operations. Content-type XSS converts either one into session theft, credential phishing on a trusted domain, defacement, and search-engine poisoning.
Detection and visibility
Good telemetry comes from three directions, and you need all three.
Storage-side audit. Enable CloudTrail data events for GetObject, PutObject, and DeleteObject, plus S3 server access logs for buckets that matter. Alert on management events that change exposure: PutBucketPolicy, PutBucketAcl, DeletePublicAccessBlock, PutAccountPublicAccessBlock. On MinIO, forward audit webhooks to your SIEM and watch for anonymous principals in the access log.
Content-side inspection. For every bucket you know about, record the HTTP response for a sample of objects: status code, Content-Type, Content-Disposition, X-Content-Type-Options, and the hostname that served it. Objects returning text/html, application/xhtml+xml, or image/svg+xml from a hostname on a trusted domain are the highest-value signal in this entire workflow — that is content-type XSS waiting to happen.
External vantage point. Anonymous access is only visible from outside your network. A continuous, rate-limited probe of known buckets — unauthenticated list and read attempts, plus checks for dangling CNAMEs that point at deleted buckets — tells you whether the exposure is real rather than theoretical. Combined with asset inventory, this is what separates a finding from a risk.
Metrics that hold up in a board deck: number of buckets with anonymous read, number with anonymous write, number of renderable objects served from trusted domains, percentage of bucket findings validated as actionable, and mean time from discovery to remediation.
Reduce risk / best practices
- Enforce Block Public Access at the organization and account level. Bucket-level settings are too easy to undo; the account-level controls (
BlockPublicAcls,IgnorePublicAcls,BlockPublicPolicy,RestrictPublicBuckets) are the backstop. - Treat
Principal: "*"as a finding, not a design choice. Review every bucket policy for wildcard principals, wildcard actions, and missingaws:SourceArn/aws:SourceVpceconditions. - Never grant
ListBucketanonymously. Long random key names are a reasonable compensating control; a public listing endpoint is not. - Pin the
Content-Typeon upload. Validate it server-side against an allow-list, and never accept a client-supplied MIME type for anything you serve back. - Force download for user content. Set
Content-Disposition: attachmentandX-Content-Type-Options: nosniffat the bucket, CDN, or reverse-proxy layer. - Serve untrusted content from a cookieless origin. Use a separate registrable domain — not just a subdomain — for user uploads, with no session cookies scoped to it and a strict CSP that includes
sandbox. - Harden MinIO like production infrastructure. Rotate off default root credentials, restrict the console port to an admin network, set explicit IAM policies rather than anonymous presets, and audit
mc anonymousstate on a schedule. - Shorten presigned URLs. Reduce expiry, avoid logging full signed URLs, and never let a client control response-header overrides for renderable types.
- Watch for dangling references. When a bucket is decommissioned, remove the CNAME the same day. A dangling record pointing at a re-registerable bucket name is subdomain takeover.
- Make exposure continuous. Inventory, probe anonymously from the outside, validate, and re-check on a schedule. A quarterly review of a cloud estate that changes daily is not a control.
How Trusteed CTEM helps

- Continuous attack surface and asset inventory. Domains, IPs, services, and technologies are discovered passively and actively under ongoing scan plans, so storage endpoints and the assets that reference them stay in one exposure graph instead of scattered across cloud consoles and spreadsheets.
- Findings enriched with real-world context. Scanner-driven detection is correlated with catalog CVE data, EPSS/KEV context, and exploit references where available — so a bucket exposure that chains into an actively exploited issue moves up the queue on evidence rather than severity math.
- A validation gate before the SOC queue. Not every scanner hit becomes an alarm. Trusteed validates exploitability and business context so dashboards and analyst queues focus on the actionable risk (
should_alarm) instead of a wall of anonymous-access warnings. - Depth where it matters. A dedicated API surface testing worker and a Deep DAST worker cover the web and API paths that reference storage — the same paths where content-type confusion and stored-XSS patterns actually land.
- Compliance and reporting views. Framework-oriented reporting makes it straightforward to evidence bucket posture and remediation progress over time, not only at audit season.
- Vulnerability intelligence built in. KEV and emergent-threat narratives from the public Trusteed blog feed prioritization work, which matters when a storage misconfiguration becomes the entry point for a known-exploited issue.
Operate it from app.trusteed.io; product context and research live at trusteed.io.
Trusteed CTEM vs point tools
| Capability | Typical point tool (Nuclei, Trivy, generic scanner) | Trusteed CTEM |
|---|---|---|
| Coverage model | Template- or container-driven checks that run when you invoke them | Continuous asset and service inventory with scheduled scan plans |
| Storage context | Bucket checks only if you write and maintain your own templates | Object storage exposure correlated with the domains, apps, and services that reference it |
| Exploitability context | Raw template severity | Findings enriched with catalog CVE data, EPSS/KEV context, and exploit references where available |
| Noise handling | Every hit becomes a finding | Validation / SOC gate that surfaces actionable risk for analyst queues |
| Web and API depth | Generic web checks | Dedicated API surface testing worker plus a Deep DAST worker |
| Workflow and reporting | CLI output; ticketing and reporting are manual | Operator workflow and dashboards at app.trusteed.io, with compliance-oriented reporting |
Point tools are good at producing signals. CTEM is the workflow that turns signals into prioritized, validated, continuously re-checked exposure.
FAQ
Is a bucket ACL the same thing as a bucket policy? No. ACLs are legacy per-object and per-bucket grants; bucket policies are JSON documents evaluated by the service. Modern S3 defaults new buckets to Bucket Owner Enforced, which disables ACLs entirely. If ACLs are still active in your account, assume both surfaces need auditing.
Does enabling Block Public Access make my bucket safe? It closes the anonymous path, but not every exposure. Overly broad authenticated policies, long-lived presigned URLs, CDN misrouting, and dangling CNAMEs all survive it. Block Public Access is a required control, not a complete one.
How does content-type XSS work if the bucket is on a different subdomain?
Browsers treat subdomains as separate origins, which blocks direct same-origin API access. But cookies scoped to .example.com are sent to cdn.example.com, and the page is served over your valid TLS certificate on your domain — enough for phishing, credential harvesting, and SEO poisoning. If storage is path-based or proxied on the application origin, the impact escalates to full session theft.
Is MinIO as risky as S3? The risks are the same class; the operations differ. Because MinIO is self-hosted, it often runs outside cloud-native posture tooling, with default root credentials, exposed consoles, and one-line anonymous policies. That combination is frequently worse than a misconfigured S3 bucket, not better.
How do I find publicly readable buckets before attackers do? Work from the external references you already publish — DNS CNAMEs, JavaScript bundles, mobile app strings, and certificate transparency — then probe unauthenticated list and read requests continuously. Anything you can discover without credentials is discoverable by anyone.
How is CTEM different from running scanners like Nuclei or Trivy? Scanners are excellent at executing checks: templates, container images, CI gates. CTEM is the surrounding discipline — continuous inventory of what actually exists, validation that a finding is genuinely exploitable in your context, and prioritization that keeps analyst attention on what needs action. You can run scanners forever and still not know whether an exposure is reachable, owned, or business-critical. CTEM answers those questions; scanners produce the raw material.
Can I tell if someone has already read or overwritten my objects? Sometimes. CloudTrail data events, S3 server access logs, MinIO audit logs, and GuardDuty S3 Protection findings are your evidence trail. Buckets created without logging enabled are effectively blind spots — which is itself a finding worth fixing before you need it.
Related resources
- Trusteed CTEM platform — continuous threat exposure management for external and internal attack surface.
- Trusteed tenant app — inventory, findings, validation, and reporting in one operator workflow.
- Trusteed vulnerability intelligence blog — KEV and emergent-threat narratives that inform prioritization.
- AWS: Blocking public access to your Amazon S3 storage
- MinIO: Identity and access management
- OWASP: Cross Site Scripting Prevention Cheat Sheet
- CISA: Cloud Security Technical Reference Architecture
- NIST SP 800-210: General Access Control Guidance for Cloud Systems