Skip to content

Commit e37e979

Browse files
authored
Add a string based URL parameter shuffling function (#486)
* Add a string based URL parameter shuffling function * Add docs and change the error and log components in tests
1 parent 9362a98 commit e37e979

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed

transform/transform.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package transform
2+
3+
import (
4+
crand "crypto/rand"
5+
"math"
6+
"math/big"
7+
"math/rand"
8+
"net/url"
9+
"strings"
10+
"testing"
11+
12+
"github.com/vulncheck-oss/go-exploit/output"
13+
)
14+
15+
// ShuffleURLParameters will take a URL or path with URL parameters and extract the query parameters and cryptographically shuffle the parameters.
16+
func ShuffleURLParameters(request string) string {
17+
p, err := url.Parse(request)
18+
if err != nil {
19+
if !testing.Testing() {
20+
output.PrintfFrameworkError("Could not parse URL: %s", err.Error())
21+
}
22+
23+
return ""
24+
}
25+
// this feels a bit hacky, but net/url.Values is a map[string][]string which has some side effects:
26+
// the map is "randomized" already but if there are multiple of the same parameters they will be
27+
// returned together in order since the slice is not randomized. My solution to actually randomize
28+
// the whole thing is to split the raw values into an []string and then shuffle that.
29+
30+
b, _ := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
31+
r := rand.New(rand.NewSource(b.Int64()))
32+
a := []string{}
33+
for key, value := range p.Query() {
34+
for _, v := range value {
35+
a = append(a, key+`=`+v)
36+
}
37+
}
38+
r.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })
39+
p.RawQuery = strings.Join(a, "&")
40+
41+
return p.String()
42+
}

transform/transform_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package transform
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestShuffleParameters(t *testing.T) {
8+
unsorted := "/?a=1&b=2&c=3&d=4&e=5&f=6&g=7&h=8&j=9&k=10&j=11&k=12&l=13&m=14&n=15&o=16&p=17&q=18&r=19&s=20&t=21&u=22&v=23&w=24&x=25&y=26&z=27&xxx=AAAA&xxx=BBBB&xxx=CCCC&xxx=DDDD"
9+
triggered := 0
10+
for range 10 {
11+
s := ShuffleURLParameters(unsorted)
12+
if s == unsorted {
13+
t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s)
14+
triggered++
15+
} else {
16+
break
17+
}
18+
}
19+
if triggered > 2 {
20+
t.Fatal("Multiple sort generations is unlikely, something is wrong with randomness")
21+
}
22+
}
23+
24+
func TestShuffleParametersFullURL(t *testing.T) {
25+
unsorted := "vulncheck.com/?a=1&b=2&c=3&d=4&e=5&f=6&g=7&h=8&j=9&k=10&j=11&k=12&l=13&m=14&n=15&o=16&p=17&q=18&r=19&s=20&t=21&u=22&v=23&w=24&x=25&y=26&z=27&xxx=AAAA&xxx=BBBB&xxx=CCCC&xxx=DDDD"
26+
triggered := 0
27+
for range 10 {
28+
s := ShuffleURLParameters(unsorted)
29+
if s == unsorted {
30+
t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s)
31+
triggered++
32+
} else {
33+
break
34+
}
35+
}
36+
if triggered > 2 {
37+
t.Fatal("Multiple sort generations is unlikely, something is wrong with randomness")
38+
}
39+
unsorted = "https://vulncheck.com/?a=1&b=2&c=3&d=4&e=5&f=6&g=7&h=8&j=9&k=10&j=11&k=12&l=13&m=14&n=15&o=16&p=17&q=18&r=19&s=20&t=21&u=22&v=23&w=24&x=25&y=26&z=27&xxx=AAAA&xxx=BBBB&xxx=CCCC&xxx=DDDD"
40+
triggered = 0
41+
for range 10 {
42+
s := ShuffleURLParameters(unsorted)
43+
if s == unsorted {
44+
t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s)
45+
triggered++
46+
} else {
47+
break
48+
}
49+
}
50+
if triggered > 2 {
51+
t.Fatal("Multiple sort generations is unlikely, something is wrong with randomness")
52+
}
53+
}
54+
55+
func TestShuffleParametersInvalid(t *testing.T) {
56+
unsorted := "!:!!!!!!"
57+
s := ShuffleURLParameters(unsorted)
58+
if s != "" {
59+
t.Fatal("Invalid URL should be empty", s)
60+
}
61+
}

0 commit comments

Comments
 (0)