CVE-2024-21495 (published 2024-02-17T05:15Z)

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.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/greenpau/go-authcrunch
commit_details: repo_url: https://github.com/greenpau/go-authcrunch
author: Paul Greenberg
author_date: 2023-12-02 11:28:20-05:00
msg: security: addresses insecure randomness finding

See also: https://github.com/greenpau/caddy-security/issues/265
repository: repo_url: https://github.com/greenpau/go-authcrunch
repo_name: greenpau/go-authcrunch
description: AuthCrunch provides Authentication, Authorization, and Accounting (AAA) Security Functions (SF) in Golang
repo_language: Go
owner: greenpau
file_changes: filename: Makefile
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Shell
filename: random.go
change_type: ModificationType.MODIFY
diff: @@ -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)
 }

programming_language: Go
filename: api_key.go
change_type: ModificationType.MODIFY
diff: @@ -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,

programming_language: Go
filename: api_key_test.go
change_type: ModificationType.MODIFY
diff: @@ -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,
 				},
 			},

programming_language: Go
filename: database.go
change_type: ModificationType.MODIFY
diff: @@ -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)

programming_language: Go
filename: mfa_token.go
change_type: ModificationType.MODIFY
diff: @@ -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),

programming_language: Go
filename: public_key.go
change_type: ModificationType.MODIFY
diff: @@ -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(),

programming_language: Go
filename: random.go
change_type: ModificationType.DELETE
diff: @@ -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)
-}

programming_language: unknown
filename: random.go
change_type: ModificationType.MODIFY
diff: @@ -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)
 }

programming_language: Go