netsafe.go 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package netsafe
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net"
  7. "regexp"
  8. "strings"
  9. "time"
  10. )
  11. // ErrPrivateAddressBlocked marks a failed dial where the guard refused at least
  12. // one resolved address, so a caller offering an opt-in can tell it apart from an
  13. // ordinary connection failure.
  14. var ErrPrivateAddressBlocked = errors.New("blocked private/internal address")
  15. func IsBlockedIP(ip net.IP) bool {
  16. return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
  17. ip.IsLinkLocalMulticast() || ip.IsUnspecified()
  18. }
  19. type allowPrivateCtxKey struct{}
  20. func ContextWithAllowPrivate(ctx context.Context, allow bool) context.Context {
  21. return context.WithValue(ctx, allowPrivateCtxKey{}, allow)
  22. }
  23. func AllowPrivateFromContext(ctx context.Context) bool {
  24. v, _ := ctx.Value(allowPrivateCtxKey{}).(bool)
  25. return v
  26. }
  27. var defaultDialer = &net.Dialer{Timeout: 10 * time.Second}
  28. func SSRFGuardedDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
  29. host, port, err := net.SplitHostPort(addr)
  30. if err != nil {
  31. return nil, err
  32. }
  33. allowPrivate := AllowPrivateFromContext(ctx)
  34. var ips []net.IPAddr
  35. if ip := net.ParseIP(host); ip != nil {
  36. ips = []net.IPAddr{{IP: ip}}
  37. } else {
  38. ips, err = net.DefaultResolver.LookupIPAddr(ctx, host)
  39. if err != nil {
  40. return nil, err
  41. }
  42. }
  43. var lastErr, blockedErr error
  44. for _, ipAddr := range ips {
  45. if !allowPrivate && IsBlockedIP(ipAddr.IP) {
  46. blockedErr = fmt.Errorf("%w %s", ErrPrivateAddressBlocked, ipAddr.IP)
  47. continue
  48. }
  49. conn, derr := defaultDialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))
  50. if derr == nil {
  51. return conn, nil
  52. }
  53. lastErr = derr
  54. }
  55. // A dual-stack name can mix refused and merely unreachable addresses, so the
  56. // refusal is reported alongside instead of being lost to the last failure.
  57. if blockedErr != nil {
  58. if lastErr != nil {
  59. return nil, fmt.Errorf("%w; %v", blockedErr, lastErr)
  60. }
  61. return nil, blockedErr
  62. }
  63. if lastErr == nil {
  64. lastErr = fmt.Errorf("no usable address for %s", host)
  65. }
  66. return nil, lastErr
  67. }
  68. var hostnamePattern = regexp.MustCompile(`^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$`)
  69. func NormalizeHost(addr string) (string, error) {
  70. addr = strings.TrimSpace(addr)
  71. if addr == "" {
  72. return "", fmt.Errorf("address is required")
  73. }
  74. if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
  75. addr = addr[1 : len(addr)-1]
  76. }
  77. if ip := net.ParseIP(addr); ip != nil {
  78. return ip.String(), nil
  79. }
  80. if len(addr) > 253 || !hostnamePattern.MatchString(addr) {
  81. return "", fmt.Errorf("invalid host %q", addr)
  82. }
  83. return addr, nil
  84. }