From c96d05be9dce78fa3b1514dcdde3e4a8021dd399 Mon Sep 17 00:00:00 2001 From: terrorbyte Date: Fri, 5 Dec 2025 13:37:18 -0700 Subject: [PATCH 1/2] Add a string based URL parameter shuffling function --- transform/transform.go | 41 +++++++++++++++++++++++++ transform/transform_test.go | 61 +++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 transform/transform.go create mode 100644 transform/transform_test.go diff --git a/transform/transform.go b/transform/transform.go new file mode 100644 index 0000000..fcaca82 --- /dev/null +++ b/transform/transform.go @@ -0,0 +1,41 @@ +package transform + +import ( + crand "crypto/rand" + "math" + "math/big" + "math/rand" + "net/url" + "strings" + "testing" + + "github.com/vulncheck-oss/go-exploit/output" +) + +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..c82de87 --- /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.Log("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.Log("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.Log("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.Log("Invalid URL should be empty", s) + } +} From 8ae1d1b1ea6cacbe3d436465d520b01603301351 Mon Sep 17 00:00:00 2001 From: terrorbyte Date: Fri, 5 Dec 2025 13:47:51 -0700 Subject: [PATCH 2/2] Add docs and change the error and log components in tests --- transform/transform.go | 1 + transform/transform_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/transform/transform.go b/transform/transform.go index fcaca82..17fb00f 100644 --- a/transform/transform.go +++ b/transform/transform.go @@ -12,6 +12,7 @@ import ( "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 { diff --git a/transform/transform_test.go b/transform/transform_test.go index c82de87..7799553 100644 --- a/transform/transform_test.go +++ b/transform/transform_test.go @@ -10,7 +10,7 @@ func TestShuffleParameters(t *testing.T) { for range 10 { s := ShuffleURLParameters(unsorted) if s == unsorted { - t.Log("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) + t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) triggered++ } else { break @@ -27,7 +27,7 @@ func TestShuffleParametersFullURL(t *testing.T) { for range 10 { s := ShuffleURLParameters(unsorted) if s == unsorted { - t.Log("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) + t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) triggered++ } else { break @@ -41,7 +41,7 @@ func TestShuffleParametersFullURL(t *testing.T) { for range 10 { s := ShuffleURLParameters(unsorted) if s == unsorted { - t.Log("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) + t.Error("Parameters are in order... it's possible but unlikely. Go buy a lotto ticket", s) triggered++ } else { break @@ -56,6 +56,6 @@ func TestShuffleParametersInvalid(t *testing.T) { unsorted := "!:!!!!!!" s := ShuffleURLParameters(unsorted) if s != "" { - t.Log("Invalid URL should be empty", s) + t.Fatal("Invalid URL should be empty", s) } }