VYPR
Moderate severityNVD Advisory· Published Sep 23, 2022· Updated May 27, 2025

CVE-2022-40716

CVE-2022-40716

Description

HashiCorp Consul and Consul Enterprise up to 1.11.8, 1.12.4, and 1.13.1 do not check for multiple SAN URI values in a CSR on the internal RPC endpoint, enabling leverage of privileged access to bypass service mesh intentions. Fixed in 1.11.9, 1.12.5, and 1.13.2."

AI Insight

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

HashiCorp Consul fails to validate multiple SAN URI values in CSRs on the internal RPC endpoint, allowing attackers with privileged access to bypass service mesh intentions.

Vulnerability

Analysis

CVE-2022-40716 affects HashiCorp Consul and Consul Enterprise versions prior to 1.11.9, 1.12.5, and 1.13.2. The vulnerability lies in the internal RPC endpoint of the certificate signing process. When a Certificate Signing Request (CSR) is submitted for signing, the software does not validate that the CSR contains only a single Subject Alternative Name (SAN) URI value [1][3]. This oversight allows a CSR to include multiple SAN URIs, which is against the expected behavior for SPIFFE-based identities in a service mesh [4].

Exploitation

Exploitation requires privileged access to Consul, as the attacker must be able to issue CSRs to the internal RPC endpoint. With this level of access, an attacker can craft a CSR containing multiple SAN URIs. The system will then sign a certificate that includes multiple identities (e.g., a previously authorized service identity and a target service identity). This bypasses the service mesh intention checks that rely on single-identity certificates for access control [1]. The attack does not require network position changes but leverages existing administrative capabilities.

Impact

A successful attack allows an attacker to bypass service mesh intentions, which are Consul's mechanism for defining which services can communicate. An attacker with privileged access could potentially connect to services they should not have access to, effectively bypassing security policies enforced by the service mesh. This could lead to unauthorized data access, service disruption, or lateral movement within the mesh [3].

Mitigation

HashiCorp has released fixed versions: Consul 1.11.9, 1.12.5, and 1.13.2 [2][3]. The fix introduces a validation check that rejects CSRs with multiple SAN URIs, ensuring that each signed certificate represents only one identity [1][4]. Users should upgrade to these patched versions. There are no known workarounds; upgrading is the recommended mitigation.

AI Insight generated on May 21, 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/hashicorp/consulGo
< 1.11.91.11.9
github.com/hashicorp/consulGo
>= 1.12.0, < 1.12.51.12.5
github.com/hashicorp/consulGo
>= 1.13.0, < 1.13.21.13.2

Affected products

29

Patches

1
8f6fb4f6fe94

Backport #14579 to 1.13

https://github.com/hashicorp/consulDerek MenteerSep 13, 2022via ghsa
5 files changed · +281 7
  • agent/consul/auto_config_endpoint.go+6 3 modified
    @@ -394,9 +394,12 @@ func parseAutoConfigCSR(csr string) (*x509.CertificateRequest, *connect.SpiffeID
     		return nil, nil, fmt.Errorf("Failed to parse CSR: %w", err)
     	}
     
    -	// ensure that a URI SAN is present
    -	if len(x509CSR.URIs) < 1 {
    -		return nil, nil, fmt.Errorf("CSR didn't include any URI SANs")
    +	// ensure that exactly one URI SAN is present
    +	if len(x509CSR.URIs) != 1 {
    +		return nil, nil, fmt.Errorf("CSR SAN contains an invalid number of URIs: %v", len(x509CSR.URIs))
    +	}
    +	if len(x509CSR.EmailAddresses) > 0 {
    +		return nil, nil, fmt.Errorf("CSR SAN does not allow specifying email addresses")
     	}
     
     	// Parse the SPIFFE ID
    
  • agent/consul/auto_config_endpoint_test.go+125 0 modified
    @@ -1,12 +1,17 @@
     package consul
     
     import (
    +	"bytes"
    +	"crypto"
    +	crand "crypto/rand"
     	"crypto/x509"
     	"encoding/base64"
    +	"encoding/pem"
     	"fmt"
     	"io/ioutil"
     	"math/rand"
     	"net"
    +	"net/url"
     	"path"
     	"testing"
     	"time"
    @@ -874,6 +879,126 @@ func TestAutoConfig_updateJoinAddressesInConfig(t *testing.T) {
     	backend.AssertExpectations(t)
     }
     
    +func TestAutoConfig_parseAutoConfigCSR(t *testing.T) {
    +	// createCSR copies the behavior of connect.CreateCSR with some
    +	// customizations to allow for better unit testing.
    +	createCSR := func(tmpl *x509.CertificateRequest, privateKey crypto.Signer) (string, error) {
    +		connect.HackSANExtensionForCSR(tmpl)
    +		bs, err := x509.CreateCertificateRequest(crand.Reader, tmpl, privateKey)
    +		require.NoError(t, err)
    +		var csrBuf bytes.Buffer
    +		err = pem.Encode(&csrBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: bs})
    +		require.NoError(t, err)
    +		return csrBuf.String(), nil
    +	}
    +	pk, _, err := connect.GeneratePrivateKey()
    +	require.NoError(t, err)
    +
    +	agentURI := connect.SpiffeIDAgent{
    +		Host:       "test-host",
    +		Datacenter: "tdc1",
    +		Agent:      "test-agent",
    +	}.URI()
    +
    +	tests := []struct {
    +		name      string
    +		setup     func() string
    +		expectErr string
    +	}{
    +		{
    +			name:      "err_garbage_data",
    +			expectErr: "Failed to parse CSR",
    +			setup:     func() string { return "garbage" },
    +		},
    +		{
    +			name:      "err_not_one_uri",
    +			expectErr: "CSR SAN contains an invalid number of URIs",
    +			setup: func() string {
    +				tmpl := &x509.CertificateRequest{
    +					URIs:               []*url.URL{agentURI, agentURI},
    +					SignatureAlgorithm: connect.SigAlgoForKey(pk),
    +				}
    +				csr, err := createCSR(tmpl, pk)
    +				require.NoError(t, err)
    +				return csr
    +			},
    +		},
    +		{
    +			name:      "err_email",
    +			expectErr: "CSR SAN does not allow specifying email addresses",
    +			setup: func() string {
    +				tmpl := &x509.CertificateRequest{
    +					URIs:               []*url.URL{agentURI},
    +					EmailAddresses:     []string{"test@example.com"},
    +					SignatureAlgorithm: connect.SigAlgoForKey(pk),
    +				}
    +				csr, err := createCSR(tmpl, pk)
    +				require.NoError(t, err)
    +				return csr
    +			},
    +		},
    +		{
    +			name:      "err_spiffe_parse_uri",
    +			expectErr: "Failed to parse the SPIFFE URI",
    +			setup: func() string {
    +				tmpl := &x509.CertificateRequest{
    +					URIs:               []*url.URL{connect.SpiffeIDAgent{}.URI()},
    +					SignatureAlgorithm: connect.SigAlgoForKey(pk),
    +				}
    +				csr, err := createCSR(tmpl, pk)
    +				require.NoError(t, err)
    +				return csr
    +			},
    +		},
    +		{
    +			name:      "err_not_agent",
    +			expectErr: "SPIFFE ID is not an Agent ID",
    +			setup: func() string {
    +				spiffe := connect.SpiffeIDService{
    +					Namespace:  "tns",
    +					Datacenter: "tdc1",
    +					Service:    "test-service",
    +				}
    +				tmpl := &x509.CertificateRequest{
    +					URIs:               []*url.URL{spiffe.URI()},
    +					SignatureAlgorithm: connect.SigAlgoForKey(pk),
    +				}
    +				csr, err := createCSR(tmpl, pk)
    +				require.NoError(t, err)
    +				return csr
    +			},
    +		},
    +		{
    +			name: "success",
    +			setup: func() string {
    +				tmpl := &x509.CertificateRequest{
    +					URIs:               []*url.URL{agentURI},
    +					SignatureAlgorithm: connect.SigAlgoForKey(pk),
    +				}
    +				csr, err := createCSR(tmpl, pk)
    +				require.NoError(t, err)
    +				return csr
    +			},
    +		},
    +	}
    +
    +	for _, tc := range tests {
    +		t.Run(tc.name, func(t *testing.T) {
    +			req, spif, err := parseAutoConfigCSR(tc.setup())
    +			if tc.expectErr != "" {
    +				require.Error(t, err)
    +				require.Contains(t, err.Error(), tc.expectErr)
    +			} else {
    +				require.NoError(t, err)
    +				// TODO better verification of these
    +				require.NotNil(t, req)
    +				require.NotNil(t, spif)
    +			}
    +
    +		})
    +	}
    +}
    +
     func TestAutoConfig_invalidSegmentName(t *testing.T) {
     	invalid := []string{
     		"\n",
    
  • agent/consul/leader_connect_ca.go+9 4 modified
    @@ -1406,10 +1406,15 @@ func (l *connectSignRateLimiter) getCSRRateLimiterWithLimit(limit rate.Limit) *r
     // identified by the SPIFFE ID in the given CSR's SAN. It performs authorization
     // using the given acl.Authorizer.
     func (c *CAManager) AuthorizeAndSignCertificate(csr *x509.CertificateRequest, authz acl.Authorizer) (*structs.IssuedCert, error) {
    -	// Parse the SPIFFE ID from the CSR SAN.
    -	if len(csr.URIs) == 0 {
    -		return nil, connect.InvalidCSRError("CSR SAN does not contain a SPIFFE ID")
    +	// Note that only one spiffe id is allowed currently. If more than one is desired
    +	// in future implmentations, then each ID should have authorization checks.
    +	if len(csr.URIs) != 1 {
    +		return nil, connect.InvalidCSRError("CSR SAN contains an invalid number of URIs: %v", len(csr.URIs))
    +	}
    +	if len(csr.EmailAddresses) > 0 {
    +		return nil, connect.InvalidCSRError("CSR SAN does not allow specifying email addresses")
     	}
    +	// Parse the SPIFFE ID from the CSR SAN.
     	spiffeID, err := connect.ParseCertURI(csr.URIs[0])
     	if err != nil {
     		return nil, err
    @@ -1452,7 +1457,7 @@ func (c *CAManager) AuthorizeAndSignCertificate(csr *x509.CertificateRequest, au
     				"we are %s", v.Datacenter, dc)
     		}
     	default:
    -		return nil, connect.InvalidCSRError("SPIFFE ID in CSR must be a service or agent ID")
    +		return nil, connect.InvalidCSRError("SPIFFE ID in CSR must be a service, mesh-gateway, or agent ID")
     	}
     
     	return c.SignCertificate(csr, spiffeID)
    
  • agent/consul/leader_connect_ca_test.go+138 0 modified
    @@ -24,6 +24,7 @@ import (
     	msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc"
     	"github.com/hashicorp/consul-net-rpc/net/rpc"
     
    +	"github.com/hashicorp/consul/acl"
     	"github.com/hashicorp/consul/agent/connect"
     	ca "github.com/hashicorp/consul/agent/connect/ca"
     	"github.com/hashicorp/consul/agent/consul/fsm"
    @@ -1042,3 +1043,140 @@ func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string, rootPEM
     	require.NoError(t, err, "failed to set signed intermediate")
     	return lib.EnsureTrailingNewline(buf.String())
     }
    +
    +func TestCAManager_AuthorizeAndSignCertificate(t *testing.T) {
    +	conf := DefaultConfig()
    +	conf.PrimaryDatacenter = "dc1"
    +	conf.Datacenter = "dc2"
    +	manager := NewCAManager(nil, nil, testutil.Logger(t), conf)
    +
    +	agentURL := connect.SpiffeIDAgent{
    +		Agent:      "test-agent",
    +		Datacenter: conf.PrimaryDatacenter,
    +		Host:       "test-host",
    +	}.URI()
    +	serviceURL := connect.SpiffeIDService{
    +		Datacenter: conf.PrimaryDatacenter,
    +		Namespace:  "ns1",
    +		Service:    "test-service",
    +	}.URI()
    +	meshURL := connect.SpiffeIDMeshGateway{
    +		Datacenter: conf.PrimaryDatacenter,
    +		Host:       "test-host",
    +		Partition:  "test-partition",
    +	}.URI()
    +
    +	tests := []struct {
    +		name      string
    +		expectErr string
    +		getCSR    func() *x509.CertificateRequest
    +		authAllow bool
    +	}{
    +		{
    +			name:      "err_not_one_uri",
    +			expectErr: "CSR SAN contains an invalid number of URIs",
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{agentURL, agentURL},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_email",
    +			expectErr: "CSR SAN does not allow specifying email addresses",
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs:           []*url.URL{agentURL},
    +					EmailAddresses: []string{"test@example.com"},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_invalid_spiffe_id",
    +			expectErr: "SPIFFE ID is not in the expected format",
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{connect.SpiffeIDAgent{}.URI()},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_service_write_not_allowed",
    +			expectErr: "Permission denied",
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{serviceURL},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_service_different_dc",
    +			expectErr: "SPIFFE ID in CSR from a different datacenter",
    +			authAllow: true,
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{serviceURL},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_agent_write_not_allowed",
    +			expectErr: "Permission denied",
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{agentURL},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_meshgw_write_not_allowed",
    +			expectErr: "Permission denied",
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{meshURL},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_meshgw_different_dc",
    +			expectErr: "SPIFFE ID in CSR from a different datacenter",
    +			authAllow: true,
    +			getCSR: func() *x509.CertificateRequest {
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{meshURL},
    +				}
    +			},
    +		},
    +		{
    +			name:      "err_invalid_spiffe_type",
    +			expectErr: "SPIFFE ID in CSR must be a service, mesh-gateway, or agent ID",
    +			getCSR: func() *x509.CertificateRequest {
    +				u := connect.SpiffeIDSigning{
    +					ClusterID: "test-cluster-id",
    +					Domain:    "test-domain",
    +				}.URI()
    +				return &x509.CertificateRequest{
    +					URIs: []*url.URL{u},
    +				}
    +			},
    +		},
    +	}
    +
    +	for _, tc := range tests {
    +		t.Run(tc.name, func(t *testing.T) {
    +			authz := acl.DenyAll()
    +			if tc.authAllow {
    +				authz = acl.AllowAll()
    +			}
    +
    +			cert, err := manager.AuthorizeAndSignCertificate(tc.getCSR(), authz)
    +			if tc.expectErr != "" {
    +				require.Error(t, err)
    +				require.Contains(t, err.Error(), tc.expectErr)
    +			} else {
    +				require.NoError(t, err)
    +				require.NotNil(t, cert)
    +			}
    +		})
    +	}
    +}
    
  • .changelog/14579.txt+3 0 added
    @@ -0,0 +1,3 @@
    +```release-note:security
    +connect: Added URI length checks to ConnectCA CSR requests. Prior to this change, it was possible for a malicious actor to designate multiple SAN URI values in a call to the `ConnectCA.Sign` endpoint. The endpoint now only allows for exactly one SAN URI to be specified.
    +```
    \ 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

11

News mentions

0

No linked articles in our index yet.