From 27a4314a19491672dcb76c7300c3c4087ed1effa Mon Sep 17 00:00:00 2001 From: Jozef Gloncak Date: Tue, 17 Mar 2015 14:35:57 +0100 Subject: [PATCH] BUG 2723 - Topology spoofing via LLDP - hash check in topology-discovery Checking of CustomSec (TLV field in LLDP packet). Value of CustomSec from LLDP packet has to be equal to hash value which is computed in topology-lldp-discovery artifact. Hash value is obtained as MD5 value calculated from concatenation of strings: - node connector ID - pseudo PID of running JAVA karaf Method getValueForLLDPPacketIntegrityEnsuring() prepare array of bytes which will be after hashing used to check integrity of LLDP packets. Ensuring that LLDP packet wasn't modified. (extra authenticator; CVE-2015-1611 CVE-2015-1612) Change-Id: Ic8f50c88e7d8e3722d8d83a01ffa94a96bde313f Signed-off-by: Jozef Gloncak (cherry picked from commit 67eed66d24b20d03645140d40b44d16ce53e1210) --- .../topology/lldp/LLDPDiscoveryListener.java | 2 +- .../lldp/utils/LLDPDiscoveryUtils.java | 116 +++++++++++++++--- 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java index 76afa09e31..08c3c9c4a9 100644 --- a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java +++ b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java @@ -28,7 +28,7 @@ class LLDPDiscoveryListener implements PacketProcessingListener { } public void onPacketReceived(PacketReceived lldp) { - NodeConnectorRef src = LLDPDiscoveryUtils.lldpToNodeConnectorRef(lldp.getPayload()); + NodeConnectorRef src = LLDPDiscoveryUtils.lldpToNodeConnectorRef(lldp.getPayload(), true); if(src != null) { LinkDiscoveredBuilder ldb = new LinkDiscoveredBuilder(); ldb.setDestination(lldp.getIngress()); diff --git a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java index 443746e65e..da14b4e71d 100644 --- a/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java +++ b/applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java @@ -7,28 +7,42 @@ */ package org.opendaylight.openflowplugin.applications.topology.lldp.utils; +import org.apache.commons.lang3.ArrayUtils; import java.nio.charset.Charset; +import com.google.common.hash.HashCode; import org.opendaylight.controller.liblldp.Ethernet; import org.opendaylight.controller.liblldp.LLDP; +import org.opendaylight.controller.liblldp.BitBufferHelper; import org.opendaylight.controller.liblldp.LLDPTLV; import org.opendaylight.controller.liblldp.NetUtils; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import com.google.common.hash.HashFunction; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; +import java.util.Arrays; +import java.security.NoSuchAlgorithmException; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; +import java.lang.management.ManagementFactory; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.controller.liblldp.CustomTLVKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + public class LLDPDiscoveryUtils { private static final Logger LOG = LoggerFactory.getLogger(LLDPDiscoveryUtils.class); - public static final Long LLDP_INTERVAL = (long) (1000*5); // Send LLDP every five seconds - public static final Long LLDP_EXPIRATION_TIME = LLDP_INTERVAL*3; // Let up to three intervals pass before we decide we are expired. + // Send LLDP every five seconds + public static final Long LLDP_INTERVAL = (long) (1000*5); + + // Let up to three intervals pass before we decide we are expired. + public static final Long LLDP_EXPIRATION_TIME = LLDP_INTERVAL*3; public static String macToString(byte[] mac) { StringBuilder b = new StringBuilder(); @@ -39,7 +53,21 @@ public class LLDPDiscoveryUtils { return b.toString(); } + /** + * @param payload + * @return nodeConnectorId - encoded in custom TLV of given lldp + * @see LLDPDiscoveryUtils#lldpToNodeConnectorRef(byte[], boolean) + */ public static NodeConnectorRef lldpToNodeConnectorRef(byte[] payload) { + return lldpToNodeConnectorRef(payload, false); + } + + /** + * @param payload + * @param useExtraAuthenticatorCheck make it more secure (CVE-2015-1611 CVE-2015-1612) + * @return nodeConnectorId - encoded in custom TLV of given lldp + */ + public static NodeConnectorRef lldpToNodeConnectorRef(byte[] payload, boolean useExtraAuthenticatorCheck) { Ethernet ethPkt = new Ethernet(); try { ethPkt.deserialize(payload, 0,payload.length * NetUtils.NumBitsInAByte); @@ -47,32 +75,84 @@ public class LLDPDiscoveryUtils { LOG.warn("Failed to decode LLDP packet {}", e); } + NodeConnectorRef nodeConnectorRef = null; + if (ethPkt.getPayload() instanceof LLDP) { LLDP lldp = (LLDP) ethPkt.getPayload(); try { NodeId srcNodeId = null; NodeConnectorId srcNodeConnectorId = null; - for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { - if (lldptlv.getType() == LLDPTLV.TLVType.Custom.getValue()) { - srcNodeConnectorId = new NodeConnectorId(LLDPTLV.getCustomString(lldptlv.getValue(), lldptlv.getLength())); - } - if (lldptlv.getType() == LLDPTLV.TLVType.SystemName.getValue()) { - String srcNodeIdString = new String(lldptlv.getValue(),Charset.defaultCharset()); - srcNodeId = new NodeId(srcNodeIdString); - } + + final LLDPTLV systemIdTLV = lldp.getSystemNameId(); + if (systemIdTLV != null) { + String srcNodeIdString = new String(systemIdTLV.getValue(),Charset.defaultCharset()); + srcNodeId = new NodeId(srcNodeIdString); + } else { + throw new Exception("Node id wasn't specified via systemNameId in LLDP packet."); } - if(srcNodeId != null && srcNodeConnectorId != null) { - InstanceIdentifier srcInstanceId = InstanceIdentifier.builder(Nodes.class) - .child(Node.class,new NodeKey(srcNodeId)) - .child(NodeConnector.class, new NodeConnectorKey(srcNodeConnectorId)) - .toInstance(); - return new NodeConnectorRef(srcInstanceId); + + final LLDPTLV nodeConnectorIdLldptlv = lldp.getCustomTLV( + new CustomTLVKey(BitBufferHelper.getInt(LLDPTLV.OFOUI), LLDPTLV.CUSTOM_TLV_SUB_TYPE_NODE_CONNECTOR_ID[0])); + if (nodeConnectorIdLldptlv != null) { + srcNodeConnectorId = new NodeConnectorId(LLDPTLV.getCustomString( + nodeConnectorIdLldptlv.getValue(), nodeConnectorIdLldptlv.getLength())); + } else { + throw new Exception("Node connector wasn't specified via Custom TLV in LLDP packet."); } + + if (useExtraAuthenticatorCheck) { + boolean secure = checkExtraAuthenticator(lldp, srcNodeConnectorId); + if (! secure) { + LOG.warn("SECURITY ALERT: there is probably a LLDP spoofing attack in progress."); + throw new Exception("Attack. LLDP packet with inconsistent extra authenticator field was received."); + } + } + + InstanceIdentifier srcInstanceId = InstanceIdentifier.builder(Nodes.class) + .child(Node.class,new NodeKey(srcNodeId)) + .child(NodeConnector.class, new NodeConnectorKey(srcNodeConnectorId)) + .toInstance(); + nodeConnectorRef = new NodeConnectorRef(srcInstanceId); } catch (Exception e) { - LOG.warn("Caught exception ", e); + LOG.debug("Caught exception while parsing out lldp optional and custom fields: {}", e.getMessage(), e); } } - return null; + return nodeConnectorRef; + } + + /** + * @param nodeConnectorId + * @return extra authenticator for lldp security + * @throws NoSuchAlgorithmException + */ + public static byte[] getValueForLLDPPacketIntegrityEnsuring(final NodeConnectorId nodeConnectorId) throws NoSuchAlgorithmException { + final String pureValue = nodeConnectorId+ManagementFactory.getRuntimeMXBean().getName(); + final byte[] pureBytes = pureValue.getBytes(); + HashFunction hashFunction = Hashing.md5(); + Hasher hasher = hashFunction.newHasher(); + HashCode hashedValue = hasher.putBytes(pureBytes).hash(); + return hashedValue.asBytes(); + } + + /** + * @param lldp + * @param srcNodeConnectorId + * @throws NoSuchAlgorithmException + */ + private static boolean checkExtraAuthenticator(LLDP lldp, NodeConnectorId srcNodeConnectorId) throws NoSuchAlgorithmException { + final LLDPTLV hashLldptlv = lldp.getCustomTLV( + new CustomTLVKey(BitBufferHelper.getInt(LLDPTLV.OFOUI), LLDPTLV.CUSTOM_TLV_SUB_TYPE_CUSTOM_SEC[0])); + boolean secAuthenticatorOk = false; + if (hashLldptlv != null) { + byte[] rawTlvValue = hashLldptlv.getValue(); + byte[] lldpCustomSecurityHash = ArrayUtils.subarray(rawTlvValue, 4, rawTlvValue.length); + byte[] calculatedHash = getValueForLLDPPacketIntegrityEnsuring(srcNodeConnectorId); + secAuthenticatorOk = Arrays.equals(calculatedHash, lldpCustomSecurityHash); + } else { + LOG.debug("Custom security hint wasn't specified via Custom TLV in LLDP packet."); + } + + return secAuthenticatorOk; } } -- 2.36.6