Skip to content

Commit

Permalink
Merge pull request #239 from cerberauth/weak-jwt-offline-faster-detec…
Browse files Browse the repository at this point in the history
…tion

Make weak JWT scan offline and faster
  • Loading branch information
emmanuelgautier authored Dec 20, 2024
2 parents a922459 + a75a64f commit 2862a49
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 59 deletions.
84 changes: 50 additions & 34 deletions scan/broken_authentication/jwt/weak_secret/weak_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
28 changes: 3 additions & 25 deletions scan/broken_authentication/jwt/weak_secret/weak_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 2862a49

Please sign in to comment.