Detection rules › Splunk

Cisco IOS XE Reconnaissance Command Activity

Status
production
Severity
low
Group by
_time, dest, src_ip, user
Author
Nasreddine Bencherchali
Source
github.com/splunk/security_content

This analytic detects bursts of Cisco IOS or NX-OS discovery commands associated with Salt Typhoon tradecraft. Adversaries who gain initial access to network infrastructure devices typically perform systematic reconnaissance to understand the device configuration, network topology, security policies, connected systems, and potential attack paths. This reconnaissance phase involves executing multiple "show" commands to enumerate device details, running configurations, active connections, routing information, and VPN sessions.

MITRE ATT&CK coverage

Rule body splunk

name: Cisco IOS XE Reconnaissance Command Activity
id: 71ac5328-2364-40cf-8381-92b8dc531399
version: 1
creation_date: '2026-05-19'
modification_date: '2026-05-20'
author: Nasreddine Bencherchali
status: production
type: Anomaly
description: |
    This analytic detects bursts of Cisco IOS or NX-OS discovery commands associated with Salt Typhoon tradecraft.
    Adversaries who gain initial access to network infrastructure devices typically perform systematic reconnaissance to understand the device configuration, network topology, security policies, connected systems, and potential attack paths.
    This reconnaissance phase involves executing multiple "show" commands to enumerate device details, running configurations, active connections, routing information, and VPN sessions.
data_source:
    - Cisco IOS Logs
search: |-
    `cisco_ios`
    facility IN ("AAA", "HA_EM")
    mnemonic IN ("AAA_ACCOUNTING_MESSAGE", "LOG")
    message_text IN (
        "*show running-config*",
        "*show tacacs*",
        "*show cdp neighbors*",
        "*show file systems*",
        "*dir bootflash:*",
        "*show clock*",
        "*show platform software status control-processor brief*",
        "*terminal length 0*",
        "*terminal width 0*"
    )
    
    | rex field=message_text "^\w+:(?<aaa_src>[^:@]+)(?:@[^:]*)?:(?<aaa_user>[^:]*):(?<aaa_command>.*?)(?:\s+\((?<aaa_result>SUCCESS|FAILURE)\))?$"
    | rex field=message_text "^(?:[^:]+:\s+)?(?:catchall:\s+)?(?<eem_command>.+?)\s*$"
    | eval command=lower(trim(coalesce(aaa_command, eem_command, "")))
    
    | eval command_type=case(
        like(command, "show running-config%"), "show_conf",
        like(command, "show tacacs%"), "show_tacacs",
        like(command, "show cdp neighbors detail%"), "show_cdp",
        like(command, "show cdp neighbors%"), "show_cdp",
        like(command, "show file systems%"), "show_file",
        like(command, "dir bootflash:%"), "dir_bootflash",
        like(command, "show clock%"), "show_clock",
        like(command, "show platform software status control-processor brief%"), "show_platform",
        like(command, "terminal length 0%"), "terminal",
        like(command, "terminal width 0%"), "terminal",
        true(), null())
    | where isnotnull(command_type)
    | eval user=coalesce(aaa_user, user, "unknown")
    | eval src_ip=coalesce(aaa_src, src_ip, "unknown")
    | eval dest=coalesce(host, dvc, dest, "unknown")
    | bin _time span=5m
    | stats count min(_time) as firstTime
                  max(_time) as lastTime
                  dc(command_type) as unique_recon_commands
                  values(command_type) as command_types
                  values(command) as commands
      by _time dest user src_ip
    | where unique_recon_commands >= 4
    | `security_content_ctime(firstTime)`
    | `security_content_ctime(lastTime)`
    | `cisco_ios_xe_reconnaissance_command_activity_filter`
how_to_implement: |
    Use the Cisco Catalyst Add-on for Splunk (https://splunkbase.splunk.com/app/7538) to Ingest Cisco IOS-XE syslog with sourcetype "cisco:ios".
    On Nexus, local accounting can emit "%AAA-6-AAA_ACCOUNTING_MESSAGE".
    On Catalyst, use TACACS+ command accounting or EEM catchall syslog for exec command visibility.
known_false_positives: |
    Network audits and troubleshooting can produce similar command bursts. Tune the command list, threshold, and approved administrators for the environment.
references:
    - https://www.cisa.gov/news-events/cybersecurity-advisories/aa25-239a
    - https://blog.talosintelligence.com/salt-typhoon-analysis/
drilldown_searches:
    - name: View the detection results for - "$dest$"
      search: '%original_detection_search% | search  dest = "$dest$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
    - name: View risk events for the last 7 days for - "$dest$"
      search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$dest$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
      earliest_offset: 7d
      latest_offset: "0"
intermediate_findings:
    entities:
        - field: dest
          type: system
          score: 20
          message: User $user$ executed $unique_recon_commands$ Cisco reconnaissance command categories on $dest$.
threat_objects:
    - field: commands
      type: command
analytic_story:
    - Salt Typhoon
asset_type: Network
mitre_attack_id:
    - T1082
    - T1016
    - T1590
product:
    - Splunk Enterprise
    - Splunk Enterprise Security
    - Splunk Cloud
category: application
security_domain: network
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/emerging_threats/SaltTyphoon/salttyphoon_cisco.log
          source: ctb:nexus:syslog
          sourcetype: cisco:ios
      test_type: unit

Stages and Predicates

Stage 1: search

`cisco_ios`
facility IN ("AAA", "HA_EM")
mnemonic IN ("AAA_ACCOUNTING_MESSAGE", "LOG")
message_text IN (
    "*show running-config*",
    "*show tacacs*",
    "*show cdp neighbors*",
    "*show file systems*",
    "*dir bootflash:*",
    "*show clock*",
    "*show platform software status control-processor brief*",
    "*terminal length 0*",
    "*terminal width 0*"
)

Stage 2: rex

| rex field=message_text "^\w+:(?<aaa_src>[^:@]+)(?:@[^:]*)?:(?<aaa_user>[^:]*):(?<aaa_command>.*?)(?:\s+\((?<aaa_result>SUCCESS|FAILURE)\))?$"

Stage 3: rex

| rex field=message_text "^(?:[^:]+:\s+)?(?:catchall:\s+)?(?<eem_command>.+?)\s*$"

Stage 4: eval

| eval command=lower(trim(coalesce(aaa_command, eem_command, "")))

Stage 5: eval

| eval command_type=case(
    like(command, "show running-config%"), "show_conf",
    like(command, "show tacacs%"), "show_tacacs",
    like(command, "show cdp neighbors detail%"), "show_cdp",
    like(command, "show cdp neighbors%"), "show_cdp",
    like(command, "show file systems%"), "show_file",
    like(command, "dir bootflash:%"), "dir_bootflash",
    like(command, "show clock%"), "show_clock",
    like(command, "show platform software status control-processor brief%"), "show_platform",
    like(command, "terminal length 0%"), "terminal",
    like(command, "terminal width 0%"), "terminal",
    true(), null())
command_type =
iflike(command, "show running-config%")"show_conf"
eliflike(command, "show tacacs%")"show_tacacs"
eliflike(command, "show cdp neighbors detail%")"show_cdp"
eliflike(command, "show cdp neighbors%")"show_cdp"
eliflike(command, "show file systems%")"show_file"
eliflike(command, "dir bootflash:%")"dir_bootflash"
eliflike(command, "show clock%")"show_clock"
eliflike(command, "show platform software status control-processor brief%")"show_platform"
eliflike(command, "terminal length 0%")"terminal"
eliflike(command, "terminal width 0%")"terminal"
elsenull()

Stage 6: where

| where isnotnull(command_type)

Stage 7: eval

| eval user=coalesce(aaa_user, user, "unknown")

Stage 8: eval

| eval src_ip=coalesce(aaa_src, src_ip, "unknown")

Stage 9: eval

| eval dest=coalesce(host, dvc, dest, "unknown")

Stage 10: bucket

| bin _time span=5m

Stage 11: stats

| stats count min(_time) as firstTime
              max(_time) as lastTime
              dc(command_type) as unique_recon_commands
              values(command_type) as command_types
              values(command) as commands
  by _time dest user src_ip

Stage 12: where

| where unique_recon_commands >= 4

Stage 13: search

| `security_content_ctime(firstTime)`

Stage 14: search

| `security_content_ctime(lastTime)`

Stage 15: search

| `cisco_ios_xe_reconnaissance_command_activity_filter`

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
command_typeis_not_null
  • (no value, null check)
facilityin
  • "AAA"
  • "HA_EM"
message_textin
  • "*dir bootflash:*"
  • "*show cdp neighbors*"
  • "*show clock*"
  • "*show file systems*"
  • "*show platform software status control-processor brief*"
  • "*show running-config*"
  • "*show tacacs*"
  • "*terminal length 0*"
  • "*terminal width 0*"
mnemonicin
  • "AAA_ACCOUNTING_MESSAGE"
  • "LOG"
sourcetypeeq
  • cisco:ios
unique_recon_commandsge
  • 4