Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/websocket/websocket_test.go
4538 views
1
package websocket
2
3
import (
4
"net/url"
5
"testing"
6
7
"github.com/stretchr/testify/require"
8
urlutil "github.com/projectdiscovery/utils/url"
9
)
10
11
// resolveAddress mirrors the path resolution logic in executeRequestWithPayloads.
12
// it parses the template address and the input URL then applies the path rule.
13
func resolveAddress(templateAddress, inputURL string) (string, error) {
14
parsedAddress, err := url.Parse(templateAddress)
15
if err != nil {
16
return "", err
17
}
18
parsed, err := urlutil.Parse(inputURL)
19
if err != nil {
20
return "", err
21
}
22
if parsedAddress.Path == "" || parsedAddress.Path == "/" {
23
parsedAddress.Path = parsed.Path
24
}
25
return parsedAddress.String(), nil
26
}
27
28
func TestAddressResolution(t *testing.T) {
29
tests := []struct {
30
name string
31
templateAddress string
32
inputURL string
33
expected string
34
}{
35
// template already has a path so we keep it and don't double
36
{
37
name: "same path in both - no doubling",
38
templateAddress: "wss://jenkins.cloud/cli/ws",
39
inputURL: "https://jenkins.cloud/cli/ws",
40
expected: "wss://jenkins.cloud/cli/ws",
41
},
42
{
43
name: "different paths - template path preserved",
44
templateAddress: "wss://example.com/ws/connect",
45
inputURL: "https://example.com/api/v1",
46
expected: "wss://example.com/ws/connect",
47
},
48
{
49
name: "deep template path preserved",
50
templateAddress: "wss://example.com/a/b/c/d",
51
inputURL: "https://example.com/x/y",
52
expected: "wss://example.com/a/b/c/d",
53
},
54
{
55
name: "template path with trailing slash preserved",
56
templateAddress: "wss://example.com/ws/",
57
inputURL: "https://example.com/other",
58
expected: "wss://example.com/ws/",
59
},
60
61
// when the template has no path we fall back to the input path
62
{
63
name: "no template path - input path used",
64
templateAddress: "wss://example.com",
65
inputURL: "https://example.com/api/ws",
66
expected: "wss://example.com/api/ws",
67
},
68
{
69
name: "root template path - input path used",
70
templateAddress: "wss://example.com/",
71
inputURL: "https://example.com/chat/ws",
72
expected: "wss://example.com/chat/ws",
73
},
74
{
75
name: "no paths on either side",
76
templateAddress: "wss://example.com",
77
inputURL: "https://example.com",
78
expected: "wss://example.com",
79
},
80
{
81
name: "root template, root input",
82
templateAddress: "wss://example.com/",
83
inputURL: "https://example.com/",
84
expected: "wss://example.com/",
85
},
86
87
// ports should not affect path resolution
88
{
89
name: "template with port and path",
90
templateAddress: "wss://example.com:8443/ws",
91
inputURL: "https://example.com:8443/api",
92
expected: "wss://example.com:8443/ws",
93
},
94
{
95
name: "template with port, no path - input path used",
96
templateAddress: "ws://example.com:9090",
97
inputURL: "http://example.com:9090/stream",
98
expected: "ws://example.com:9090/stream",
99
},
100
{
101
name: "ws scheme with port and deep input path",
102
templateAddress: "ws://example.com:8080",
103
inputURL: "http://example.com:8080/api/v2/ws",
104
expected: "ws://example.com:8080/api/v2/ws",
105
},
106
107
// query strings should stay with their respective URLs
108
{
109
name: "template with query string preserved",
110
templateAddress: "wss://example.com/ws?token=abc",
111
inputURL: "https://example.com/other",
112
expected: "wss://example.com/ws?token=abc",
113
},
114
{
115
name: "input query string not leaked when template has path",
116
templateAddress: "wss://example.com/ws",
117
inputURL: "https://example.com/api?key=secret",
118
expected: "wss://example.com/ws",
119
},
120
{
121
name: "no template path - input path used but not query",
122
templateAddress: "wss://example.com",
123
inputURL: "https://example.com/stream?v=1",
124
expected: "wss://example.com/stream",
125
},
126
127
// both ws and wss schemes should behave the same way
128
{
129
name: "ws scheme template path preserved",
130
templateAddress: "ws://example.com/plain",
131
inputURL: "http://example.com/other",
132
expected: "ws://example.com/plain",
133
},
134
{
135
name: "wss scheme no path - input path used",
136
templateAddress: "wss://secure.example.com",
137
inputURL: "https://secure.example.com/endpoint",
138
expected: "wss://secure.example.com/endpoint",
139
},
140
141
// same logic applies to IP-based targets
142
{
143
name: "IPv4 template with path",
144
templateAddress: "ws://192.168.1.1/ws",
145
inputURL: "http://192.168.1.1/api",
146
expected: "ws://192.168.1.1/ws",
147
},
148
{
149
name: "IPv4 template no path - input path used",
150
templateAddress: "ws://192.168.1.1",
151
inputURL: "http://192.168.1.1/metrics/ws",
152
expected: "ws://192.168.1.1/metrics/ws",
153
},
154
{
155
name: "IPv4 with port, no path",
156
templateAddress: "ws://10.0.0.1:3000",
157
inputURL: "http://10.0.0.1:3000/graphql/ws",
158
expected: "ws://10.0.0.1:3000/graphql/ws",
159
},
160
161
// patterns from actual templates and bug reports
162
{
163
name: "jenkins websocket - the original bug report",
164
templateAddress: "wss://jenkins-ci.corp.cloud/cli/ws",
165
inputURL: "https://jenkins-ci.corp.cloud/cli/ws",
166
expected: "wss://jenkins-ci.corp.cloud/cli/ws",
167
},
168
{
169
name: "grafana live websocket",
170
templateAddress: "wss://grafana.local/api/live/ws",
171
inputURL: "https://grafana.local/api/live/ws",
172
expected: "wss://grafana.local/api/live/ws",
173
},
174
{
175
name: "generic host-only template with target path",
176
templateAddress: "wss://target.example.com",
177
inputURL: "https://target.example.com/socket.io/ws",
178
expected: "wss://target.example.com/socket.io/ws",
179
},
180
}
181
182
for _, tt := range tests {
183
t.Run(tt.name, func(t *testing.T) {
184
result, err := resolveAddress(tt.templateAddress, tt.inputURL)
185
require.NoError(t, err)
186
require.Equal(t, tt.expected, result)
187
})
188
}
189
}
190
191
func TestGetAddress(t *testing.T) {
192
tests := []struct {
193
name string
194
input string
195
want string
196
wantErr bool
197
}{
198
{
199
name: "ws scheme returns host",
200
input: "ws://example.com/path",
201
want: "example.com",
202
},
203
{
204
name: "wss scheme returns host with port",
205
input: "wss://example.com:8443/path",
206
want: "example.com:8443",
207
},
208
{
209
name: "ws with non-standard port",
210
input: "ws://example.com:9090",
211
want: "example.com:9090",
212
},
213
{
214
name: "wss with standard port",
215
input: "wss://example.com:443/ws",
216
want: "example.com:443",
217
},
218
{
219
name: "ws IPv4",
220
input: "ws://192.168.1.1/ws",
221
want: "192.168.1.1",
222
},
223
{
224
name: "ws IPv4 with port",
225
input: "ws://192.168.1.1:8080/ws",
226
want: "192.168.1.1:8080",
227
},
228
{
229
name: "http scheme rejected",
230
input: "http://example.com",
231
wantErr: true,
232
},
233
{
234
name: "https scheme rejected",
235
input: "https://example.com",
236
wantErr: true,
237
},
238
{
239
name: "ftp scheme rejected",
240
input: "ftp://example.com",
241
wantErr: true,
242
},
243
{
244
name: "invalid URL",
245
input: "://broken",
246
wantErr: true,
247
},
248
}
249
250
for _, tt := range tests {
251
t.Run(tt.name, func(t *testing.T) {
252
got, err := getAddress(tt.input)
253
if tt.wantErr {
254
require.Error(t, err)
255
return
256
}
257
require.NoError(t, err)
258
require.Equal(t, tt.want, got)
259
})
260
}
261
}
262
263