newSSRFSafeHTTPClient returns an HTTP client that rejects connections to private, loopback, link-local, multicast, and unspecified IP addresses. This prevents DNS rebinding attacks where a hostname passes URL-level validation but resolves to an internal IP at dial time.
()
| 483 | // This prevents DNS rebinding attacks where a hostname passes URL-level |
| 484 | // validation but resolves to an internal IP at dial time. |
| 485 | func newSSRFSafeHTTPClient() *http.Client { |
| 486 | return &http.Client{ |
| 487 | Transport: &http.Transport{ |
| 488 | DialContext: (&net.Dialer{ |
| 489 | Control: func(_ string, address string, _ syscall.RawConn) error { |
| 490 | host, _, err := net.SplitHostPort(address) |
| 491 | if err != nil { |
| 492 | return xerrors.Errorf("split host/port: %w", err) |
| 493 | } |
| 494 | ip, err := netip.ParseAddr(host) |
| 495 | if err != nil { |
| 496 | return xerrors.Errorf("parse resolved IP: %w", err) |
| 497 | } |
| 498 | if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || |
| 499 | ip.IsLinkLocalMulticast() || ip.IsMulticast() || |
| 500 | ip.IsUnspecified() { |
| 501 | return xerrors.Errorf( |
| 502 | "webpush endpoint resolved to non-public address %s", ip.String(), |
| 503 | ) |
| 504 | } |
| 505 | return nil |
| 506 | }, |
| 507 | }).DialContext, |
| 508 | }, |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | // RegenerateVAPIDKeys regenerates the VAPID keys and deletes all existing |
| 513 | // push subscriptions as part of the transaction, as they are no longer valid. |