VYPR
Moderate severityNVD Advisory· Published Dec 4, 2023· Updated May 29, 2025

Malicious parameters can cause a denial of service in lestrrat-go/jwx

CVE-2023-49290

Description

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.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CVE-2023-49290 is a high-severity denial-of-service vulnerability in the lestrrat-go/jwx Go module where an attacker can set an excessively large p2c parameter in JWE PBES2-* algorithms, causing excessive CPU consumption.

Vulnerability

CVE-2023-49290 is a denial-of-service vulnerability in the lestrrat-go/jwx Go module, which implements JOSE technologies (JWA/JWE/JWK/JWS/JWT). The flaw resides in the JWE key management algorithms based on PBKDF2 (PBES2-*). These algorithms require a JOSE Header Parameter called p2c (PBES2 Count), which dictates the number of PBKDF2 iterations needed to derive a content encryption key (CEK) wrapping key. The intended purpose of p2c is to intentionally slow down key derivation to resist password attacks. However, if an attacker sets the p2c parameter to a very large number, it can cause excessive computational consumption, resulting in a denial of service [1][2][4].

Exploitation

An attacker can exploit this vulnerability by crafting a JWE token with a maliciously high p2c value. When a server or application using an affected version of the lestrrat-go/jwx library attempts to decrypt or process such a token, the PBKDF2 key derivation will consume excessive CPU resources. The official proof-of-concept demonstrates that a token with p2c set to 2,000,000,000 can cause the decryption operation to block for an extended period, effectively exhausting CPU resources and leading to a denial of service [3][4]. No authentication or special privileges are required for the attacker to send such a malicious token; any endpoint that accepts and processes JWE tokens is potentially vulnerable.

Impact

Successful exploitation allows an unauthenticated attacker to cause a denial of service by forcing the targeted system to perform an excessive number of PBKDF2 iterations. This can lead to high CPU utilization, potentially making the system unresponsive to legitimate requests. The vulnerability affects all applications that use the vulnerable library to process JWE tokens with PBES2-* algorithms [1][2].

Mitigation

The vulnerability has been patched in commit 64f2a229b, which introduced a configurable maximum p2c count via jwe.WithMaxPBES2Count and a default limit to prevent excessive iterations. The fix is included in release versions 1.2.27 and 2.0.18. Users are strongly advised to upgrade to these patched versions. There are no known workarounds for this vulnerability [1][2][3][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/lestrrat-go/jwxGo
< 1.2.271.2.27
github.com/lestrrat-go/jwx/v2Go
< 2.0.182.0.18

Affected products

93

Patches

1
64f2a229b8e1

Merge pull request from GHSA-7f9x-gw85-8grf

https://github.com/lestrrat-go/jwxlestrratDec 3, 2023via ghsa
6 files changed · +113 0
  • Changes+9 0 modified
    @@ -4,6 +4,15 @@ Changes
     v2 has many incompatibilities with v1. To see the full list of differences between
     v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md)
     
    +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]
    +
     v2.0.17 20 Nov 2023
     [Bug Fixes]
       * [jws] Previously, `jws.UnregisterSigner` did not remove the previous signer instance when
    
  • jwe/jwe.go+21 0 modified
    @@ -10,6 +10,7 @@ import (
     	"crypto/rsa"
     	"fmt"
     	"io"
    +	"sync"
     
     	"github.com/lestrrat-go/blackmagic"
     	"github.com/lestrrat-go/jwx/v2/internal/base64"
    @@ -24,6 +25,20 @@ import (
     	"github.com/lestrrat-go/jwx/v2/x25519"
     )
     
    +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)
    +		}
    +	}
    +}
    +
     const (
     	fmtInvalid = iota
     	fmtCompact
    @@ -702,6 +717,12 @@ func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptio
     		if !ok {
     			return nil, fmt.Errorf("unexpected type for 'p2c': %T", count)
     		}
    +		muSettings.RLock()
    +		maxCount := maxPBES2Count
    +		muSettings.RUnlock()
    +		if countFlt > float64(maxCount) {
    +			return nil, fmt.Errorf("invalid 'p2c' value")
    +		}
     		salt, err := base64.DecodeString(saltB64Str)
     		if err != nil {
     			return nil, fmt.Errorf(`failed to b64-decode 'salt': %w`, err)
    
  • jwe/jwe_test.go+48 0 modified
    @@ -911,3 +911,51 @@ func TestGH1001(t *testing.T) {
     	require.Equal(t, "Lorem Ipsum", string(decrypted), `decrypted message should match`)
     	require.NotNil(t, cek, `cek should not be nil`)
     }
    +
    +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
    +		}
    +	}
    +}
    
  • jwe/options_gen.go+24 0 modified
    @@ -62,6 +62,18 @@ type encryptOption struct {
     
     func (*encryptOption) encryptOption() {}
     
    +// GlobalOption describes options that changes global settings for this package
    +type GlobalOption interface {
    +	Option
    +	globalOption()
    +}
    +
    +type globalOption struct {
    +	Option
    +}
    +
    +func (*globalOption) globalOption() {}
    +
     // ReadFileOption is a type of `Option` that can be passed to `jwe.Parse`
     type ParseOption interface {
     	Option
    @@ -117,6 +129,7 @@ type identFS struct{}
     type identKey struct{}
     type identKeyProvider struct{}
     type identKeyUsed struct{}
    +type identMaxPBES2Count struct{}
     type identMergeProtectedHeaders struct{}
     type identMessage struct{}
     type identPerRecipientHeaders struct{}
    @@ -153,6 +166,10 @@ func (identKeyUsed) String() string {
     	return "WithKeyUsed"
     }
     
    +func (identMaxPBES2Count) String() string {
    +	return "WithMaxPBES2Count"
    +}
    +
     func (identMergeProtectedHeaders) String() string {
     	return "WithMergeProtectedHeaders"
     }
    @@ -228,6 +245,13 @@ func WithKeyUsed(v interface{}) DecryptOption {
     	return &decryptOption{option.New(identKeyUsed{}, v)}
     }
     
    +// 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)}
    +}
    +
     // WithMergeProtectedHeaders specify that when given multiple headers
     // as options to `jwe.Encrypt`, these headers should be merged instead
     // of overwritten
    
  • jwe/options_gen_test.go+1 0 modified
    @@ -16,6 +16,7 @@ func TestOptionIdent(t *testing.T) {
     	require.Equal(t, "WithKey", identKey{}.String())
     	require.Equal(t, "WithKeyProvider", identKeyProvider{}.String())
     	require.Equal(t, "WithKeyUsed", identKeyUsed{}.String())
    +	require.Equal(t, "WithMaxPBES2Count", identMaxPBES2Count{}.String())
     	require.Equal(t, "WithMergeProtectedHeaders", identMergeProtectedHeaders{}.String())
     	require.Equal(t, "WithMessage", identMessage{}.String())
     	require.Equal(t, "WithPerRecipientHeaders", identPerRecipientHeaders{}.String())
    
  • jwe/options.yaml+10 0 modified
    @@ -1,6 +1,9 @@
     package_name: jwe
     output: jwe/options_gen.go
     interfaces:
    +  - name: GlobalOption
    +    comment: |
    +      GlobalOption describes options that changes global settings for this package
       - name: CompactOption
         comment: |
           CompactOption describes options that can be passed to `jwe.Compact`
    @@ -129,3 +132,10 @@ options:
           
           This option is currently considered EXPERIMENTAL, and is subject to
           future changes across minor/micro versions.
    +  - 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.
    \ No newline at end of file
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.