CVE-2023-49290 (Go, published 2023-12-05T00:15Z)
Description: value: lestrrat-go/jwx is a Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies. A p2c parameter set too high in JWE's algorithm PBES2-* could lead to a denial of service. The JWE key management algorithms based on PBKDF2 require a JOSE Header Parameter called p2c (PBES2 Count). This parameter dictates the number of PBKDF2 iterations needed to derive a CEK wrapping key. Its primary purpose is to intentionally slow down the key derivation function, making password brute-force and dictionary attacks more resource- intensive. Therefore, if an attacker sets the p2c parameter in JWE to a very large number, it can cause a lot of computational consumption, resulting in a denial of service. This vulnerability has been addressed in commit `64f2a229b` which has been included in release version 1.2.27 and 2.0.18. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Severity: nan (CVSS v3 base score: 5.3)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Repository: https://github.com/lestrrat-go/jwx
Files changed:
Changes
jwe/jwe.go
jwe/jwe_test.go
jwe/options.yaml
jwe/options_gen.go
jwe/options_gen_test.go
Commit message: Merge pull request from GHSA-7f9x-gw85-8grf



Vulnerable code:

Fixed code:
v2.0.18 UNRELEASED
[Security Fixes]
  * [jwe] A large number in p2c parameter for PBKDF2 based encryptions could cause a DoS attack,
    similar to https://nvd.nist.gov/vuln/detail/CVE-2022-36083.  All users who use JWE via this
    package should upgrade. While the JOSE spec allows for encryption using JWE on JWTs, users of
    the `jwt` package are not immediately susceptible unless they explicitly try to decrypt
    JWTs -- by default the `jwt` package verifies signatures, but does not decrypt messages.
    [GHSA-7f9x-gw85-8grf]

	"sync"
var muSettings sync.RWMutex
var maxPBES2Count = 10000

func Settings(options ...GlobalOption) {
	muSettings.Lock()
	defer muSettings.Unlock()
	for _, option := range options {
		switch option.Ident() {
		case identMaxPBES2Count{}:
			maxPBES2Count = option.Value().(int)
		}
	}
}

		muSettings.RLock()
		maxCount := maxPBES2Count
		muSettings.RUnlock()
		if countFlt > float64(maxCount) {
			return nil, fmt.Errorf("invalid 'p2c' value")
		}

func TestGHSA_7f9x_gw85_8grf(t *testing.T) {
	token := []byte("eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjoyMDAwMDAwMDAwLCJwMnMiOiJNNzczSnlmV2xlX2FsSXNrc0NOTU9BIn0=.S8B1kXdIR7BM6i_TaGsgqEOxU-1Sgdakp4mHq7UVhn-_REzOiGz2gg.gU_LfzhBXtQdwYjh.9QUIS-RWkLc.m9TudmzUoCzDhHsGGfzmCA")
	key, err := jwk.FromRaw([]byte(`abcdefg`))
	require.NoError(t, err, `jwk.FromRaw should succeed`)

	{
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		done := make(chan struct{})
		go func(t *testing.T, done chan struct{}) {
			_, err := jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW, key))
			require.Error(t, err, `jwe.Decrypt should fail`)
			close(done)
		}(t, done)

		select {
		case <-done:
		case <-ctx.Done():
			require.Fail(t, "jwe.Decrypt should not block")
		}
	}

	// NOTE: HAS GLOBAL EFFECT
	// Should allow for timeout to occur
	jwe.Settings(jwe.WithMaxPBES2Count(100000000000000000))

	// put it back to normal after the test
	defer jwe.Settings(jwe.WithMaxPBES2Count(10000))
	{
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		done := make(chan struct{})
		go func(t *testing.T, done chan struct{}) {
			_, _ = jwe.Decrypt(token, jwe.WithKey(jwa.PBES2_HS256_A128KW, key))
			close(done)
		}(t, done)

		select {
		case <-done:
			require.Fail(t, "jwe.Decrypt should block")
		case <-ctx.Done():
			// timeout occurred as it should
		}
	}
}
  - name: GlobalOption
    comment: |
      GlobalOption describes options that changes global settings for this package
  - ident: MaxPBES2Count
    interface: GlobalOption
    argument_type: int
    comment: |
      WithMaxPBES2Count specifies the maximum number of PBES2 iterations
      to use when decrypting a message. If not specified, the default
      value of 10,000 is used.
// GlobalOption describes options that changes global settings for this package
type GlobalOption interface {
	Option
	globalOption()
}

type globalOption struct {
	Option
}

func (*globalOption) globalOption() {}

type identMaxPBES2Count struct{}
func (identMaxPBES2Count) String() string {
	return "WithMaxPBES2Count"
}

// WithMaxPBES2Count specifies the maximum number of PBES2 iterations
// to use when decrypting a message. If not specified, the default
// value of 10,000 is used.
func WithMaxPBES2Count(v int) GlobalOption {
	return &globalOption{option.New(identMaxPBES2Count{}, v)}
}

	require.Equal(t, "WithMaxPBES2Count", identMaxPBES2Count{}.String())