Skip to content

Commit

Permalink
feat: perform dictionnary attack against jwt with hmac alg
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelgautier committed Mar 25, 2024
1 parent 11b6b6d commit 41fa322
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 8 deletions.
8 changes: 8 additions & 0 deletions jwt/hmac_alg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package jwt

import jwtlib "github.com/golang-jwt/jwt/v5"

func (j *JWTWriter) IsHMACAlg() bool {
_, ok := j.Token.Method.(*jwtlib.SigningMethodHMAC)
return ok
}
34 changes: 34 additions & 0 deletions jwt/hmac_alg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package jwt_test

import (
"testing"

"github.com/cerberauth/vulnapi/jwt"
jwtlib "github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
)

func TestIsHMACAlgWithHS256(t *testing.T) {
token := jwtlib.New(jwtlib.SigningMethodHS256)
tokenString, _ := token.SignedString([]byte(""))
jwtWriter, err := jwt.NewJWTWriter(tokenString)

assert.NoError(t, err)
assert.True(t, jwtWriter.IsHMACAlg())
}

func TestIsHMACAlgWithRSA(t *testing.T) {
privateKeyData := []byte(`
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIKwkZA+Y19xPuGLCkk+JkrTnCo5bQvJcf0MdC73xg473
-----END PRIVATE KEY-----
`)

key, _ := jwtlib.ParseEdPrivateKeyFromPEM(privateKeyData)
token := jwtlib.New(jwtlib.SigningMethodEdDSA)
tokenString, _ := token.SignedString(key)
jwtWriter, err := jwt.NewJWTWriter(tokenString)

assert.NoError(t, err)
assert.False(t, jwtWriter.IsHMACAlg())
}
48 changes: 46 additions & 2 deletions scan/jwt/weak_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/cerberauth/vulnapi/internal/scan"
"github.com/cerberauth/vulnapi/jwt"
"github.com/cerberauth/vulnapi/report"
"github.com/cerberauth/vulnapi/seclist"
)

const (
Expand All @@ -14,6 +15,10 @@ const (
WeakSecretVulnerabilityDescription = "JWT secret is weak and can be easily guessed."
)

var defaultJwtSecretDictionary = []string{"secret", "password", "123456", "changeme", "admin", "token"}

const jwtSecretDictionarySeclistUrl = "https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/scraped-JWT-secrets.txt"

func BlankSecretScanHandler(operation *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) {
r := report.NewScanReport()
if !ShouldBeScanned(ss) {
Expand Down Expand Up @@ -44,10 +49,49 @@ func BlankSecretScanHandler(operation *request.Operation, ss auth.SecurityScheme
return r, nil
}

func DictSecretScanHandler(o *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) {
func WeakHMACSecretScanHandler(o *request.Operation, ss auth.SecurityScheme) (*report.ScanReport, error) {
r := report.NewScanReport()
if !ShouldBeScanned(ss) {
return r, nil
}

// TODO: Use a dictionary attack to try finding the secret
valueWriter := ss.GetValidValueWriter().(*jwt.JWTWriter)
if !valueWriter.IsHMACAlg() {
return r, nil
}

jwtSecretDictionary := defaultJwtSecretDictionary
if secretDictionnaryFromSeclist, err := seclist.NewSecListFromURL("JWT Secrets Dictionnary", jwtSecretDictionarySeclistUrl); err == nil {
jwtSecretDictionary = secretDictionnaryFromSeclist.Items

Check warning on line 65 in scan/jwt/weak_secret.go

View check run for this annotation

Codecov / codecov/patch

scan/jwt/weak_secret.go#L65

Added line #L65 was not covered by tests
}

for _, secret := range jwtSecretDictionary {
newToken, err := valueWriter.SignWithKey([]byte(secret))
if err != nil {
return r, nil

Check warning on line 71 in scan/jwt/weak_secret.go

View check run for this annotation

Codecov / codecov/patch

scan/jwt/weak_secret.go#L71

Added line #L71 was not covered by tests
}

if newToken != valueWriter.Token.Raw {
continue
}

ss.SetAttackValue(newToken)
vsa, err := scan.ScanURL(o, &ss)
r.AddScanAttempt(vsa)
if err != nil {
return r, err

Check warning on line 82 in scan/jwt/weak_secret.go

View check run for this annotation

Codecov / codecov/patch

scan/jwt/weak_secret.go#L82

Added line #L82 was not covered by tests
}

if err := scan.DetectNotExpectedResponse(vsa.Response); err != nil {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: WeakSecretVulnerabilitySeverityLevel,
Name: WeakSecretVulnerabilityName,
Description: WeakSecretVulnerabilityDescription,
Operation: o,
})
break
}
}

r.End()

Expand Down
62 changes: 57 additions & 5 deletions scan/jwt/weak_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ import (
)

func TestBlankSecretScanHandlerWithoutJwt(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil)

httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(405, nil))

report, err := jwt.BlankSecretScanHandler(operation, securityScheme)

assert.NoError(t, err)
Expand All @@ -42,3 +37,60 @@ func TestBlankSecretScanHandler(t *testing.T) {
assert.Equal(t, 1, httpmock.GetTotalCallCount())
assert.False(t, report.HasVulnerabilityReport())
}

func TestWeakHMACSecretScanHandlerWithoutJwt(t *testing.T) {
securityScheme := auth.NewNoAuthSecurityScheme()
operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil)

report, err := jwt.WeakHMACSecretScanHandler(operation, securityScheme)

assert.NoError(t, err)
assert.Equal(t, 0, httpmock.GetTotalCallCount())
assert.False(t, report.HasVulnerabilityReport())
}

func TestWeakHMACSecretScanHandlerWithJWTUsingOtherAlg(t *testing.T) {
token := "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYmMxMjMifQ.vLBmArLmAKEshqJa3px6qYfrkAfiwBrKPs5dCMxqj9bdiEKR5W4o0Srxt6VHZKzsxIGMTTsqpW21lKnYsLw5DA"
securityScheme := auth.NewAuthorizationBearerSecurityScheme("token", &token)
operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil)

report, err := jwt.WeakHMACSecretScanHandler(operation, securityScheme)

assert.NoError(t, err)
assert.Equal(t, 0, httpmock.GetTotalCallCount())
assert.False(t, report.HasVulnerabilityReport())
}

func TestWeakHMACSecretScanHandlerWithWeakJWT(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M"
securityScheme := auth.NewAuthorizationBearerSecurityScheme("token", &token)
operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil)

httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(104, nil))

report, err := jwt.WeakHMACSecretScanHandler(operation, securityScheme)

assert.NoError(t, err)
assert.Equal(t, 1, httpmock.GetTotalCallCount())
assert.True(t, report.HasVulnerabilityReport())
}

func TestWeakHMACSecretScanHandlerWithStrongerJWT(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.MWUarT7Q4e5DqnZbdr7VKw3rx9VW-CrvoVkfpllS4CY"
securityScheme := auth.NewAuthorizationBearerSecurityScheme("token", &token)
operation := request.NewOperation("http://localhost:8080/", "GET", nil, nil, nil)

httpmock.RegisterResponder(operation.Method, operation.Request.URL.String(), httpmock.NewBytesResponder(104, nil))

report, err := jwt.WeakHMACSecretScanHandler(operation, securityScheme)

assert.NoError(t, err)
assert.Equal(t, 0, httpmock.GetTotalCallCount())
assert.False(t, report.HasVulnerabilityReport())
}
2 changes: 1 addition & 1 deletion scan/vulns.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func (s *Scan) WithJWTNullSignatureScan() *Scan {
}

func (s *Scan) WithWeakJwtSecretScan() *Scan {
return s.AddOperationScanHandler(jwt.BlankSecretScanHandler).AddOperationScanHandler(jwt.DictSecretScanHandler)
return s.AddOperationScanHandler(jwt.BlankSecretScanHandler).AddOperationScanHandler(jwt.WeakHMACSecretScanHandler)

Check warning on line 18 in scan/vulns.go

View check run for this annotation

Codecov / codecov/patch

scan/vulns.go#L18

Added line #L18 was not covered by tests
}

func (s *Scan) WithAllVulnsScans() *Scan {
Expand Down

0 comments on commit 41fa322

Please sign in to comment.