@@ -5,59 +5,122 @@ package httpx
55
66import (
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://127.0.0.1" ,
81+ disallowInternalIPs : false ,
82+ allowedIP : true ,
83+ },
84+ {
85+ url : "http://192.168.178.5" ,
86+ disallowInternalIPs : false ,
87+ allowedIP : true ,
88+ },
89+ {
90+ url : "http://127.0.0.1:80/glob/bar" ,
91+ disallowInternalIPs : false ,
92+ allowedIP : true ,
93+ },
94+ {
95+ url : "http://100.64.1.1:80/route" ,
96+ disallowInternalIPs : false ,
97+ allowedIP : true ,
98+ },
99+ }
100+ for _ , tt := range testCases {
101+ t .Run (
102+ fmt .Sprintf ("%s should be allowed %v when disallowed internal IPs is %v" , tt .url , tt .allowedIP , tt .disallowInternalIPs ),
103+ func (t * testing.T ) {
104+ options := []ResilientOptions {
105+ ResilientClientWithMaxRetry (0 ),
106+ ResilientClientWithConnectionTimeout (50 * time .Millisecond ),
107+ }
108+ if tt .disallowInternalIPs {
109+ options = append (options , ResilientClientDisallowInternalIPs ())
110+ options = append (options , ResilientClientAllowInternalIPRequestsTo (
111+ "http://127.0.0.1:56789/foobar" ,
112+ "http://127.0.0.1:56789/glob/*" ,
113+ "http://100.64.1.1:80/private" ))
114+ }
42115
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- }
116+ c := NewResilientClient (options ... )
117+ _ , err := c .Get (tt .url )
118+ if tt .allowedIP {
119+ assert .NotErrorIs (t , err , ssrf .ErrProhibitedIP )
120+ } else {
121+ assert .ErrorIs (t , err , ssrf .ErrProhibitedIP )
122+ }
123+ })
61124 }
62125}
63126
0 commit comments