From a75a64f165c71049172850cf1b8824591f63474d Mon Sep 17 00:00:00 2001 From: Emmanuel Gautier Date: Sat, 21 Dec 2024 00:38:22 +0100 Subject: [PATCH] feat: make weak jwt scan offline and faster --- .../jwt/weak_secret/weak_secret.go | 84 +++++++++++-------- .../jwt/weak_secret/weak_secret_test.go | 28 +------ 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/scan/broken_authentication/jwt/weak_secret/weak_secret.go b/scan/broken_authentication/jwt/weak_secret/weak_secret.go index 277878e..18d3f31 100644 --- a/scan/broken_authentication/jwt/weak_secret/weak_secret.go +++ b/scan/broken_authentication/jwt/weak_secret/weak_secret.go @@ -3,7 +3,6 @@ package weaksecret import ( "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/operation" - "github.com/cerberauth/vulnapi/internal/scan" "github.com/cerberauth/vulnapi/jwt" "github.com/cerberauth/vulnapi/report" "github.com/cerberauth/vulnapi/seclist" @@ -71,46 +70,63 @@ func ScanHandler(op *operation.Operation, securityScheme *auth.SecurityScheme) ( return r, err } - secretFound := false currentToken := valueWriter.GetToken().Raw - for _, secret := range jwtSecretDictionary { - if secret == "" { - continue - } - - newToken, err := valueWriter.SignWithKey([]byte(secret)) - if err != nil { - return r, nil - } + secret, err := bruteForceSecret(currentToken, jwtSecretDictionary, valueWriter) + if err != nil { + return r, err + } - if newToken != currentToken { - continue - } + if secret != "" { + r.WithData(&WeakSecretData{Secret: &secret}) + vulnReport.Fail() + } else { + vulnReport.Pass() + } + r.AddIssueReport(vulnReport).End() - newValidToken, err := jwt.NewJWTWriterWithValidClaims(valueWriter).SignWithKey([]byte(secret)) - if err != nil { - return r, nil - } + return r, nil +} - if err = securityScheme.SetAttackValue(newValidToken); err != nil { - return r, err - } - vsa, err := scan.ScanURL(op, securityScheme) - if err != nil { - return r, err - } - r.AddScanAttempt(vsa) +func bruteForceSecret(currentToken string, jwtSecretDictionary []string, valueWriter *jwt.JWTWriter) (string, error) { + type result struct { + secret string + err error + } - if scan.IsUnauthorizedStatusCodeOrSimilar(vsa.Response) { - continue - } + results := make(chan result, len(jwtSecretDictionary)) + localValueWriter := valueWriter.Clone() - secretFound = true - r.WithData(&WeakSecretData{Secret: &secret}) - break + for _, secret := range jwtSecretDictionary { + go func(secret string) { + if secret == "" { + results <- result{"", nil} + return + } + + newToken, err := localValueWriter.SignWithKey([]byte(secret)) + if err != nil { + results <- result{"", err} + return + } + + if newToken != currentToken { + results <- result{"", nil} + return + } + + results <- result{secret, nil} + }(secret) } - r.AddIssueReport(vulnReport.WithBooleanStatus(!secretFound)).End() + for range jwtSecretDictionary { + res := <-results + if res.err != nil { + return "", res.err + } + if res.secret != "" { + return res.secret, nil + } + } - return r, nil + return "", nil } diff --git a/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go b/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go index 1d3d8ef..bb9afee 100644 --- a/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go +++ b/scan/broken_authentication/jwt/weak_secret/weak_secret_test.go @@ -6,9 +6,7 @@ import ( "github.com/cerberauth/vulnapi/internal/auth" "github.com/cerberauth/vulnapi/internal/operation" - "github.com/cerberauth/vulnapi/internal/request" weaksecret "github.com/cerberauth/vulnapi/scan/broken_authentication/jwt/weak_secret" - "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -45,40 +43,28 @@ func TestWeakHMACSecretScanHandler_WithoutJWT(t *testing.T) { } func TestWeakHMACSecretScanHandler_Failed_WithWeakJWT(t *testing.T) { - client := request.GetDefaultClient() - httpmock.ActivateNonDefault(client.Client) - defer httpmock.DeactivateAndReset() - secret := "secret" token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M" securityScheme := auth.MustNewAuthorizationBearerSecurityScheme("token", &token) - operation := operation.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, client) - httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusOK, nil)) + operation := operation.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) report, err := weaksecret.ScanHandler(operation, securityScheme) assert.NoError(t, err) - assert.Equal(t, 1, httpmock.GetTotalCallCount()) assert.True(t, report.Issues[0].HasFailed()) assert.NotNil(t, report.Data) assert.Equal(t, &secret, report.Data.(*weaksecret.WeakSecretData).Secret) } func TestWeakHMACSecretScanHandler_Failed_WithExpiredJWTSignedWithWeakSecret(t *testing.T) { - client := request.GetDefaultClient() - httpmock.ActivateNonDefault(client.Client) - defer httpmock.DeactivateAndReset() - secret := "secret" token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTYyMzkwMjJ9.7BbIenT4-HobiMHaMUQdNcJ6lD_QQkKnImP9IprJFvU" securityScheme := auth.MustNewAuthorizationBearerSecurityScheme("token", &token) - operation := operation.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, client) - httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusOK, nil)) + operation := operation.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) report, err := weaksecret.ScanHandler(operation, securityScheme) assert.NoError(t, err) - assert.Equal(t, 1, httpmock.GetTotalCallCount()) assert.True(t, report.Issues[0].HasFailed()) assert.NotNil(t, report.Data) assert.Equal(t, &secret, report.Data.(*weaksecret.WeakSecretData).Secret) @@ -88,31 +74,23 @@ func TestWeakHMACSecretScanHandler_Passed_WithStrongerJWT(t *testing.T) { token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MWUarT7Q4e5DqnZbdr7VKw3rx9VW-CrvoVkfpllS4CY" securityScheme := auth.MustNewAuthorizationBearerSecurityScheme("token", &token) operation := operation.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) - httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusUnauthorized, nil)) report, err := weaksecret.ScanHandler(operation, securityScheme) assert.NoError(t, err) - assert.Equal(t, 0, httpmock.GetTotalCallCount()) assert.True(t, report.Issues[0].HasPassed()) assert.Nil(t, report.Data) } func TestWeakHMACSecretScanHandler_Failed_WithUnorderedClaims(t *testing.T) { - client := request.GetDefaultClient() - httpmock.ActivateNonDefault(client.Client) - defer httpmock.DeactivateAndReset() - secret := "secret" token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJuYmYiOjIwMTYyMzkwMjJ9.ymnE0GznV0dMkjANTQl8IqBSlTi9RFWfBeT42jBNrU4" securityScheme := auth.MustNewAuthorizationBearerSecurityScheme("token", &token) - operation := operation.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, client) - httpmock.RegisterResponder(operation.Method, operation.URL.String(), httpmock.NewBytesResponder(http.StatusOK, nil)) + operation := operation.MustNewOperation(http.MethodGet, "http://localhost:8080/", nil, nil) report, err := weaksecret.ScanHandler(operation, securityScheme) assert.NoError(t, err) - assert.Equal(t, 1, httpmock.GetTotalCallCount()) assert.True(t, report.Issues[0].HasFailed()) assert.NotNil(t, report.Data) assert.Equal(t, &secret, report.Data.(*weaksecret.WeakSecretData).Secret)