CVE-2020-13170
Description
HashiCorp Consul and Consul Enterprise did not appropriately enforce scope for local tokens issued by a primary data center, where replication to a secondary data center was not enabled. Introduced in 1.4.0, fixed in 1.6.6 and 1.7.4.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In HashiCorp Consul, local ACL tokens from a primary datacenter could be used in secondary datacenters without replication, allowing privilege escalation.
Vulnerability
CVE-2020-13170 is a security flaw in HashiCorp Consul and Consul Enterprise, where the ACL system failed to enforce that local tokens issued by a primary datacenter should only be resolvable within that datacenter when replication to secondary datacenters is not enabled. This issue was introduced in Consul version 1.4.0 and affects all subsequent versions until the fix in 1.6.6 and 1.7.4 [1][2].
Exploitation
An attacker who possesses a valid local ACL token from a primary datacenter can use that token to authenticate against a secondary datacenter that does not have replication enabled. The vulnerability arises because Consul previously resolved tokens without checking whether the token was local to the datacenter it originated from, as shown in the fix that adds a check for resp.Token.Local and SourceDatacenter [4].
Impact
Successful exploitation allows an attacker to potentially gain unauthorized access and privileges in secondary datacenters, bypassing ACL restrictions. This could lead to unauthorized operations such as reading or modifying configuration data, disrupting service discovery, or accessing sensitive information within the secondary datacenter.
Mitigation
HashiCorp has addressed this vulnerability in Consul versions 1.6.6 and 1.7.4 [1][2]. Users are strongly advised to upgrade to these or later versions. There is no known workaround; applying the patch is the recommended course of action.
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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/hashicorp/consulGo | >= 1.6.0-beta1, < 1.6.6 | 1.6.6 |
github.com/hashicorp/consulGo | >= 1.7.0, < 1.7.4 | 1.7.4 |
Affected products
3- HashiCorp/Consuldescription
- osv-coords2 versions
>= 1.4.0, < 1.6.6+ 1 more
- (no CPE)range: >= 1.4.0, < 1.6.6
- (no CPE)range: >= 1.6.0-beta1, < 1.6.6
Patches
1242994a016a1acl: do not resolve local tokens from remote dcs (#8068)
4 files changed · +53 −2
agent/consul/acl_endpoint.go+1 −0 modified@@ -260,6 +260,7 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke } reply.Index, reply.Token = index, token + reply.SourceDatacenter = args.Datacenter return nil }) }
agent/consul/acl.go+3 −0 modified@@ -417,6 +417,9 @@ func (r *ACLResolver) fetchAndCacheIdentityFromToken(token string, cached *struc if resp.Token == nil { r.cache.PutIdentity(cacheID, nil) return nil, acl.ErrNotFound + } else if resp.Token.Local && r.config.Datacenter != resp.SourceDatacenter { + r.cache.PutIdentity(cacheID, nil) + return nil, acl.PermissionDeniedError{Cause: fmt.Sprintf("This is a local token in datacenter %q", resp.SourceDatacenter)} } else { r.cache.PutIdentity(cacheID, resp.Token) return resp.Token, nil
agent/consul/acl_test.go+46 −0 modified@@ -3703,3 +3703,49 @@ func TestDedupeServiceIdentities(t *testing.T) { }) } } +func TestACL_LocalToken(t *testing.T) { + t.Run("local token in same dc", func(t *testing.T) { + d := &ACLResolverTestDelegate{ + datacenter: "dc1", + tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { + reply.Token = &structs.ACLToken{Local: true} + // different dc + reply.SourceDatacenter = "dc1" + return nil + }, + } + r := newTestACLResolver(t, d, nil) + _, err := r.fetchAndCacheIdentityFromToken("", nil) + require.NoError(t, err) + }) + + t.Run("non local token in remote dc", func(t *testing.T) { + d := &ACLResolverTestDelegate{ + datacenter: "dc1", + tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { + reply.Token = &structs.ACLToken{Local: false} + // different dc + reply.SourceDatacenter = "remote" + return nil + }, + } + r := newTestACLResolver(t, d, nil) + _, err := r.fetchAndCacheIdentityFromToken("", nil) + require.NoError(t, err) + }) + + t.Run("local token in remote dc", func(t *testing.T) { + d := &ACLResolverTestDelegate{ + datacenter: "dc1", + tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { + reply.Token = &structs.ACLToken{Local: true} + // different dc + reply.SourceDatacenter = "remote" + return nil + }, + } + r := newTestACLResolver(t, d, nil) + _, err := r.fetchAndCacheIdentityFromToken("", nil) + require.Equal(t, acl.PermissionDeniedError{Cause: "This is a local token in datacenter \"remote\""}, err) + }) +}
agent/structs/acl.go+3 −2 modified@@ -1258,8 +1258,9 @@ type ACLTokenBootstrapRequest struct { // ACLTokenResponse returns a single Token + metadata type ACLTokenResponse struct { - Token *ACLToken - Redacted bool // whether the token's secret was redacted + Token *ACLToken + Redacted bool // whether the token's secret was redacted + SourceDatacenter string QueryMeta }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-p2j5-3f4c-224rghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-13170ghsaADVISORY
- github.com/hashicorp/consul/blob/v1.6.6/CHANGELOG.mdghsax_refsource_CONFIRMWEB
- github.com/hashicorp/consul/blob/v1.7.4/CHANGELOG.mdghsax_refsource_CONFIRMWEB
- github.com/hashicorp/consul/commit/242994a016a181d6c62a5bb83189716ad13d4216ghsaWEB
- github.com/hashicorp/consul/pull/8068ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.