Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 57a2754

Browse files
committedOct 28, 2024·
fix: allow calling internal 100.64.0.0/10 IP range with relevant option
The `ResilientClient` options `ResilientClientDisallowInternalIPs` and `ResilientClientAllowInternalIPRequestsTo` were not allowing to call the IP range, like 100.64.0.0/10, properly. Some IP ranges are still not possible to bypass.
1 parent c15c471 commit 57a2754

File tree

2 files changed

+118
-41
lines changed

2 files changed

+118
-41
lines changed
 

‎httpx/resilient_client_test.go

+116-41
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,134 @@ package httpx
55

66
import (
77
"context"
8-
"net"
8+
"fmt"
99
"net/http"
10-
"net/http/httptest"
1110
"net/http/httptrace"
1211
"net/netip"
13-
"net/url"
1412
"sync/atomic"
1513
"testing"
14+
"time"
15+
16+
"code.dny.dev/ssrf"
1617

1718
"github.com/hashicorp/go-retryablehttp"
1819
"github.com/stretchr/testify/assert"
1920
"github.com/stretchr/testify/require"
2021
)
2122

22-
func TestNoPrivateIPs(t *testing.T) {
23-
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
24-
_, _ = w.Write([]byte("Hello, world!"))
25-
}))
26-
t.Cleanup(ts.Close)
27-
28-
target, err := url.ParseRequestURI(ts.URL)
29-
require.NoError(t, err)
30-
31-
_, port, err := net.SplitHostPort(target.Host)
32-
require.NoError(t, err)
33-
34-
allowedURL := "http://localhost:" + port + "/foobar"
35-
allowedGlob := "http://localhost:" + port + "/glob/*"
36-
37-
c := NewResilientClient(
38-
ResilientClientWithMaxRetry(1),
39-
ResilientClientDisallowInternalIPs(),
40-
ResilientClientAllowInternalIPRequestsTo(allowedURL, allowedGlob),
41-
)
23+
func TestPrivateIPs(t *testing.T) {
24+
testCases := []struct {
25+
url string
26+
disallowInternalIPs bool
27+
allowedIP bool
28+
}{
29+
{
30+
url: "http://127.0.0.1/foobar",
31+
disallowInternalIPs: true,
32+
allowedIP: false,
33+
},
34+
{
35+
url: "http://localhost/foobar",
36+
disallowInternalIPs: true,
37+
allowedIP: false,
38+
},
39+
{
40+
url: "http://127.0.0.1:56789/test",
41+
disallowInternalIPs: true,
42+
allowedIP: false,
43+
},
44+
{
45+
url: "http://192.168.178.5:56789",
46+
disallowInternalIPs: true,
47+
allowedIP: false,
48+
},
49+
{
50+
url: "http://127.0.0.1:56789/foobar",
51+
disallowInternalIPs: true,
52+
allowedIP: true,
53+
},
54+
{
55+
url: "http://127.0.0.1:56789/glob/bar",
56+
disallowInternalIPs: true,
57+
allowedIP: true,
58+
},
59+
{
60+
url: "http://127.0.0.1:56789/glob/bar/baz",
61+
disallowInternalIPs: true,
62+
allowedIP: false,
63+
},
64+
{
65+
url: "http://127.0.0.1:56789/FOOBAR",
66+
disallowInternalIPs: true,
67+
allowedIP: false,
68+
},
69+
{
70+
url: "http://100.64.1.1:80/private",
71+
disallowInternalIPs: true,
72+
allowedIP: true,
73+
},
74+
{
75+
url: "http://100.64.1.1:80/route",
76+
disallowInternalIPs: true,
77+
allowedIP: false,
78+
},
79+
{
80+
url: "http://198.18.99.99/forbidden",
81+
disallowInternalIPs: true,
82+
allowedIP: false,
83+
},
84+
{
85+
// Even if in the allowed requests, no exceptions can be made.
86+
url: "http://198.18.99.99/allowed",
87+
disallowInternalIPs: true,
88+
allowedIP: false,
89+
},
90+
{
91+
url: "http://127.0.0.1",
92+
disallowInternalIPs: false,
93+
allowedIP: true,
94+
},
95+
{
96+
url: "http://192.168.178.5",
97+
disallowInternalIPs: false,
98+
allowedIP: true,
99+
},
100+
{
101+
url: "http://127.0.0.1:80/glob/bar",
102+
disallowInternalIPs: false,
103+
allowedIP: true,
104+
},
105+
{
106+
url: "http://100.64.1.1:80/route",
107+
disallowInternalIPs: false,
108+
allowedIP: true,
109+
},
110+
}
111+
for _, tt := range testCases {
112+
t.Run(
113+
fmt.Sprintf("%s should be allowed %v when disallowed internal IPs is %v", tt.url, tt.allowedIP, tt.disallowInternalIPs),
114+
func(t *testing.T) {
115+
options := []ResilientOptions{
116+
ResilientClientWithMaxRetry(0),
117+
ResilientClientWithConnectionTimeout(50 * time.Millisecond),
118+
}
119+
if tt.disallowInternalIPs {
120+
options = append(options, ResilientClientDisallowInternalIPs())
121+
options = append(options, ResilientClientAllowInternalIPRequestsTo(
122+
"http://127.0.0.1:56789/foobar",
123+
"http://127.0.0.1:56789/glob/*",
124+
"http://100.64.1.1:80/private",
125+
"http://198.18.99.99/allowed"))
126+
}
42127

43-
for i := 0; i < 10; i++ {
44-
for destination, passes := range map[string]bool{
45-
"http://127.0.0.1:" + port: false,
46-
"http://localhost:" + port: false,
47-
"http://192.168.178.5:" + port: false,
48-
allowedURL: true,
49-
"http://localhost:" + port + "/glob/bar": true,
50-
"http://localhost:" + port + "/glob/bar/baz": false,
51-
"http://localhost:" + port + "/FOOBAR": false,
52-
} {
53-
_, err := c.Get(destination)
54-
if !passes {
55-
require.Errorf(t, err, "dest = %s", destination)
56-
assert.Containsf(t, err.Error(), "is not a permitted destination", "dest = %s", destination)
57-
} else {
58-
require.NoErrorf(t, err, "dest = %s", destination)
59-
}
60-
}
128+
c := NewResilientClient(options...)
129+
_, err := c.Get(tt.url)
130+
if tt.allowedIP {
131+
assert.NotErrorIs(t, err, ssrf.ErrProhibitedIP)
132+
} else {
133+
assert.ErrorIs(t, err, ssrf.ErrProhibitedIP)
134+
}
135+
})
61136
}
62137
}
63138

‎httpx/ssrf.go

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func init() {
8989
ssrf.WithNetworks("tcp4", "tcp6"),
9090
ssrf.WithAllowedV4Prefixes(
9191
netip.MustParsePrefix("10.0.0.0/8"), // Private-Use (RFC 1918)
92+
netip.MustParsePrefix("100.64.0.0/10"), // Shared Address Space (RFC 6598)
9293
netip.MustParsePrefix("127.0.0.0/8"), // Loopback (RFC 1122, Section 3.2.1.3))
9394
netip.MustParsePrefix("169.254.0.0/16"), // Link Local (RFC 3927)
9495
netip.MustParsePrefix("172.16.0.0/12"), // Private-Use (RFC 1918)
@@ -109,6 +110,7 @@ func init() {
109110
ssrf.WithNetworks("tcp4"),
110111
ssrf.WithAllowedV4Prefixes(
111112
netip.MustParsePrefix("10.0.0.0/8"), // Private-Use (RFC 1918)
113+
netip.MustParsePrefix("100.64.0.0/10"), // Shared Address Space (RFC 6598)
112114
netip.MustParsePrefix("127.0.0.0/8"), // Loopback (RFC 1122, Section 3.2.1.3))
113115
netip.MustParsePrefix("169.254.0.0/16"), // Link Local (RFC 3927)
114116
netip.MustParsePrefix("172.16.0.0/12"), // Private-Use (RFC 1918)

0 commit comments

Comments
 (0)
Please sign in to comment.