BUG 2723 - Topology spoofing via LLDP - hash check in topology-discovery 97/16697/6
authorJozef Gloncak <jgloncak@cisco.com>
Tue, 17 Mar 2015 13:35:57 +0000 (14:35 +0100)
committerJozef Gloncak <jgloncak@cisco.com>
Wed, 27 May 2015 12:27:45 +0000 (14:27 +0200)
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 <jgloncak@cisco.com>
(cherry picked from commit 67eed66d24b20d03645140d40b44d16ce53e1210)

applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/LLDPDiscoveryListener.java
applications/topology-lldp-discovery/src/main/java/org/opendaylight/openflowplugin/applications/topology/lldp/utils/LLDPDiscoveryUtils.java

index 76afa09e31f817a04e45aebd3846e5ffb1d55838..08c3c9c4a9b196cb47517fbca2aafe7493efbb9e 100644 (file)
@@ -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());
index 443746e65e846faa93905ca370e6c2946a5f265f..da14b4e71d0afde604e69a7ec31daf5e0685f2c7 100644 (file)
@@ -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<NodeConnector> 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<NodeConnector> 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;
     }
 }