Skip to content
DnsLister Forum

Where domain hunters compare notes

The avatar-from-URL field that turned into an allowlist bypass exercise

You're on an API assessment and you find an endpoint that fetches a URL server-side: PUT /api/v1/users/me/avatar with {"avatar_url": "https://..."}. The server downloads whatever's at that URL and stores it as the profile picture. First instinct, point it at http://169.254.169.254/latest/meta-data/iam/security-credentials/, the AWS cloud metadata address, and see what comes back.

  1. invalid host. There's an allowlist.

That's not the end of the test. That's where it actually starts, because a hostname allowlist that only checks the string you sent is checking the wrong thing, and there are several ways to hand it a string that passes and resolves somewhere else entirely.

Open redirects hand you the bypass for free. If the app or any third-party service it trusts has an open redirect, point the fetcher at that instead of the internal target directly. A filter checking the hostname in the request never sees the internal IP, because it only shows up after the server-side fetcher follows a 302 it was never told to distrust.

IP encoding breaks string-match filters that never learned there's more than one way to write an address. 127.0.0.1 also parses as the decimal integer 2130706433, the hex 0x7f000001, the octal 0177.0.0.1, and the IPv6-mapped ::ffff:127.0.0.1. A filter built around a regex for dotted-quad notation lets every one of those through, and most HTTP client libraries resolve them all to the same address anyway.

DNS rebinding attacks the gap between the check and the fetch. If validation resolves the hostname once to confirm it's safe and the actual fetch resolves it again later, point the domain at a DNS record with a short TTL: safe IP on the first lookup, metadata IP on the second. Most naive filters assume the two resolutions will agree.

The one that breaks the most real filters is URL parser disagreement. http://trusted-host.com@169.254.169.254/ parses trusted-host.com as userinfo and 169.254.169.254 as the actual host under the URL spec, but a filter written with a sloppy regex checking whether the trusted hostname appears anywhere in the string passes it straight through. This exact class of bug, validating code and fetching code parsing the same URL two different ways, shows up disproportionately in API SSRF specifically, because APIs push URLs through more layers, SDKs, proxies, internal services, than a browser-facing form ever does.

And if none of that gets you a visible response, the bug isn't dead, it's blind. Point the field at a domain you control with a unique subdomain per test and watch for the callback. A hit proves the server fetched your URL even when nothing comes back to you directly.

Once you're actually reaching internal addresses, the payoff is what's sitting at them. Cloud metadata endpoints hand out temporary IAM credentials to anything that can reach them from inside the network, no auth required, because the whole design assumes instances trust their own network. An SSRF that reaches metadata isn't a medium finding about the server making an unintended request. It's full cloud account compromise wearing an avatar upload as a disguise.

This is the exact chain Codelivly's API Hacking Book for Beginners walks through: finding SSRF sinks in real API surfaces, proving blind SSRF with out-of-band techniques, defeating allowlists the way above, and turning that access into the credential compromise that makes an SSRF finding worth reporting as critical. The free SSRF lab and SSRF room on codelivly.com are good hands-on reps for the same bypass chain if you want to try it before you read about it.

Source: r/u/Potential-Couple-745 · by /u/Potential-Couple-745

Leave a Reply

Your email address will not be published. Required fields are marked *