CVE-2024-21495
Description
Versions of the package github.com/greenpau/caddy-security before 1.0.42 are vulnerable to Insecure Randomness due to using an insecure random number generation library which could possibly be predicted via a brute-force search. Attackers could use the potentially predictable nonce value used for authentication purposes in the OAuth flow to conduct OAuth replay attacks. In addition, insecure randomness is used while generating multifactor authentication (MFA) secrets and creating API keys in the database package.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/greenpau/caddy-securityGo | <= 1.0.42 | — |
Affected products
1- Range: 0
Patches
1ecd3725baf26security: addresses insecure randomness finding
9 files changed · +71 −85
internal/tests/random.go+19 −5 modified@@ -15,8 +15,10 @@ package tests import ( + "crypto/rand" "github.com/google/uuid" - "math/rand" + "io" + mathrand "math/rand" "strings" ) @@ -28,12 +30,24 @@ func NewID() string { // NewRandomString returns a random string. func NewRandomString(length int) string { chars := []rune("abcdefghijklmnopqrstuvwxyz0123456789") + charsLen := byte(36) + if length == 0 { length = 32 } - var b strings.Builder - for i := 0; i < length; i++ { - b.WriteRune(chars[rand.Intn(len(chars))]) + + b := make([]byte, length) + if _, err := io.ReadFull(rand.Reader, b); err != nil { + var sb strings.Builder + for i := 0; i < length; i++ { + sb.WriteRune(chars[mathrand.Intn(len(chars))]) + } + return sb.String() } - return b.String() + + for i, char := range b { + b[i] = byte(chars[char%charsLen]) + } + + return string(b) }
Makefile+3 −1 modified@@ -102,7 +102,9 @@ clean: qtest: covdir @echo "Perform quick tests ..." @#time richgo test -v -coverprofile=.coverage/coverage.out internal/tag/*.go + @#time richgo test -v -coverprofile=.coverage/coverage.out internal/testutils/*.go @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/util/data/... + @time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/util/... @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewConfig ./*.go @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewServer ./*.go @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/registry/... @@ -147,7 +149,7 @@ qtest: covdir @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/authproxy/... @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/identity/... @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/authn/backends/... - @time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run NewAPIKey ./pkg/identity/... + @#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run NewAPIKey ./pkg/identity/... @go tool cover -html=.coverage/coverage.out -o .coverage/coverage.html @#go tool cover -func=.coverage/coverage.out | grep -v "100.0" @go tool cover -func=.coverage/coverage.out
pkg/identity/api_key.go+2 −1 modified@@ -17,6 +17,7 @@ package identity import ( "github.com/greenpau/go-authcrunch/pkg/errors" "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/util" "golang.org/x/crypto/bcrypt" "time" ) @@ -80,7 +81,7 @@ func NewAPIKey(r *requests.Request) (*APIKey, error) { } p := &APIKey{ Comment: r.Key.Comment, - ID: GetRandomString(40), + ID: util.GetRandomString(40), Prefix: r.Key.Prefix, Payload: r.Key.Payload, Usage: r.Key.Usage,
pkg/identity/api_key_test.go+6 −5 modified@@ -21,6 +21,7 @@ import ( "github.com/greenpau/go-authcrunch/internal/tests" "github.com/greenpau/go-authcrunch/pkg/errors" "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/util" ) func TestNewAPIKey(t *testing.T) { @@ -37,7 +38,7 @@ func TestNewAPIKey(t *testing.T) { Key: requests.Key{ Usage: "api", Comment: "jsmith-api-key", - Payload: GetRandomStringFromRange(54, 72), + Payload: util.GetRandomStringFromRange(54, 72), }, }, want: map[string]interface{}{ @@ -53,7 +54,7 @@ func TestNewAPIKey(t *testing.T) { Usage: "api", Comment: "jsmith-api-key", Disabled: true, - Payload: GetRandomStringFromRange(54, 72), + Payload: util.GetRandomStringFromRange(54, 72), }, }, want: map[string]interface{}{ @@ -91,7 +92,7 @@ func TestNewAPIKey(t *testing.T) { req: &requests.Request{ Key: requests.Key{ Comment: "jsmith-api-key", - Payload: GetRandomStringFromRange(54, 72), + Payload: util.GetRandomStringFromRange(54, 72), Disabled: true, }, }, @@ -104,7 +105,7 @@ func TestNewAPIKey(t *testing.T) { Key: requests.Key{ Usage: "foo", Comment: "jsmith-api-key", - Payload: GetRandomStringFromRange(54, 72), + Payload: util.GetRandomStringFromRange(54, 72), Disabled: true, }, }, @@ -116,7 +117,7 @@ func TestNewAPIKey(t *testing.T) { req: &requests.Request{ Key: requests.Key{ Usage: "api", - Payload: GetRandomStringFromRange(54, 72), + Payload: util.GetRandomStringFromRange(54, 72), Disabled: true, }, },
pkg/identity/database.go+2 −1 modified@@ -20,6 +20,7 @@ import ( "github.com/greenpau/go-authcrunch/internal/utils" "github.com/greenpau/go-authcrunch/pkg/errors" "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/util" "github.com/greenpau/versioned" "io/ioutil" "os" @@ -566,7 +567,7 @@ func (db *Database) AddAPIKey(r *requests.Request) error { if err != nil { return errors.ErrAddAPIKey.WithArgs(r.Key.Usage, err) } - s := GetRandomStringFromRange(72, 96) + s := util.GetRandomStringFromRange(72, 96) failCount := 0 for { hk, err := NewPassword(s)
pkg/identity/mfa_token.go+2 −1 modified@@ -36,6 +36,7 @@ import ( "github.com/greenpau/go-authcrunch/pkg/errors" "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/util" ) // MfaTokenBundle is a collection of public keys. @@ -99,7 +100,7 @@ func (b *MfaTokenBundle) Size() int { // NewMfaToken returns an instance of MfaToken. func NewMfaToken(req *requests.Request) (*MfaToken, error) { p := &MfaToken{ - ID: GetRandomString(40), + ID: util.GetRandomString(40), CreatedAt: time.Now().UTC(), Parameters: make(map[string]string), Flags: make(map[string]bool),
pkg/identity/public_key.go+2 −1 modified@@ -24,6 +24,7 @@ import ( "fmt" "github.com/greenpau/go-authcrunch/pkg/errors" "github.com/greenpau/go-authcrunch/pkg/requests" + "github.com/greenpau/go-authcrunch/pkg/util" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/ssh" "strconv" @@ -87,7 +88,7 @@ func (b *PublicKeyBundle) Size() int { func NewPublicKey(r *requests.Request) (*PublicKey, error) { p := &PublicKey{ Comment: r.Key.Comment, - ID: GetRandomString(40), + ID: util.GetRandomString(40), Payload: r.Key.Payload, Usage: r.Key.Usage, CreatedAt: time.Now().UTC(),
pkg/identity/random.go+0 −55 removed@@ -1,55 +0,0 @@ -// Copyright 2022 Paul Greenberg greenpau@outlook.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package identity - -import ( - "math/rand" - "time" -) - -const charset = "abcdefghijklmnopqrstuvwxyz" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -var seed *rand.Rand = rand.New( - rand.NewSource(time.Now().UnixNano()), -) - -func gen(length int, charset string) string { - b := make([]byte, length) - for i := range b { - b[i] = charset[seed.Intn(len(charset))] - } - return string(b) -} - -// GetRandomString returns X character long random string. -func GetRandomString(i int) string { - if i < 1 { - i = 40 - } - return gen(i, charset) -} - -// GetRandomStringFromRange generates random string of a random length. The -// random lenght is bounded by a and b. -func GetRandomStringFromRange(a, b int) string { - var i int - if a > b { - i = rand.Intn(a-b) + b - } else { - i = rand.Intn(b-a) + a - } - return gen(i, charset) -}
pkg/util/random.go+35 −15 modified@@ -15,9 +15,12 @@ package util import ( + "crypto/rand" "encoding/base32" - "math/rand" - "time" + "io" + // "math" + "math/big" + mathrand "math/rand" "unicode" ) @@ -33,15 +36,32 @@ var charsetTable = &unicode.RangeTable{ LatinOffset: 1, } -var seed *rand.Rand = rand.New( - rand.NewSource(time.Now().UnixNano()), -) +func genRandInt(i int) uint32 { + n, err := rand.Int(rand.Reader, big.NewInt(int64(i))) + if err != nil { + return uint32(mathrand.Intn(i)) + } + //if n.Uint64() > math.MaxUint32+1 { + // return uint32(n.Uint64() & uint32(0xFFFFFFFF)) + //} + return uint32(n.Uint64()) +} -func gen(length int, charset string) string { +func gen(length uint32, charset string) string { + charsetLen := byte(len(charset)) b := make([]byte, length) - for i := range b { - b[i] = charset[seed.Intn(len(charset))] + if _, err := io.ReadFull(rand.Reader, b); err != nil { + // for i uint32 := 0; i < length; i++ { + for i := uint32(0); i < length; { + b[i] = charset[mathrand.Intn(len(charset))] + } + return string(b) } + + for i, char := range b { + b[i] = byte(charset[char%charsetLen]) + } + return string(b) } @@ -50,17 +70,17 @@ func GetRandomString(i int) string { if i < 1 { i = 40 } - return gen(i, charset) + return gen(uint32(i), charset) } // GetRandomStringFromRange generates random string of a random length. The // random lenght is bounded by a and b. func GetRandomStringFromRange(a, b int) string { - var i int + var i uint32 if a > b { - i = rand.Intn(a-b) + b + i = genRandInt(a-b) + uint32(b) } else { - i = rand.Intn(b-a) + a + i = genRandInt(b-a) + uint32(a) } return gen(i, charset) } @@ -75,11 +95,11 @@ func GetRandomEncodedStringFromRange(a, b int) string { // GetRandomStringFromRangeWithCharset generates random string of a random length. The // random lenght is bounded by a and b. The charset is provided. func GetRandomStringFromRangeWithCharset(a, b int, cs string) string { - var i int + var i uint32 if a > b { - i = rand.Intn(a-b) + b + i = genRandInt(a-b) + uint32(b) } else { - i = rand.Intn(b-a) + a + i = genRandInt(b-a) + uint32(a) } return gen(i, cs) }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-c7vf-m394-m4x4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-21495ghsaADVISORY
- github.com/greenpau/caddy-securityghsaPACKAGE
- blog.trailofbits.com/2023/09/18/security-flaws-in-an-sso-plugin-for-caddyghsaWEB
- github.com/greenpau/caddy-security/issues/265ghsaWEB
- github.com/greenpau/go-authcrunch/commit/ecd3725baf2683eb1519bb3c81ae41085fbf7dc2ghsaWEB
- security.snyk.io/vuln/SNYK-GOLANG-GITHUBCOMGREENPAUCADDYSECURITY-6248275ghsaWEB
- blog.trailofbits.com/2023/09/18/security-flaws-in-an-sso-plugin-for-caddy/mitre
News mentions
0No linked articles in our index yet.