Insecure IPsec transport encryption in Cilium
Description
Cilium is a networking, observability, and security solution with an eBPF-based dataplane. Users of IPsec transparent encryption in Cilium may be vulnerable to cryptographic attacks that render the transparent encryption ineffective. In particular, Cilium is vulnerable to chosen plaintext, key recovery, replay attacks by a man-in-the-middle attacker. These attacks are possible due to an ESP sequence number collision when multiple nodes are configured with the same key. Fixed versions of Cilium use unique keys for each IPsec tunnel established between nodes, resolving all of the above attacks. This vulnerability is fixed in 1.13.13, 1.14.9, and 1.15.3.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/cilium/ciliumGo | >= 1.4.0, < 1.13.14 | 1.13.14 |
github.com/cilium/ciliumGo | >= 1.14.0, < 1.14.9 | 1.14.9 |
github.com/cilium/ciliumGo | >= 1.15.0, < 1.15.3 | 1.15.3 |
Affected products
1Patches
3311fbce52804ipsec: fix per-node-pair-key computation
1 file changed · +18 −3
pkg/datapath/linux/ipsec/ipsec_linux.go+18 −3 modified@@ -152,14 +152,26 @@ func getGlobalIPsecKey(ip net.IP) *ipSecKey { // pre-shared key. The per-node-pair keys are computed with a SHA256 hash of // the global key, source node IP, destination node IP appended together. func computeNodeIPsecKey(globalKey, srcNodeIP, dstNodeIP, srcBootID, dstBootID []byte) []byte { - input := append(globalKey, srcNodeIP...) + inputLen := len(globalKey) + len(srcNodeIP) + len(dstNodeIP) + len(srcBootID) + len(dstBootID) + input := make([]byte, 0, inputLen) + input = append(input, globalKey...) + input = append(input, srcNodeIP...) input = append(input, dstNodeIP...) - input = append(input, srcBootID...) - input = append(input, dstBootID...) + input = append(input, srcBootID[:36]...) + input = append(input, dstBootID[:36]...) output := sha256.Sum256(input) return output[:len(globalKey)] } +// canonicalIP returns a canonical IPv4 address (4 bytes) +// in case we were dealing with a v4 mapped V6 address. +func canonicalIP(ip net.IP) net.IP { + if v4 := ip.To4(); v4 != nil { + return v4 + } + return ip +} + // deriveNodeIPsecKey builds a per-node-pair ipSecKey object from the global // ipSecKey object. func deriveNodeIPsecKey(globalKey *ipSecKey, srcNodeIP, dstNodeIP net.IP, srcBootID, dstBootID string) *ipSecKey { @@ -169,6 +181,9 @@ func deriveNodeIPsecKey(globalKey *ipSecKey, srcNodeIP, dstNodeIP net.IP, srcBoo ESN: globalKey.ESN, } + srcNodeIP = canonicalIP(srcNodeIP) + dstNodeIP = canonicalIP(dstNodeIP) + if globalKey.Aead != nil { nodeKey.Aead = &netlink.XfrmStateAlgo{ Name: globalKey.Aead.Name,
a1742b478306ipsec: fix per-node-pair-key computation
1 file changed · +18 −3
pkg/datapath/linux/ipsec/ipsec_linux.go+18 −3 modified@@ -150,14 +150,26 @@ func getGlobalIPsecKey(ip net.IP) *ipSecKey { // pre-shared key. The per-node-pair keys are computed with a SHA256 hash of // the global key, source node IP, destination node IP appended together. func computeNodeIPsecKey(globalKey, srcNodeIP, dstNodeIP, srcBootID, dstBootID []byte) []byte { - input := append(globalKey, srcNodeIP...) + inputLen := len(globalKey) + len(srcNodeIP) + len(dstNodeIP) + len(srcBootID) + len(dstBootID) + input := make([]byte, 0, inputLen) + input = append(input, globalKey...) + input = append(input, srcNodeIP...) input = append(input, dstNodeIP...) - input = append(input, srcBootID...) - input = append(input, dstBootID...) + input = append(input, srcBootID[:36]...) + input = append(input, dstBootID[:36]...) output := sha256.Sum256(input) return output[:len(globalKey)] } +// canonicalIP returns a canonical IPv4 address (4 bytes) +// in case we were dealing with a v4 mapped V6 address. +func canonicalIP(ip net.IP) net.IP { + if v4 := ip.To4(); v4 != nil { + return v4 + } + return ip +} + // deriveNodeIPsecKey builds a per-node-pair ipSecKey object from the global // ipSecKey object. func deriveNodeIPsecKey(globalKey *ipSecKey, srcNodeIP, dstNodeIP net.IP, srcBootID, dstBootID string) *ipSecKey { @@ -167,6 +179,9 @@ func deriveNodeIPsecKey(globalKey *ipSecKey, srcNodeIP, dstNodeIP net.IP, srcBoo ESN: globalKey.ESN, } + srcNodeIP = canonicalIP(srcNodeIP) + dstNodeIP = canonicalIP(dstNodeIP) + if globalKey.Aead != nil { nodeKey.Aead = &netlink.XfrmStateAlgo{ Name: globalKey.Aead.Name,
a652c1233318ipsec: fix per-node-pair-key computation
1 file changed · +18 −3
pkg/datapath/linux/ipsec/ipsec_linux.go+18 −3 modified@@ -150,14 +150,26 @@ func getGlobalIPsecKey(ip net.IP) *ipSecKey { // pre-shared key. The per-node-pair keys are computed with a SHA256 hash of // the global key, source node IP, destination node IP appended together. func computeNodeIPsecKey(globalKey, srcNodeIP, dstNodeIP, srcBootID, dstBootID []byte) []byte { - input := append(globalKey, srcNodeIP...) + inputLen := len(globalKey) + len(srcNodeIP) + len(dstNodeIP) + len(srcBootID) + len(dstBootID) + input := make([]byte, 0, inputLen) + input = append(input, globalKey...) + input = append(input, srcNodeIP...) input = append(input, dstNodeIP...) - input = append(input, srcBootID...) - input = append(input, dstBootID...) + input = append(input, srcBootID[:36]...) + input = append(input, dstBootID[:36]...) output := sha256.Sum256(input) return output[:len(globalKey)] } +// canonicalIP returns a canonical IPv4 address (4 bytes) +// in case we were dealing with a v4 mapped V6 address. +func canonicalIP(ip net.IP) net.IP { + if v4 := ip.To4(); v4 != nil { + return v4 + } + return ip +} + // deriveNodeIPsecKey builds a per-node-pair ipSecKey object from the global // ipSecKey object. func deriveNodeIPsecKey(globalKey *ipSecKey, srcNodeIP, dstNodeIP net.IP, srcBootID, dstBootID string) *ipSecKey { @@ -167,6 +179,9 @@ func deriveNodeIPsecKey(globalKey *ipSecKey, srcNodeIP, dstNodeIP net.IP, srcBoo ESN: globalKey.ESN, } + srcNodeIP = canonicalIP(srcNodeIP) + dstNodeIP = canonicalIP(dstNodeIP) + if globalKey.Aead != nil { nodeKey.Aead = &netlink.XfrmStateAlgo{ Name: globalKey.Aead.Name,
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-pwqm-x5x6-5586ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-28860ghsaADVISORY
- docs.cilium.io/en/stable/security/network/encryption-ipsecghsax_refsource_MISCWEB
- github.com/cilium/cilium/commit/311fbce5280491cddceab178d83b06fa23688c72ghsax_refsource_MISCWEB
- github.com/cilium/cilium/commit/a1742b478306fa256cd27df1039dfae0537b4149ghsax_refsource_MISCWEB
- github.com/cilium/cilium/commit/a652c123331852cca90c74202f993d4170fd37faghsax_refsource_MISCWEB
- github.com/cilium/cilium/security/advisories/GHSA-pwqm-x5x6-5586ghsax_refsource_CONFIRMWEB
- pkg.go.dev/vuln/GO-2024-2666ghsaWEB
News mentions
0No linked articles in our index yet.