We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Defending Against SSRF

The safest SSRF defense is to avoid arbitrary destinations. If an app needs images from one provider, accept HTTPS URLs for that exact host and reject everything else.

Better yet, accept image IDs and build the URLs within server code.

Parse and validate the URL before sending a request:

func allowedURL(rawURL string) (*url.URL, bool) {
    parsed, err := url.Parse(rawURL)
    if err != nil || parsed.Scheme != "https" {
        return nil, false
    }
    if parsed.User != nil || parsed.Host != "images.example.com" {
        return nil, false
    }
    return parsed, true
}

Compare the parsed host exactly. That rejects unexpected ports and lookalike hosts such as images.example.com.attacker.test.

The HTTP client needs its own boundaries too. Set request, response-header, and TLS-handshake timeouts so a slow approved host can't hold resources indefinitely. Disable proxy discovery when it is not part of the feature's design.

Redirects need an explicit policy. Go's http.Client follows redirects by default, so an approved URL might redirect to an internal destination. A CheckRedirect function can return http.ErrUseLastResponse, leaving the redirect response unread for the caller to reject.

An exact-host allowlist avoids most DNS ambiguity, but if you truly need arbitrary public hosts, blocking private addresses gets much harder: validate every resolved IPv4 and IPv6 address and ensure the connection uses an address you checked. A separate DNS lookup followed by a normal request doesn't guarantee both operations use the same address, so it doesn't stop DNS rebinding by itself.

Assignment

Bearly Secure's image-preview service can fetch any destination the server can reach. Restrict previews to the exact approved HTTPS host.

Run and submit the CLI tests from the project root.