diff --git a/transform/transform.go b/transform/transform.go new file mode 100644 index 0000000..17fb00f --- /dev/null +++ b/transform/transform.go @@ -0,0 +1,42 @@ +package transform + +import ( + crand "crypto/rand" + "math" + "math/big" + "math/rand" + "net/url" + "strings" + "testing" + + "github.com/vulncheck-oss/go-exploit/output" +) + +// ShuffleURLParameters will take a URL or path with URL parameters and extract the query parameters and cryptographically shuffle the parameters. +func ShuffleURLParameters(request string) string { + p, err := url.Parse(request) + if err != nil { + if !testing.Testing() { + output.PrintfFrameworkError("Could not parse URL: %s", err.Error()) + } + + return "" + } + // this feels a bit hacky, but net/url.Values is a map[string][]string which has some side effects: + // the map is "randomized" already but if there are multiple of the same parameters they will be + // returned together in order since the slice is not randomized. My solution to actually randomize + // the whole thing is to split the raw values into an []string and then shuffle that. + + b, _ := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) + r := rand.New(rand.NewSource(b.Int64())) + a := []string{} + for key, value := range p.Query() { + for _, v := range value { + a = append(a, key+`=`+v) + } + } + r.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] }) + p.RawQuery = strings.Join(a, "&") + + return p.String() +} diff --git a/transform/transform_test.go b/transform/transform_test.go new file mode 100644 index 0000000..7799553 --- /dev/null +++ b/transform/transform_test.go @@ -0,0 +1,61 @@ +package transform + +import ( + "testing" +) + +func TestShuffleParameters(t *testing.T) { + 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" + triggered := 0 + for range 10 { + s := ShuffleURLParameters(unsorted) + if s == unsorted { + t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) + triggered++ + } else { + break + } + } + if triggered > 2 { + t.Fatal("Multiple sort generations is unlikely, something is wrong with randomness") + } +} + +func TestShuffleParametersFullURL(t *testing.T) { + 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" + triggered := 0 + for range 10 { + s := ShuffleURLParameters(unsorted) + if s == unsorted { + t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) + triggered++ + } else { + break + } + } + if triggered > 2 { + t.Fatal("Multiple sort generations is unlikely, something is wrong with randomness") + } + 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" + triggered = 0 + for range 10 { + s := ShuffleURLParameters(unsorted) + if s == unsorted { + t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) + triggered++ + } else { + break + } + } + if triggered > 2 { + t.Fatal("Multiple sort generations is unlikely, something is wrong with randomness") + } +} + +func TestShuffleParametersInvalid(t *testing.T) { + unsorted := "!:!!!!!!" + s := ShuffleURLParameters(unsorted) + if s != "" { + t.Fatal("Invalid URL should be empty", s) + } +}