August 22, 2026
How 8 of the Top 20 HackerOne SSRF Reports Escalated to Cloud Credentials Leaks
30-second version: I mined the HackerOne report dataset and read the top 20 SSRF reports by upvotes in full. Eight of them end in stolen…

By Abhishek meena
9 min read
30-second version: I mined the HackerOne report dataset and read the top 20 SSRF reports by upvotes in full. Eight of them end in stolen cloud credentials: AWS keys, GCP tokens, SSH keys. The rest stop at port scans, partial reads, or internal dashboards. Paid bounties run from an $850 median to $25,000 at the top, and the difference is where the request lands. This playbook maps the five surfaces that keep producing SSRF, walks the escalation ladder from port scan to root, and gives you the five questions to run on every endpoint.
Why SSRF pays badly until it escalates
The dataset holds 286 reports tagged Server-Side Request Forgery. I pulled the numbers first, then read the top 20 writeups in full.
Eighty-nine of the 286 ever received a bounty. That is 31%. Total paid across all of them: $170,796. The median payout is $850. The top is $25,000, paid by HackerOne for an SSRF in its own analytics reports that reached internal AWS services and temporary credentials.
The gap between $0 and $25,000 is not the bug. It is the escalation. Blind SSRF reports tend to die at "informative" because the impact stops at "the server made a request." The eight reports at the top of the list do something different. Each one turns a server-side request into credentials:
- Shopify: GCP metadata via a screenshot bot, root on every container in the affected subset
- HackerOne: AWS temporary credentials, $25,000
- Snapchat: GCP service-account tokens via headless Chrome and DNS rebinding
- GitLab: a webhook bypass that reads the AWS metadata IP
- Slack: a TURN server proxying TCP into the metadata service and 10.0.0.0/8, $3,500
- Vimeo: GCP data and SSH keys through an upload function
- Evernote: AWS metadata plus file:// local file inclusion
- Omise: AWS private keys through a webhook redirect
That is 8 of the top 20. (One more top-20 report, Dropbox HelloSign leaking AWS private keys, is no longer publicly readable; the dataset still records its title and $4,913 bounty. Several other top-20 reports have gone private too.)
The pattern is loud: SSRF that stops at "I can scan ports" stays cheap. SSRF that reaches metadata becomes critical. Everything below is about closing that distance.
Where the bug lives
SSRF hides wherever the server fetches something on your behalf. Four zones in the attack surface, each with its own shape:
Fetch-by-URL features. Import, export, attach-by-URL, avatar upload, link preview. Any endpoint with a parameter named url, uri, source, attachment, or callback.
Processing pipelines. Video transcoding, document thumbnails, image rendering. The server opens your file and a parser inside follows references: HLS playlists, SVG xlink:href, OLE objects.
Rendering bots. Screenshot services, og-scrapers, page previews. A headless browser loads your content and every resource it references, with the server's network position.
Outbound callbacks. Webhooks, integrations, OAuth flows. The feature must reach your server by design, so a validator gets bolted on after the fact.
All four zones funnel into the same destination: the internal network, where the metadata endpoints (169.254.169.254, metadata.google.internal) and internal dashboards live. The bug is almost never the fetch. The bug is that the fetch runs with the server's identity.
The five patterns that kept appearing
Pattern 1: Attach by URL
Any feature where the user supplies a URL and the server fetches it. The feature is the product, and validation is an afterthought.
GitLab's project import accepted a remote_attachment_url field on notes. The importer downloaded whatever URL was inside, attached the file, and rendered it. The report paid $10,000. Lichess passed the players parameter of its game export straight into an HTTP client. The report walks the source code line by line:
Evernote had an endpoint that fetched any base64-encoded URL and returned the body, plus file:// support. EXNESS had a GraphQL source parameter that made blind GETs from the server, paid $3,000. inDrive's file storage proxied ?url= straight through.
Why it works: the server never asks whether it should fetch the URL. The feature exists to fetch URLs. The only question is whether anyone thought about which URLs.
How to test: map every parameter that carries a URL. Send your Collaborator domain first. Then send http://169.254.169.254/latest/meta-data/. Watch whether the response leaks back, and in what shape.
Pattern 2: The pipeline behind the upload
The file you upload is not inert. Transcoders and renderers treat its contents as instructions.
TikTok's video pipeline processed HLS playlists with FFmpeg, which fetched the URLs inside them and read local files. The report paid $2,727. WordPress.com had the same FFmpeg behavior triggered through GAB2 subtitle chunks in an AVI file. Slack rendered Office thumbnails with LibreOffice, and a crafted file exposed an internal AWS credential, $4,000. Vimeo's upload function leaked GCP data and SSH keys. PlayStation's image renderer accepted a gopher:// redirect and sent SMTP from the server, $1,000. Shopify accepted an SVG named .png whose xlink:href fetched arbitrary URLs, $500.
Why it works: the parser runs with the processing container's identity, and that container usually has cloud credentials for its storage buckets. Your file is a request smuggled into a trusted place.
How to test: upload a video, document, or image with an external reference inside. A Collaborator URL in an HLS playlist, an OLE object, or an SVG xlink:href. Then check who called you: the user agent and IP tell you which pipeline fired.
Pattern 3: The bot that renders your page
Preview and screenshot features load your content in a headless browser. Rendering your page means executing your fetches.
The Shopify Exchange listing page screenshotted new stores. A store template could contain a script that redirects the bot to the GCP metadata service, which minted an instance token, which became root on every container in the subset. That report has 580 upvotes and is the anchor story below. Snapchat's media import ran the same attack with DNS rebinding against its Chrome instances and exfiltrated GCP service-account tokens. Lark Docs escalated stored XSS into SSRF the moment the content was rendered headlessly, $3,000.
Why it works: the browser holds the server's network position and cloud identity. Your HTML is code that runs where you cannot reach.
How to test: find features that generate a preview, screenshot, or thumbnail of content you control. Embed an external image or script. Detect the fetch with your server's access log, or use timing to infer a slow internal target.
Pattern 4: The webhook that follows redirects
Webhooks must reach external hosts, so programs add filters. The filters sit exactly where the bypass happens: at the redirect.
Omise's webhooks did not follow redirects, except HTTP 303. A webhook that answered 303 See Other pointing at the metadata service returned AWS private keys in the delivery log:
header('Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-opsworks-ec2-role', TRUE, 303);header('Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/aws-opsworks-ec2-role', TRUE, 303);GitLab's webhooks had DNS-rebinding protection that failed when the domain did not resolve cleanly, which let the request reach 169.254.169.254 anyway. HackerOne's own webhook filter missed IPv4-mapped IPv6 addresses like [::ffff:a9fe:a9fe], $2,500. In Kubernetes, a hijacked aggregated API server answering 30X redirects could pull control-plane clients into internal requests, $1,000.
Why it works: the validator checks the URL you gave it. The HTTP client follows the URL the server gave back. Those are two different decisions made by two different layers.
How to test: register a webhook, answer with a 303 or 307 redirect to an internal target. Try alternate encodings of loopback and metadata addresses. Check the delivery history for what the redirect returned.
Pattern 5: The check that checks wrong
The validator and the HTTP client disagree about what a URL is. Every disagreement is a bypass.
GitLab's UrlBlocker resolved the domain for validation, then the HTTP client resolved it again for the request. Two resolutions, one check. Stripe's Smokescreen deny list was beaten by a trailing dot on the domain. DuckDuckGo's image proxy checked that the URL contained yimg.com anywhere, so the string went in the query parameter and the real target went in the path, exposing Redis status data. A 2025 report beat ssrf_filter with the NAT64 local-use prefix 64:ff9b:1::/48, which the filter did not block. Slack's API accepted http://[::]:22/ and interacted with internal SSH and SMTP. DNS rebinding against the Burp Suite MCP server paid $2,000 in 2025.
Why it works: address parsing is a tangle. IPv4-mapped IPv6, NAT64, integer and octal forms, trailing dots, TTL-zero DNS. The validator sees one address, the client dials another.
How to test: take a working SSRF and run it through every alternate address form you know, then through a rebinding domain with a TTL of zero. One of the two layers will eventually disagree.
From port scan to root: the escalation ladder
Each rung is a different answer to the same question: can you read what came back?
Blind: the request fires, the response stays inside. EXNESS earned $3,000 for blind GETs. This is the floor.
Partial read: the response comes back constrained. Reddit's Matrix preview endpoint returned only og:title, which was enough to enumerate internal service names for $6,000. PlayStation's renderer returned internal images through a chained second SSRF, $1,000.
Full read: the response comes back whole. DuckDuckGo's proxy returned Redis status and config. GitLab's bundled Grafana became a full-read SSRF through a redirect chain and a URL-decoding bug. The dataset's highest-vote Dropbox report, full-response SSRF through Google Drive, paid $17,576, though the report page is no longer public.
Cloud credentials: the response contains keys. Omise's AWS private keys. Vimeo's GCP SSH keys. Slack's AWS credential, $4,000. HackerOne's $25,000 AWS temporary credentials.
Root, RCE, account takeover. Shopify's screenshot bot gave root on every container in the subset. Autodesk's rendering SSRF became full account takeover.
The rung you land on decides the severity, the payout, and whether triage takes you seriously. The request was the same in almost every case. The difference is where it landed and whether the response came back.
The screenshot bot that got root
The report that made SSRF click for me is Shopify's #341876, 580 upvotes.
The hunter created a store on the partner platform and edited the theme file password.liquid. The template is rendered for visitors, but it is also rendered by a bot: Shopify Exchange screenshots every new store for its listing page. A script inside the template redirected the bot:
<script>
window.location="http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token";
</script><script>
window.location="http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token";
</script>Two details make the story. First, GCP metadata usually demands the Metadata-Flavor: Google header. The /v1beta1 endpoint does not. No header, same token. Second, the screenshot renderer would not return plain text responses, so the hunter appended ?alt=json to force the metadata into JSON that the renderer would draw. The token came back inside a PNG.
From there: the token belonged to the instance's service account, and the subset's containers could be reached. Root access on any instance in that subset, demonstrated with SSH key metadata and a refused setCommonInstanceMetadata call that confirmed the credential's power.
Shopify disabled the vulnerable service within an hour and shipped a metadata concealment proxy as the fix. Not a regex. A proxy that hides metadata from every service that does not need it. That is the depth this bug class rewards, and the fix every program with screenshot bots should copy.
Five questions to run on every endpoint
Run these on every feature that touches a URL, a file, or a callback.
- Does it fetch a URL or host I control? Any parameter named
url,uri,source,attachment,avatar,callback, orwebhook. Test with Collaborator, then with the metadata IP. - Does it follow redirects, and is the redirect target revalidated? Answer 303 and 307 redirects at an internal target and watch the delivery log.
- Does the validator resolve DNS the same way the HTTP client does? A rebinding domain with TTL zero, or a hostname whose resolution changes between check and fetch, tests both layers at once.
- What protocols and address forms does the client accept?
gopher://for SMTP,file://for local reads, [::], IPv4-mapped IPv6, NAT64, trailing dots. Each is a different disagreement with a filter. - If it lands internal, can you read the response, and where are the credentials? The metadata endpoint is the destination. The response channel, whether a screenshot, an
og:title, or a webhook delivery, is the whole game.
What to remember
SSRF severity is decided by where the request lands, not by where it starts. Stop at the port scan and you will file another informative. Follow the fetch to metadata and you are in the top 20.
Next in the Playbook series: XSS. The dataset holds 2,456 XSS-tagged reports, 525 of them paid, $535,319 in bounties, $16,000 at the top. I will mine the same way and show which payloads survive real filters.
Sources
- Shopify: SSRF in Exchange leads to ROOT access in all instances
- HackerOne: SSRF via Analytics Reports, $25,000
- Snapchat: SSRF exfiltrates Google Metadata
- Omise: SSRF in webhooks leads to AWS private keys
- Slack: TURN server proxying to internal network and metadata
- GitLab: SSRF via remote_attachment_url, $10,000
- Lichess: SSRF via Game Export API
- TikTok: SSRF via FFmpeg HLS processing
- Slack: SSRF via Office file thumbnails, $4,000
- GitLab: UrlBlocker validation bypass
- DuckDuckGo: SSRF on the /iu/ image proxy
- GitLab: Full read SSRF on internal Grafana