-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathrawparam_test.go
More file actions
389 lines (335 loc) · 14 KB
/
rawparam_test.go
File metadata and controls
389 lines (335 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
package urlutil
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/julienschmidt/httprouter"
"github.com/stretchr/testify/require"
)
func TestUTF8URLEncoding(t *testing.T) {
exstring := '上'
expected := `e4%b8%8a`
val := getutf8hex(exstring)
require.Equalf(t, val, expected, "failed to url encode utf char expected %v but got %v", expected, val)
}
func TestParamEncoding(t *testing.T) {
testcases := []struct {
Payload string
Expected string
}{
{"1+AND+(SELECT+*+FROM+(SELECT(SLEEP(12)))nQIP)", "1+AND+(SELECT+*+FROM+(SELECT(SLEEP(12)))nQIP)"},
{"1 AND SELECT", "1+AND+SELECT"},
}
for _, v := range testcases {
val := ParamEncode(v.Payload)
require.Equalf(t, val, v.Expected, "failed to url encode payload expected %v got %v", v.Expected, val)
}
}
func TestRawParam(t *testing.T) {
p := NewParams()
p.Add("sqli", "1+AND+(SELECT+*+FROM+(SELECT(SLEEP(12)))nQIP)")
p.Add("xss", "<script>alert('XSS')</script>")
p.Add("xssiwthspace", "<svg id=alert(1) onload=eval(id)>")
p.Add("jsprotocol", "javascript://alert(1)")
// Note keys are sorted
expected := "jsprotocol=javascript://alert(1)&sqli=1+AND+(SELECT+*+FROM+(SELECT(SLEEP(12)))nQIP)&xss=<script>alert('XSS')</script>&xssiwthspace=<svg+id=alert(1)+onload=eval(id)>"
require.Equalf(t, p.Encode(), expected, "failed to encode parameters expected %v but got %v", expected, p.Encode())
}
func TestParamIntegration(t *testing.T) {
var routerErr error
expected := "/params?jsprotocol=javascript://alert(1)&sqli=1+AND+(SELECT+*+FROM+(SELECT(SLEEP(12)))nQIP)&xss=<script>alert('XSS')</script>&xssiwthspace=<svg+id=alert(1)+onload=eval(id)>"
router := httprouter.New()
router.GET("/params", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.RequestURI != expected {
routerErr = fmt.Errorf("expected %v but got %v", expected, r.RequestURI)
}
w.WriteHeader(http.StatusOK)
})
ts := httptest.NewServer(router)
defer ts.Close()
p := NewParams()
p.Add("sqli", "1+AND+(SELECT+*+FROM+(SELECT(SLEEP(12)))nQIP)")
p.Add("xss", "<script>alert('XSS')</script>")
p.Add("xssiwthspace", "<svg id=alert(1) onload=eval(id)>")
p.Add("jsprotocol", "javascript://alert(1)")
url, _ := url.Parse(ts.URL + "/params")
url.RawQuery = p.Encode()
_, err := http.Get(url.String())
require.Nil(t, err)
require.Nil(t, routerErr)
}
func TestPercentEncoding(t *testing.T) {
// From Burpsuite
expected := "%74%65%73%74%20%26%23%20%28%29%20%70%65%72%63%65%6E%74%20%5B%5D%7C%2A%20%65%6E%63%6F%64%69%6E%67"
payload := "test &# () percent []|* encoding"
value := PercentEncoding(payload)
require.Equalf(t, value, expected, "expected percentencoding to be %v but got %v", expected, value)
decoded, err := url.QueryUnescape(value)
require.Nil(t, err)
require.Equal(t, payload, decoded)
}
func TestGetParams(t *testing.T) {
values := url.Values{}
values.Add("sqli", "1+AND+(SELECT+*+FROM+(SELECT(SLEEP(12)))nQIP)")
values.Add("xss", "<script>alert('XSS')</script>")
p := GetParams(values)
require.NotNilf(t, p, "expected params but got nil")
require.Equalf(t, p.Get("sqli"), values.Get("sqli"), "malformed or missing value for param sqli expected %v but got %v", values.Get("sqli"), p.Get("sqli"))
require.Equalf(t, p.Get("xss"), values.Get("xss"), "malformed or missing value for param xss expected %v but got %v", values.Get("xss"), p.Get("xss"))
}
func TestURLEncode(t *testing.T) {
example := "\r\n"
got := URLEncodeWithEscapes(example)
require.Equalf(t, "%0D%0A", got, "failed to url encode characters")
// verify with stdlib
for r := 0; r < 20; r++ {
expected := url.QueryEscape(string(rune(r)))
got := URLEncodeWithEscapes(string(rune(r)))
require.Equalf(t, expected, got, "url encoding mismatch for non-printable char with ascii val:%v", r)
}
}
func TestURLDecode(t *testing.T) {
testcases := []struct {
url string
Expected string
}{
{
"/ctc/servlet/ConfigServlet?param=com.sap.ctc.util.FileSystemConfig;EXECUTE_CMD;CMDLINE=tasklist",
"param=com.sap.ctc.util.FileSystemConfig;EXECUTE_CMD;CMDLINE=tasklist",
},
}
for _, v := range testcases {
parsed, err := Parse(v.url)
require.Nilf(t, err, "failed to parse url %v", v.url)
require.Equalf(t, v.Expected, parsed.Query().Encode(), "failed to decode params in url %v expected %v got %v", v.url, v.Expected, parsed.Query())
}
}
func TestPathEncode(t *testing.T) {
testcases := []struct {
Input string
Expected string
Desc string
}{
// Space encoding - always %20 in paths
{"hello world", "hello%20world", "spaces encoded as %20"},
{"test+value", "test+value", "+ preserved as literal"},
// Special characters that need escaping in paths
{"path?query", "path%3Fquery", "? must be escaped in paths"},
{"path#fragment", "path%23fragment", "# must be escaped in paths"},
{"user@domain", "user%40domain", "@ must be escaped"},
// Characters that don't need escaping in paths (unlike query params)
{"key=value", "key=value", "= is literal in paths"},
{"param&other", "param&other", "& is literal in paths"},
// Control characters
{"test\nline", "test%0Aline", "newline encoded"},
{"test\tline", "test%09line", "tab encoded"},
// Non-ASCII characters
{"café", "caf%c3%a9", "unicode encoded"},
// Edge cases
{"", "", "empty string"},
{"/", "/", "forward slash preserved"},
{"../../../etc/passwd", "../../../etc/passwd", "path traversal sequences preserved"},
}
for _, v := range testcases {
result := PathEncode(v.Input)
require.Equalf(t, v.Expected, result, "%s: expected %q but got %q", v.Desc, v.Expected, result)
}
}
func TestPathDecode(t *testing.T) {
testcases := []struct {
Input string
Expected string
Desc string
}{
// Space decoding - only %20 becomes space
{"hello%20world", "hello world", "%20 decoded to space"},
{"test+value", "test+value", "+ preserved as literal (not decoded to space)"},
// Hex decoding
{"path%3Fquery", "path?query", "? decoded"},
{"path%23fragment", "path#fragment", "# decoded"},
{"user%40domain", "user@domain", "@ decoded"},
// Characters that don't need decoding
{"key=value", "key=value", "= preserved"},
{"param&other", "param&other", "& preserved"},
// Control characters
{"test%0Aline", "test\nline", "newline decoded"},
{"test%09line", "test\tline", "tab decoded"},
// Non-ASCII
{"caf%C3%A9", "café", "unicode decoded"},
// Invalid sequences should be preserved
{"test%GG", "test%GG", "invalid hex preserved"},
{"test%2", "test%2", "incomplete hex preserved"},
// Edge cases
{"", "", "empty string"},
{"/", "/", "forward slash preserved"},
{"../../../etc/passwd", "../../../etc/passwd", "path traversal preserved"},
}
for _, v := range testcases {
result, err := PathDecode(v.Input)
require.Nilf(t, err, "%s: unexpected error: %v", v.Desc, err)
require.Equalf(t, v.Expected, result, "%s: expected %q but got %q", v.Desc, v.Expected, result)
}
}
func TestPathEncodeDecodeRoundtrip(t *testing.T) {
testcases := []string{
"hello world",
"path?query#fragment",
"user@domain.com",
"key=value¶m=other",
"test\nwith\tcontrol\rchars",
"café with unicode",
"../../../etc/passwd",
"test+literal+plus",
}
for _, input := range testcases {
encoded := PathEncode(input)
decoded, err := PathDecode(encoded)
require.Nilf(t, err, "decode error for input %q", input)
require.Equalf(t, input, decoded, "roundtrip failed for %q: encoded=%q decoded=%q", input, encoded, decoded)
}
}
func TestPathVsParamEncodingDifferences(t *testing.T) {
testcases := []struct {
Input string
ExpectedPath string
ExpectedParam string
Desc string
}{
// Key difference: space encoding
{"hello world", "hello%20world", "hello+world", "space encoding difference"},
// + character handling
{"test+plus", "test+plus", "test+plus", "+ preserved in both"},
// & and = handling
{"key=val&other=test", "key=val&other=test", "key=val&other=test", "& and = preserved in both by default"},
// ? and # handling
{"query?test#frag", "query%3Ftest%23frag", "query?test#frag", "? and # encoded only in paths"},
}
for _, v := range testcases {
pathResult := PathEncode(v.Input)
paramResult := ParamEncode(v.Input)
require.Equalf(t, v.ExpectedPath, pathResult, "%s: path encoding mismatch", v.Desc)
require.Equalf(t, v.ExpectedParam, paramResult, "%s: param encoding mismatch", v.Desc)
}
}
func TestSQLInjectionPathEncoding(t *testing.T) {
testcases := []struct {
Name string
Input string
ExpectedEncoded string
ExpectedDecoded string
Description string
}{
{
Name: "SQL injection in path with mixed encoding",
Input: "/admin/1' OR 1=1 ?key=y'+1=1&key2=value2",
ExpectedEncoded: "/admin/1'%20OR%201=1%20%3Fkey=y'+1=1&key2=value2",
ExpectedDecoded: "/admin/1' OR 1=1 ?key=y'+1=1&key2=value2",
Description: "SQL injection path with spaces, quotes, and query-like syntax",
},
{
Name: "Path with SQL payload and question mark",
Input: "/user/1' OR 1=1?admin=true",
ExpectedEncoded: "/user/1'%20OR%201=1%3Fadmin=true",
ExpectedDecoded: "/user/1' OR 1=1?admin=true",
Description: "SQL injection with question mark that needs encoding in paths",
},
{
Name: "Complex SQL injection with multiple special chars",
Input: "/api/user/1' UNION SELECT * FROM users WHERE admin=1#comment",
ExpectedEncoded: "/api/user/1'%20UNION%20SELECT%20*%20FROM%20users%20WHERE%20admin=1%23comment",
ExpectedDecoded: "/api/user/1' UNION SELECT * FROM users WHERE admin=1#comment",
Description: "Complex SQL injection with spaces and hash that need encoding",
},
{
Name: "Path traversal with SQL injection",
Input: "/../../../etc/passwd' OR '1'='1",
ExpectedEncoded: "/../../../etc/passwd'%20OR%20'1'='1",
ExpectedDecoded: "/../../../etc/passwd' OR '1'='1",
Description: "Path traversal combined with SQL injection",
},
{
Name: "Already encoded SQL injection",
Input: "/admin/1' OR 1=1 --",
ExpectedEncoded: "/admin/1'%20OR%201=1%20--",
ExpectedDecoded: "/admin/1' OR 1=1 --",
Description: "SQL injection should be properly encoded",
},
}
for _, tc := range testcases {
t.Run(tc.Name, func(t *testing.T) {
// Test encoding
encoded := PathEncode(tc.Input)
require.Equalf(t, tc.ExpectedEncoded, encoded,
"%s - Encoding mismatch:\nInput: %q\nExpected: %q\nGot: %q",
tc.Description, tc.Input, tc.ExpectedEncoded, encoded)
// Test decoding
decoded, err := PathDecode(tc.Input)
require.Nilf(t, err, "%s - Decode error: %v", tc.Description, err)
require.Equalf(t, tc.ExpectedDecoded, decoded,
"%s - Decoding mismatch:\nInput: %q\nExpected: %q\nGot: %q",
tc.Description, tc.Input, tc.ExpectedDecoded, decoded)
// Test roundtrip: encode then decode
roundtrip, err := PathDecode(encoded)
require.Nilf(t, err, "%s - Roundtrip decode error: %v", tc.Description, err)
require.Equalf(t, tc.Input, roundtrip,
"%s - Roundtrip failed:\nOriginal: %q\nEncoded: %q\nDecoded: %q",
tc.Description, tc.Input, encoded, roundtrip)
})
}
}
func TestPathEncodingSecurityImplications(t *testing.T) {
// Test the key security difference: + vs %20 in SQL injection contexts
sqlPayload := "1 OR 1=1"
// Path encoding (always %20)
pathEncoded := PathEncode(sqlPayload)
require.Equal(t, "1%20OR%201=1", pathEncoded, "Path should encode spaces as %20")
// Param encoding (always +)
paramEncoded := ParamEncode(sqlPayload)
require.Equal(t, "1+OR+1=1", paramEncoded, "Params should encode spaces as +")
// Decoding behavior difference
pathDecoded, err := PathDecode("test+plus")
require.Nil(t, err)
require.Equal(t, "test+plus", pathDecoded, "Path decode should preserve + as literal")
pathDecodedSpace, err := PathDecode("test%20space")
require.Nil(t, err)
require.Equal(t, "test space", pathDecodedSpace, "Path decode should convert %20 to space")
t.Log("✓ Path encoding uses %20 for spaces (correct for path context)")
t.Log("✓ Param encoding uses + for spaces (correct for query context)")
t.Log("✓ Path decode treats + as literal (preventing confusion)")
t.Log("✓ Path decode converts %20 to space (standard percent decoding)")
}
func TestSpecificSQLInjectionPath(t *testing.T) {
// Test the specific path you mentioned
originalPath := "/admin/1'%20OR%201=1%20?key=y'+1=1&key2=value2"
// Test decoding - this should convert %20 to spaces
decoded, err := PathDecode(originalPath)
require.Nil(t, err, "Failed to decode path")
expectedDecoded := "/admin/1' OR 1=1 ?key=y'+1=1&key2=value2"
require.Equal(t, expectedDecoded, decoded,
"Decoded path mismatch:\nInput: %q\nExpected: %q\nGot: %q",
originalPath, expectedDecoded, decoded)
// Test encoding the decoded version - should re-encode spaces and ?
encoded := PathEncode(decoded)
expectedEncoded := "/admin/1'%20OR%201=1%20%3Fkey=y'+1=1&key2=value2"
require.Equal(t, expectedEncoded, encoded,
"Encoded path mismatch:\nInput: %q\nExpected: %q\nGot: %q",
decoded, expectedEncoded, encoded)
// Verify that the + signs are preserved as literals in both operations
require.Contains(t, decoded, "+1=1", "Plus signs should be preserved as literals during decode")
require.Contains(t, encoded, "+1=1", "Plus signs should be preserved as literals during encode")
// Verify that spaces are properly encoded as %20 (not +)
require.Contains(t, encoded, "%20OR%20", "Spaces should be encoded as %20 in paths")
require.NotContains(t, encoded, "+OR+", "Spaces should NOT be encoded as + in paths")
// Verify that ? is encoded in paths (it has special meaning)
require.Contains(t, encoded, "%3F", "Question mark should be encoded in paths")
// Log the transformation for clarity
t.Logf("Original (mixed encoding): %s", originalPath)
t.Logf("Decoded (human readable): %s", decoded)
t.Logf("Re-encoded (consistent): %s", encoded)
t.Log("✓ Percent-20 properly decoded to spaces")
t.Log("✓ + preserved as literal characters")
t.Log("✓ Spaces re-encoded as percent-20 (not +)")
t.Log("✓ ? encoded as percent-3F (has special meaning in paths)")
}