You Can't Try It If You Don't Knock It

Small and medium business branch installations often present some unique problems, and access for remote support can be a prevalent one. This is especially true when one wants to avoid opening unnecessary access from the Internet. Permitting access only from a jump-off point with a fixed IP address was a good approach; until the day came where that fixed IP address became unreachable during a customer crisis. Painful moments like these are the mother of new approaches.

So, how do we enable remote access from almost anywhere without increasing the attack footprint of a customer's router?

A few years ago, I was reading an old blog post by my friend Aaron Conaway where he talked about port knocking on Linux. This is a process where a predefined set of ports, accessed in a particular order, trigger access to a set of otherwise-closed services.

I became intrigued and started searching for people who had done this on IOS. I found a few posts, but all involved two processes that I prefer to avoid:

  1. Logging all packets on an interface for tracking the access sequence. As soon as we add a log keyword for an interface Access Control List (ACL), CEF is disabled and the router's performance suffers.
  2. Using a script to change the router's configuration to enable services. I'm not against using a script to change router configuration, but only if it's necessary. In this particular case, it isn't.

Using Control Plane Policing (CoPP) and lock-and-key dynamic ACLs, it isn't a difficult process to make this happen elegantly. If you're interested in giving it a try, here's how we get started.

Operation

All services that we want to permit are denied by default. Predefined knock triggers (UDP packets in this example) are permitted to reach the control plane. At this point they are dropped and logged by the CoPP configuration.

Once the knock sequence is verified, a dynamic ACL entry is created that permits traffic to the applicable services from the source address of the knock sequence.

Configuration

We'll start by establishing a sequence of five pseudo-random ports for the knock sequence itself. These can be TCP or UDP ports, but I'll stick with UDP ports for this example.

object-group service OG_Knock
 udp eq 46543
 udp eq 28654
 udp eq 40204
 udp eq 57566
 udp eq 34515

We need to be sure that these ports are permitted by the inbound interface and/or firewall security mechanisms. They will be dropped once they reach the control plane. The process won't work if they are blocked before they reach this point because we are relying on CoPP logging for the trigger.

Next, we'll define the services that we want permitted after a successful knock. We'll use some common ones: SSH on port 22/tcp, and the AnyConnect access VPN in TLS/DTLS mode using ports 80/tcp, 443/tcp, and 443/udp.

object-group service OG_Knock_Services
 tcp eq 22
 tcp eq 80
 tcp-udp eq 443

Once these are defined, we'll define a CoPP policy to deny attempts to our knock sequence.

ip access-list extended ACL_CoPP_Knock
 permit object-group OG_Knock any any
!
class-map match-any CM_CoPP_Knock
 match access-group name ACL_CoPP_Knock
!
policy-map PM_CoPP
 class CM_CoPP_Knock
 drop
!
control-plane host
 service-policy input PM_CoPP

Then, we add CoPP logging of denied packets to generate a set of syslog events when the elements of the sequence have been triggered.

class-map type logging match-any CM_CoPP_Log
 match packets dropped
!
policy-map type logging PM_CoPP_Log
 class CM_CoPP_Log
 log
!
control-plane host
 service-policy type logging input PM_CoPP_Log

Here's where we resurrect the old lock and key dynamic ACLs to apply a dynamic means to permit our remote access.

This has been tested using inbound ACLs on interfaces, using the Zone-based Policy Firewall (ZBF/ZPF) and using CoPP itself. Anything that can define traffic based on an extended ACL should be able to use this mechanism, but I haven't tested beyond these.

ip access-list extended ACL_Internet_In
 dynamic DACL_Internet_In permit object-group OG_Knock_Services any any
 deny object-group OG_Knock_Services any any
 permit ip any any

The "permit ip any any" statement is for proof of concept only. Definitely replace this with whatever permit/deny statements are necessary for your configuration.

Now to tie it all together, we enable sequence numbers in the router’s syslog entries and use an Embedded Event Manager (EEM) applet to detect the sequence, verify it, and add the appropriate entry to the dynamic ACL. No configuration changes to the router are made in the process. The sequence numbers are important to this application as the EEM applet uses them to verify that the ports were triggered in the correct order.

There are two key variables that we need to set for the script. They are the name of the extended ACL that the script will operate on, and the idle timeout for the dynamic ACL entry. If there is no traffic during the timeout period, the knock will need to be repeated to regain access.

service sequence-numbers
!
event manager environment _knock_Services ACL_Internet_In
event manager environment _knock_Timeout 1
!
event manager applet EEM_Knock
 event tag _knock_Tag_E1 syslog pattern ".*%CP-6-UDP.*46543.$"
 event tag _knock_Tag_E2 syslog pattern ".*%CP-6-UDP.*28654.$"
 event tag _knock_Tag_E3 syslog pattern ".*%CP-6-UDP.*40204.$"
 event tag _knock_Tag_E4 syslog pattern ".*%CP-6-UDP.*57566.$"
 event tag _knock_Tag_E5 syslog pattern ".*%CP-6-UDP.*34515.$"
 trigger occurs 5 period 1
  correlate event _knock_Tag_E1 and event _knock_Tag_E2 and event _knock_Tag_E3 and event _knock_Tag_E4 and event _knock_Tag_E5
 action 0000 info type event reqinfo tag _knock_Tag_E1
 action 0010 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E1 _knock_Source_E1
 action 0020 info type event reqinfo tag _knock_Tag_E2
 action 0030 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E2 _knock_Source_E2
 action 0040 info type event reqinfo tag _knock_Tag_E3
 action 0050 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E3 _knock_Source_E3
 action 0060 info type event reqinfo tag _knock_Tag_E4
 action 0070 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E4 _knock_Source_E4
 action 0080 info type event reqinfo tag _knock_Tag_E5
 action 0090 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E5 _knock_Source_E5
 action 0100 if $_knock_Sequence_E1 lt $_knock_Sequence_E2
 action 0110  if $_knock_Sequence_E2 lt $_knock_Sequence_E3
 action 0120   if $_knock_Sequence_E3 lt $_knock_Sequence_E4
 action 0130    if $_knock_Sequence_E4 lt $_knock_Sequence_E5
 action 0140     if $_knock_Source_E1 eq $_knock_Source_E2
 action 0150      if $_knock_Source_E2 eq $_knock_Source_E3
 action 0160       if $_knock_Source_E3 eq $_knock_Source_E4
 action 0170        if $_knock_Source_E4 eq $_knock_Source_E5
 action 0180         cli command "enable"
 action 0190         cli command "access-template $_knock_Services D$_knock_Services host $_knock_Source_E1 any timeout $_knock_Timeout"
 action 0200        end
 action 0210       end
 action 0220      end
 action 0230     end
 action 0240    end
 action 0250   end
 action 0260  end
 action 0270 end
 action 0280 exit

Ideally, I’d like to be able to have this pull the event sequence from the OG_Knock object group. I suspect that this might be possible using a TCL script rather than an applet, but expedience is going to win out over elegance for the moment. The applet converter at http://www.marcuscom.com/convert_applet/ will create a good beginning for the adventurous.

Thanks to Joe Clarke for his input on extracting variables from multiple trigger events and for the link to the applet-to-script converter.

Clients

Triggering the knock sequence itself requires an extra piece of software. This are available for almost all platforms.

For iOS: KnockOnD
For Android: PortKnocker
For Linux/Unix/macOS: knock (included in the knockd server download)
For Windows: KnockKnock

The Whisper in the Wires

This provides a poor man's two-factor authentication (2FA), but I wouldn't really bill it as this. This is mostly because the trigger ports are semi-fixed. This is more about reducing the attack footprint of the router while still enabling remote access for support. I use client-side SSH keys for SSH access, and cloud-based 2FA solutions for AnyConnect access in addition to this.

Originally published on the Cisco Support Communities site.