Testing for Cisco security vulnerabilities with Ansible
Table of contents
- Cisco security advisories
- “Jaguar Tooth” Malware
- Affected Cisco SNMP MIBs
- Ansible Playbook/Role
- Deploy workaround
- Conclusions/Notes
Cisco security advisories
In this blog post I’m demonstrating how to test for Cisco security vulnerabilities with Ansible. In most Cisco security advisories CLI commands are presented to test if a switch or router is affected by a specific vulnerability. Those CLI commands can be performed by a network engineer manually but with a certain amount of devices it’s better to automate those tests. Workarounds (also CLI commands) to fix specific issues can be rolled out as well.
“Jaguar Tooth” Malware
As example, I’m showing how to test for “Jaguar Tooth” which is, in my opinion, an interesting Malware for Cisco IOS / IOSXE devices. A CISA (Cybersecurity & Infrastructure Security Agency) Alert is available at: APT28 Exploits Known Vulnerability To Carry Out Reconnaissance and Deploy Malware on Cisco Routers. A Malware analysis report is provided by the british NCSC (National Cyber Security Center) at Jaguar Tooth Malware Analysis Report. This Malware is expoiting an old CVE described in this Cisco Security Advisory from 2017: SNMP Remote Code Execution Vulnerabilities in Cisco IOS and IOSXE Software.
Affected Cisco SNMP MIBs
As Cisco writes in SNMP Remote Code Execution Vulnerabilities in Cisco IOS and IOSXE Software, the workaround is to disable specific SNMP MIBs (Simple Network Management Protocol Management Information Base). An Ansible Playbook/Role can check if an exclusion for those SNMP MIBs is in place on a Cisco device.
Ansible Playbook/Role
Ansible Playbook
For more flexibility I decided to use an Ansible Role called “cisco_security”. This Role can be called by an Ansible Playbook named “cisco_security_checks.yml”. The playbook is quite simple and can look like this:
---
- hosts: cisco_switches:
gather_facts: no
roles:
- cisco_security
Ansible Role
The Ansible role “cisco_security” can be initialized with the command ansible-galaxy init cisco_security. The “main.yml” file in the folder “tasks” is including the file “check_jaguar_tooth.yml” and can look like this:
---
- include_tasks: check_jaguar_tooth.yml
Ansible Task
This task (file: “check_jaguar_tooth.yml”) is doing the main job to test for SNMPMIB command lines. The task is organized as a “block” and is working like this:
Check SNMPMIBS
Create CSV Data
Write CVS report
The Check SNMP tasks are executing a Cisco IOS / IOSXE command line via “cisco.ios.ios_command” to count the lines containing a specific string (the SNMPMIB). The result (count) is then registered as variable.
Those variables can be used to build a simple CSV data structure, the variables are just separated by “;”. The output format in the CSV file does not have to look nicely, but should contain the amount of lines matching the string with the SNMPMIB. If the lines counted are “0” then the SNMPMIB is not in place and some action like implement the workaround or better, fix the IOS / IOSXE to the latest version should be performed.
---
#
# Check for Jaguar Tooth Malware
#
- name: "Check for Jaguar Tooth Malware"
block:
#
# Check included SNMP MIBS
#
- name: "SNMP-MIB: iso included"
cisco.ios.ios_command:
commands: show running-config | count iso included"
register: snmpmib_iso_included
- name: "SNMP MIB: internet included"
cisco.ios.cli_command:
commands: show running-config | count internet included"
register: snmpmib_internet_included
#
# Check excluded SNMP MIBS
#
- name: "SNMP MIB: snmpUsmMIB excluded"
cisco.ios.cli_command:
commands: show running-config | count snmpUsmMIB excluded"
register: snmpmib_snmpUsmMIB_excluded
- name: "SNMP MIB: snmpVacmMIB excluded"
cisco.ios.cli_command:
commands: show running-config | count snmpVacmMIB excluded"
register: snmpmib_snmpVacmMIB_excluded
- name: "SNMP MIB: snmpCommunityMIB excluded"
cisco.ios.cli_command:
commands: show running-config | count snmpCommunityMIB excluded"
register: snmpmib_snmpCommunityMIB_excluded
- name: "SNMP MIB: ciscoMgmt.252 excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoMgmt.252 excluded"
register: snmpmib_ciscoMgmt_252_excluded
- name: "SNMP MIB: transmission.94 excluded"
cisco.ios.cli_command:
commands: show running-config | count transmission.94 excluded"
register: snmpmib_transmission_94_excluded
- name: "SNMP MIB: mib-2.34.9 excluded"
cisco.ios.cli_command:
commands: show running-config | count mib-2.34.9 excluded"
register: snmpmib_mib2_34_9_excluded
- name: "SNMP MIB: ciscoMgmt.35 excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoMgmt.35 excluded"
register: snmpmib_ciscoMgmt_35_excluded
- name: "SNMP MIB: ciscoMgmt.95 excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoMgmt.95 excluded"
register: snmpmib_ciscoMgmt_95_excluded
- name: "SNMP MIB: ciscoMgmt.130 excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoMgmt.130 excluded"
register: snmpmib_ciscoMgmt_130_excluded
- name: "SNMP MIB: ciscoMgmt.219 excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoMgmt.219 excluded"
register: snmpmib_ciscoMgmt_219_excluded
- name: "SNMP MIB: ciscoMgmt.254 excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoMgmt.254 excluded"
register: snmpmib_ciscoMgmt_254_excluded
- name: "SNMP MIB: ciscoMabMIB excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoMabMIB excluded"
register: snmpmib_ciscoMabMIB_excluded
- name: "SNMP MIB: ciscoExperiment.997 excluded"
cisco.ios.cli_command:
commands: show running-config | count ciscoExperiment.997 excluded"
register: snmpmib_ciscoExperiment_997_excluded
#
# Create CSV data
#
- name: "Create CSV data"
set_fact:
csv_data: "{{ ansible_net_hostname }};
MIB: ISO - {{ snmpmib_iso_included.stdout }};
MIB: Internet - {{ snmpmib_internet_included.stdout }};
MIB: snmpUsmMIB - {{ snmpmib_snmpUsmMIB_excluded.stdout }};
MIB: snmpVacmMIB - {{ snmpmib_snmpVacmMIB_excluded.stdout }};
MIB: snmpCommunityMIB - {{ snmpmib_snmpCommunityMIB_excluded.stdout }};
MIB: ciscoMgmt.252 - {{ snmpmib_ciscoMgmt_252_excluded.stdout }};
MIB: transmission.94 - {{ snmpmib_transmission_94_excluded.stdout }};
MIB: mib-2.34.9 - {{ snmpmib_mib2_34_9_excluded.stdout }};
MIB: ciscoMgmt.35 - {{ snmpmib_ciscoMgmt_35_excluded.stdout }};
MIB: ciscoMgmt.95 - {{ snmpmib_ciscoMgmt_95_excluded.stdout }};
MIB: ciscoMgmt.130 - {{ snmpmib_ciscoMgmt_130_excluded.stdout }};
MIB: ciscoMgmt.219 - {{ snmpmib_ciscoMgmt_219_excluded.stdout }};
MIB: ciscoMgmt.254 - {{ snmpmib_ciscoMgmt_254_excluded.stdout }};
MIB: ciscoMabMIB - {{ snmpmib_ciscoMabMIB_excluded.stdout }};
MIB: ciscoExperiment.997 - {{ snmpmib_ciscoExperiment_997_excluded.stdout }}"
#
# Write CSV report file
#
- name: "Write CSV report file"
lineinfile:
dest: security_jaguar_tooth.csv
line: "{{ csv_data }}"
create: yes
Deploy workaround
To implement the workaround another Ansible task file called “workaround_jaguar_tooth.yml” can be used and included in the Role. This task file is executing the suggested command lines from the Cisco Advisory. In addition a MotD (Message of the Day) banner is set to indicate that this Cisco device has the workaround deployed. The Ansible task file might look like this:
---
#
# Workaround for Jaguar Tooth Malware
#
- name: "Workaround for Jaguar Tooth Malware"
block:
#
# Deploy SNMP MIB exclusions
# Note: Set SNMPv2c community string in (vars/main.yml)
#
- name: "Deploy SNMP MIB exclusions for Jaguar Tooth"
cisco.ios.ios_command:
commands:
- conf t
- banner modt ^CISA/NCSC/Cisco Advisory Jaguar Tooth^
- snmp-server view NO_BAD_SNMP iso included
- snmp-server view NO_BAD_SNMP internet included
- snmp-server view NO_BAD_SNMP snmpUsmMIB excluded
- snmp-server view NO_BAD_SNMP snmpVacmMIB excluded
- snmp-server view NO_BAD_SNMP snmpCommunityMIB excluded
- snmp-server view NO_BAD_SNMP ciscoMgmt.252 excluded
- snmp-server view NO_BAD_SNMP transmission.94 excluded
- snmp-server view NO_BAD_SNMP mib-2.34.9 excluded
- snmp-server view NO_BAD_SNMP ciscoMgmt.35 excluded
- snmp-server view NO_BAD_SNMP ciscoMgmt.95 excluded
- snmp-server view NO_BAD_SNMP ciscoMgmt.130 excluded
- snmp-server view NO_BAD_SNMP ciscoMgmt.219 excluded
- snmp-server view NO_BAD_SNMP ciscoMgmt.254 excluded
- snmp-server view NO_BAD_SNMP ciscoMabMIB excluded
- snmp-server view NO_BAD_SNMP ciscoExperiment.997 excluded
- snmp-server community "{{ SNMP_COMMUNITY }}" view NO_BAD_SNMP RO
- exit
- wr mem
Conclusions/Notes
1) Ansible can support a network engineer with security checks, especially if those can be performed by command lines
2) Ansible can support a network engineer to implement the suggested workaround
3) A workaround is a workaround. The device however should be patched with the latest version