A URL parser mismatch opened an SSRF hole

In February 2021, security researcher Kumar Saurabh reported a critical server-side request forgery (SSRF) vulnerability through Dropbox's bug bounty program. The flaw lived in the branded sharing feature, which lets teams and Pro users upload a logo or background image that appears across shared links and emails. An attacker could exploit it to make HTTP GET requests to internal endpoints and read the responses. After confirming the issue, Dropbox declared an internal security incident and shipped a fix to production in roughly eight hours. There is no evidence the vulnerability was ever exploited, and no user data was exposed. The payout was calculated at $27,000.

The attack surface was limited by the fact that most internal Dropbox services speak gRPC, which includes built-in authentication and does not support plain GET communication. Still, the vulnerability demonstrated a class of problem the Application Security team wanted to eliminate more broadly: inconsistent URL parsing between validation logic and the library that actually makes the request.

How the request was hijacked

When a user configures branded sharing, the client sends an img_url argument to either /team/admin/team_logo/save or /team/admin/team_background/save. The backend then invokes curl to fetch the image from that URL. The intended domain is dl-web.dropbox.com, and the code does validate the URL's authority—but the validation relies on a custom URI library while the request itself is built using Python's urllib.parse inside a helper called CurlConnection. Those two parsers disagree on how to interpret certain URLs.

The URI library parses according to RFC 3986 Appendix B, with a modification for browser compatibility. The exploit took advantage of that divergence. A payload like https://dl-web.dropbox.com\@<host>:<port> passes the authority check when parsed by the URI library, which treats everything before the backslash-at as the authority. But urlsplit interprets the portion after @ as the actual hostname and routes the request to the attacker-controlled address. The response body would then be saved to the database and could be retrieved from https://www.dropbox.com/team/team_logo/[dbtid] or the equivalent background-image endpoint—giving the attacker a read channel into internal responses.

Fixing the immediate flaw

The simplest patch was to apply str to the parsed URL before using it for the request. Because the URI class performs validation during serialization, this step rejects the uncommon patterns that allowed the bypass. A stronger approach, though, is to stop trusting user-supplied URLs entirely. Instead of verifying that the provided domain is valid, construct the full URL with the intended domain server-side and append only the path or resource identifier from the user input. That removes the possibility of making a request to a raw, user-controlled destination.

Secure by default: moving SSRF defense to the network layer

After the immediate fix, cross-team postmortem discussions shifted toward structural prevention. The goal was to ensure that no outgoing HTTP request could accidentally reach an internal private address. Simply resolving a URL and checking whether the IP falls in a private range is not enough, for three reasons:

  • Inconsistent URL parsing: If validation and the request library use different parsers, attackers can craft a polyglot URL that passes validation but resolves elsewhere.
  • HTTP redirects: Validating only the initial request leaves the door open for a redirect to an internal address on a subsequent hop.
  • DNS rebinding: If validation and the request each perform their own DNS lookup, an attacker can return a public IP the first time and a private IP the second.

The solution is to have one central place that parses the URL, validates the resolved address, and makes the actual request—ideally in a layer that cannot be bypassed. Rather than implementing this in the application layer for every language, Dropbox chose to enforce it lower in the network stack via Envoy and an HTTP role-based access control (RBAC) filter. That approach offers several advantages:

  • Central configuration, managed in one place.
  • No need to build per-language solutions.
  • Coverage for third-party binaries and libraries.
  • No risk of parser discrepancies between a language's URL handling and the request library.
  • Flexible, fine-grained ACLs—for example, a proxy with an allowlist for corporate network requests, or blocking certain Dropbox assets from receiving webhooks.
  • The ability to enforce proxy use by blocking direct outbound requests.

There is one more caveat: special protocols. If only HTTP requests are checked, an attacker can switch to file://, gopher://, or similar schemes. Some older curl-based libraries also follow redirects into those protocols, so validating just the first request is not adequate. Disabling non-HTTP protocols via curl options like CURLOPT_PROTOCOLS or CURLOPT_REDIR_PROTOCOLS is one path. Dropbox instead recompiled libcurl to strip out non-HTTP protocol support entirely—a more bulletproof option that integrates cleanly with existing infrastructure. These defenses already applied to webhook requests; after this incident they were extended to all outgoing traffic, and incompatible use cases were migrated.

Further hardening: authentication everywhere

Network-level SSRF defenses isolate the internal network, but Dropbox wanted to reduce the attack surface inside production as well. Production services mostly speak gRPC and enforce a default-deny ACL, which already limits SSRF impact. As a safety net for anything that slips through—such as an HTTP listener with sensitive capabilities—the company built a project called Auth Everywhere.

Auth Everywhere mandates that every connection in production be authenticated, authorized, and auditable. It works by deploying a sidecar proxy alongside each HTTP service, which accepts only mutual TLS (mTLS) connections from clients within the ACL. The approach follows the least-privilege principle and provides defense in depth against SSRF, while also limiting the broader impact of any compromised production host.

The bug-bounty report was the catalyst for a much wider change: a secure-by-default framework for outbound HTTP, enforced at the proxy layer, plus a production network where every connection carries its own proof of identity.