CVE-2022-3920 (Go)

Consul's UI-facing HTTP/RPC endpoints that return node and service inventory can include both local data and data imported from peered clusters. The vulnerable behavior is that imported peer nodes, services, and health checks may be left unfiltered, or may be authorized as if they were local resources with the same node or service names. An attacker who can invoke these UI/RPC inventory endpoints with an ACL token that is authorized only for local resources could therefore learn imported peer node, service, or health-check information that the token should not permit, causing unauthorized cross-cluster information disclosure.

Weakness:
CWE-862: name: Missing Authorization
description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
CWE-639: name: Authorization Bypass Through User-Controlled Key
description: The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.

Repository: https://github.com/hashicorp/consul

Vulnerable code:
file_path: agent/structs/aclfilter/filter.go
snippet: func (f *Filter) Filter(subject any) {
	switch v := subject.(type) {
	case *structs.CheckServiceNodes:
		f.filterCheckServiceNodes(v)

	case *structs.IndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.PreparedQueryExecuteResponse:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.IndexedServiceTopology:
		filtered := f.filterServiceTopology(v.ServiceTopology)
		if filtered {
			v.FilteredByACLs = true
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	case *structs.DatacenterIndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterDatacenterCheckServiceNodes(&v.DatacenterNodes)

	case *structs.IndexedCoordinates:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCoordinates(&v.Coordinates)

	case *structs.IndexedHealthChecks:
		v.QueryMeta.ResultsFilteredByACLs = f.filterHealthChecks(&v.HealthChecks)

	case *structs.IndexedIntentions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterIntentions(&v.Intentions)

	case *structs.IndexedNodeDump:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeDump(&v.Dump)

	case *structs.IndexedServiceDump:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceDump(&v.Dump)

	case *structs.IndexedNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodes(&v.Nodes)

	case *structs.IndexedNodeServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServices(&v.NodeServices)

	case *structs.IndexedNodeServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServiceList(&v.NodeServices)

	case *structs.IndexedServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceNodes(&v.ServiceNodes)

	case *structs.IndexedServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServices(v.Services, &v.EnterpriseMeta)

	case *structs.IndexedSessions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterSessions(&v.Sessions)

	case *structs.IndexedPreparedQueries:
		v.QueryMeta.ResultsFilteredByACLs = f.filterPreparedQueries(&v.Queries)

	case **structs.PreparedQuery:
		f.redactPreparedQueryTokens(v)

	case *structs.ACLTokens:
		f.filterTokens(v)
	case **structs.ACLToken:
		f.filterToken(v)
	case *[]*structs.ACLTokenListStub:
		f.filterTokenStubs(v)
	case **structs.ACLTokenListStub:
		f.filterTokenStub(v)

	case *structs.ACLPolicies:
		f.filterPolicies(v)
	case **structs.ACLPolicy:
		f.filterPolicy(v)

	case *structs.ACLRoles:
		f.filterRoles(v)
	case **structs.ACLRole:
		f.filterRole(v)

	case *structs.ACLBindingRules:
		f.filterBindingRules(v)
	case **structs.ACLBindingRule:
		f.filterBindingRule(v)

	case *structs.ACLAuthMethods:
		f.filterAuthMethods(v)
	case **structs.ACLAuthMethod:
		f.filterAuthMethod(v)

	case *structs.IndexedServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&v.Services)

	case *structs.IndexedExportedServiceList:
		for peer, peerServices := range v.Services {
			v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&peerServices)
			if len(peerServices) == 0 {
				delete(v.Services, peer)
			} else {
				v.Services[peer] = peerServices
			}
		}

	case *structs.IndexedGatewayServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterGatewayServices(&v.Services)

	case *structs.IndexedNodesWithGateways:
		if f.filterCheckServiceNodes(&v.Nodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterGatewayServices(&v.Gateways) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterCheckServiceNodes(&v.ImportedNodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	default:
		panic(fmt.Errorf("Unhandled type passed to ACL filter: %T %#v", subject, subject))
	}
}

file_path: agent/structs/structs_oss.go
snippet: func (_ *NodeInfo) FillAuthzContext(_ *acl.AuthorizerContext) {}

Fixed code:
file_path: agent/structs/aclfilter/filter.go
snippet: func (f *Filter) Filter(subject any) {
	switch v := subject.(type) {
	case *structs.CheckServiceNodes:
		f.filterCheckServiceNodes(v)

	case *structs.IndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.PreparedQueryExecuteResponse:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.IndexedServiceTopology:
		filtered := f.filterServiceTopology(v.ServiceTopology)
		if filtered {
			v.FilteredByACLs = true
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	case *structs.DatacenterIndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterDatacenterCheckServiceNodes(&v.DatacenterNodes)

	case *structs.IndexedCoordinates:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCoordinates(&v.Coordinates)

	case *structs.IndexedHealthChecks:
		v.QueryMeta.ResultsFilteredByACLs = f.filterHealthChecks(&v.HealthChecks)

	case *structs.IndexedIntentions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterIntentions(&v.Intentions)

	case *structs.IndexedNodeDump:
		if f.filterNodeDump(&v.Dump) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterNodeDump(&v.ImportedDump) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	case *structs.IndexedServiceDump:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceDump(&v.Dump)

	case *structs.IndexedNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodes(&v.Nodes)

	case *structs.IndexedNodeServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServices(&v.NodeServices)

	case *structs.IndexedNodeServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServiceList(&v.NodeServices)

	case *structs.IndexedServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceNodes(&v.ServiceNodes)

	case *structs.IndexedServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServices(v.Services, &v.EnterpriseMeta)

	case *structs.IndexedSessions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterSessions(&v.Sessions)

	case *structs.IndexedPreparedQueries:
		v.QueryMeta.ResultsFilteredByACLs = f.filterPreparedQueries(&v.Queries)

	case **structs.PreparedQuery:
		f.redactPreparedQueryTokens(v)

	case *structs.ACLTokens:
		f.filterTokens(v)
	case **structs.ACLToken:
		f.filterToken(v)
	case *[]*structs.ACLTokenListStub:
		f.filterTokenStubs(v)
	case **structs.ACLTokenListStub:
		f.filterTokenStub(v)

	case *structs.ACLPolicies:
		f.filterPolicies(v)
	case **structs.ACLPolicy:
		f.filterPolicy(v)

	case *structs.ACLRoles:
		f.filterRoles(v)
	case **structs.ACLRole:
		f.filterRole(v)

	case *structs.ACLBindingRules:
		f.filterBindingRules(v)
	case **structs.ACLBindingRule:
		f.filterBindingRule(v)

	case *structs.ACLAuthMethods:
		f.filterAuthMethods(v)
	case **structs.ACLAuthMethod:
		f.filterAuthMethod(v)

	case *structs.IndexedServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&v.Services)

	case *structs.IndexedExportedServiceList:
		for peer, peerServices := range v.Services {
			v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&peerServices)
			if len(peerServices) == 0 {
				delete(v.Services, peer)
			} else {
				v.Services[peer] = peerServices
			}
		}

	case *structs.IndexedGatewayServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterGatewayServices(&v.Services)

	case *structs.IndexedNodesWithGateways:
		if f.filterCheckServiceNodes(&v.Nodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterGatewayServices(&v.Gateways) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterCheckServiceNodes(&v.ImportedNodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	default:
		panic(fmt.Errorf("Unhandled type passed to ACL filter: %T %#v", subject, subject))
	}
}