July 29, 2026
Unauthenticated IPFS Uploads Led to Stored XSS on a Web3 Platform
Overview
By default_0x
3 min read
In the fast-paced world of Web3 and decentralized applications, developers are racing to ship features, sometimes overlooking fundamental security principles. Recently, I decided to dig deeper into the infrastructure of a prominent platform, target.com (a fictional name for a very real, redacted launchpad).
What I found started as a classic case of missing authorization but quickly escalated into a mechanism for financial abuse, reputational damage, and, ultimately, confirmed Stored Cross-Site Scripting (XSS).
Here is the journey of how a simple API oversight allowed me to leverage a platform's dedicated IPFS gateway for unintended execution.
The Initial Discover: An Open Gate
While performing reconnaissance on the application's asset handling, my attention was drawn to how the platform processed token metadata and image uploads. Naturally, they used IPFS (InterPlanetary File System) via Pinata, a popular pinning service.
Most implementations require some form of user authentication before allowing a file upload — after all, storage costs money. However, as I intercepted the traffic, I noticed something concerning. The endpoints responsible for handling these uploads didn't seem to care who I was.
I identified two distinct endpoints on target.com that allowed unauthenticated file uploads directly to their production Pinata account. Neither endpoint required cookies, authorization headers, or API keys. It was, essentially, a free, public file hosting service.
Technical Breakdown: Circumventing the Non-Existent Checks
Let's look under the hood at how these two vectors worked.
Vector 1: The Presigned URL Route
The first endpoint, GET /api/ipfs-presign, followed a standard pattern: ask for a URL to upload to, then upload the data. The crucial oversight? Anyone could ask for that URL.
By simply curling the endpoint, I received a valid Pinata presigned upload URL, complete with the platform's user ID and environment configuration.
Bash
# Step 1 — Request presigned URL (no auth required)
curl -s https://target.com/api/ipfs-presign
# Response returns a detailed Pinata URL
{"data":"https://uploads.pinata.cloud/v3/files/XXX?X-Algorithm=PINATA2&X-User-ID=REDACTED_PINATA_USER_ID&X-Upload-Option-Keyvalue={\"env\":\"production\"}&..."}# Step 1 — Request presigned URL (no auth required)
curl -s https://target.com/api/ipfs-presign
# Response returns a detailed Pinata URL
{"data":"https://uploads.pinata.cloud/v3/files/XXX?X-Algorithm=PINATA2&X-User-ID=REDACTED_PINATA_USER_ID&X-Upload-Option-Keyvalue={\"env\":\"production\"}&..."}With this URL in hand, I could post any content I desired. I tested uploading an SVG file, and the server responded with a HTTP 200 OK and the resulting IPFS Content Identifier (CID).
Bash
# Step 2 — Upload file to presigned URL
curl -X POST "$PRESIGN_URL" -F 'file=@poc.svg;type=image/svg+xml'
# Response confirmed success
{"data":{"id":"XXX","cid":"REDACTED_CID","name":"poc.svg","mime_type":"image/svg+xml",...}}# Step 2 — Upload file to presigned URL
curl -X POST "$PRESIGN_URL" -F 'file=@poc.svg;type=image/svg+xml'
# Response confirmed success
{"data":{"id":"XXX","cid":"REDACTED_CID","name":"poc.svg","mime_type":"image/svg+xml",...}}Vector 2: The Direct POST Route
The second endpoint, POST /api/ipfs, was even simpler. It accepted a direct file POST and not only uploaded the file to IPFS but automatically created a metadata JSON file linking to it—mirroring the platform's legitimate token creation process. Again, zero authentication.
Bash
# Single-step upload (no auth required)
echo "target.com unauthorized upload PoC" > test.txt
curl -X POST https://target.com/api/ipfs -F 'file=@test.txt;type=text/plain'# Single-step upload (no auth required)
echo "target.com unauthorized upload PoC" > test.txt
curl -X POST https://target.com/api/ipfs -F 'file=@test.txt;type=text/plain'The response confirmed that the file was pinned to IPFS and associated with target.com's identity within the generated metadata.
The Amplifying Factor: Permissive CORS and Caching
Finding a free storage glitch is one thing, but I wanted to assess the real impact. This is where things got weird.
Uploaded files were served from ipfs.target.com, the platform's dedicated Pinata gateway subdomain. The configurations on this gateway acted as a significant amplifier for the vulnerability:
Access-Control-Allow-Origin: *: Permissive CORS meant any website on the internet could read content from this subdomain cross-origin.- Aggressive Caching: The files were served with
Cache-Control: public, max-age=29030400, immutable, caching the content for nearly one year. Once a malicious file was uploaded, it was here to stay.
Escalation to Stored XSS
The most pivotal finding was confirming Stored XSS via SVG upload. An attacker could upload a malicious SVG file containing JavaScript to the platform's production account.
The gateway served SVGs with Content-Type: image/svg+xml. This instructs the browser to render the SVG as a document, not just an image. Crucially, there was no Content-Disposition: attachment header to force a download.
By navigating directly to [https://ipfs.target.com/ipfs/](https://ipfs.target.com/ipfs/){REDACTED_CID}, the browser would render the SVG, causing the embedded JavaScript to execute in the context of the platform's gateway domain.
I successfully executed the following payload:
SVG
The exploit chain was deceptively simple: calling an unauthenticated endpoint, uploading a small text file with a script, and gaining code execution on a sub-domain controlled by the target.
Real-World Impact
The severity here is high because the unauthenticated upload is the root cause, but the stored XSS provides clear proof of real-world exploitability.
The potential impact spans across several areas:
- Financial Abuse: An automated script could easily exhaust the platform's Pinata storage quota, driving up billing or disrupting legitimate services.
- Reputational Risk: Attackers could host phishing pages or illegal content directly under the
target.comsubdomain, permanently pinned to IPFS via their account. - Stored XSS: The confirmation of script execution on
ipfs.target.comopens the door to session hijacking or sophisticated attacks against users visiting malicious CIDs.
Remediation: Closing the Gates
The platform reacted quickly to patch these holes. The fix involves a defense-in-depth approach:
- Enforce Authentication: Both
/api/ipfs-presignand/api/ipfsmust validate a user's session before processing uploads. - Implement Rate Limiting: Capping uploads per user or IP address prevents resource exhaustion.
- Restrict CORS: The open
Access-Control-Allow-Origin: *policy should be tightened to only allow necessary origins. - Content Validation: Server-side validation should be performed on uploaded content before it is pinned to the production environment.
It's easy to focus solely on smart contract security in Web3, but traditional web vulnerabilities remain a significant threat vector.
Let's Hunt ! > Satyam > 🔗 Follow me on X