OPERATIONS GUIDE

v6.11.0

Attack Detection

Practical setup, operations and limits for Attack Detection.

Every other security feature in this repo answers "is this box configured well?". This one answers "is someone attacking it right now, and what did they do?" — the question you have when it is happening.

It is not hypothetical. The first log this feature was pointed at, on a live router, had an attack in progress: one source hammering the API service about once a second for hours, and a second walking usernames over SSH. The box was doing exactly what it was configured to do — logging each failure and moving on. Nobody was going to read those logs.

Seven tools, in the Attack Detection module (Security):

ToolRiskWhat it does
scan_for_attacksREADDetect across the fleet now; changes nothing
list_attack_incidentsREADRecorded incidents, worst first
get_attack_incidentREADOne incident with all of its evidence
block_attackerWRITEBlock a source; dry run unless confirm: true
unblock_attackerDESTRUCTIVELift a block, keeping the record of why
list_attack_responsesREADEvery block: what, why, when it expires
configure_attack_responseREADThe policy, and why an address would be refused

What this is NOT

  • Not an IDS. No packet inspection, no payload signatures, no rule database to keep current. It reasons over what RouterOS already writes down.
  • Not DDoS mitigation. A volumetric flood is stopped by the router's own rate limiting (harden_firewall) or upstream — not by a host that finds out thirty seconds later.
  • Not a replacement for hardening. Detecting a brute force against a Winbox port open to the internet is worth less than closing it. Every incident links to the tool that would have prevented it.

Detection is free; response is not

Auto-blocking is the most dangerous thing this repo can do. Three failure modes, each of which has taken down a real network somewhere:

  1. Locking the operator out. An admin's VPN reconnect loop and an attacker's retry loop look similar from a log line.
  2. Blocking a forged source. A flood with spoofed addresses turns any auto-blocker into a weapon aimed at whoever the attacker names — your upstream gateway, your resolver, a customer.
  3. Blocking the monitoring. This server's own health probe logs a successful SSH login every few seconds. A naive "unusual login" detector alerts on itself, forever.

So the guards below are enforced in code and cannot be configured away:

GuardWhat it does
Detect-only defaultmode: "detect" ships as the default. Nothing on a device changes.
Spoofable evidence refusedA detector whose source can be forged never triggers a response — not even a manual one with confirm: true.
Never-block setSeeded from your deployment: every configured device, this server's own management path, and the private ranges.
Timed by defaultA block expires on its own. Permanent needs an explicit empty timeout and confirm: true.
Rate cap6 blocks per device per hour. Hitting it refuses loudly and says what was not blocked — silence there would be the worst outcome.
One rule per deviceBlocking is an address-list insert behind a single raw drop rule. Never a firewall rule per attacker.
Breach escalatesA confirmed breach pages a human instead of blocking. The attacker is already in; blocking the source does not undo that.

Every refusal says which rule stopped it. Ask directly:

configure_attack_response check_address=203.0.113.7

The detectors

DetectorFires onDefaultSpoofable
brute-forcefailed logins from one source10 / 5 minno
credential-sprayone source trying several usernames3 / 10 minno
successful-after-faila login that succeeded after failuresanyno
new-admin-sourcefirst successful login from an unknown public IPfirstno
port-scanthe device's own detect-portscan listmembershipno
firewall-drop-stormlogged firewall hits from one source100 / 5minyes
service-exposure-hita management port reached from the internetanyno
post-compromiseunexplained change to a security-relevant menuanyno

successful-after-fail is the one to wake up for, and it never auto-responds.

When a detector cannot run, it says so

A detector that quietly finds nothing because logging was never enabled reports a calm network — the most dangerous output this feature could produce. So each reports itself unavailable with the single thing that would fix it:

Detectors that could not run:
  · firewall-drop-storm — no firewall rule on this device logs its hits
      fix: set `log=yes log-prefix=drop` on the rules you want visibility into
  · post-compromise — no configuration-change source
      fix: set a Drift Guard baseline with config_set_baseline

Two of these are worth knowing about up front:

  • RouterOS does not audit configuration changes to the log at all. Verified against a live device. So post-compromise reads from Drift Guard (config_set_baseline + config_check_drift), which already computes exactly that against your golden config.
  • firewall-drop-storm and service-exposure-hit need log=yes on the rules you care about. Turning that on is a config change this feature will not make for you.

The learning window

new-admin-source is worthless on day one and dangerous if trusted then. The engine builds a 7-day baseline of which sources normally administer each device and reports the detector as learning until it has the data.

Incidents, not findings

Signals group into an incident keyed by source, across devices. One source hitting three routers is one incident with three affected devices — the pattern no single router can see, and the whole argument for running this on the host.

Each incident carries a stage — recon → attempt → breach → persistence — inferred from which detectors fired. That is what turns a list into a sentence:

203.0.113.7 across 2 devices (edge, core): tagged by the device's own port-scan signatures; then 240 failed logins within 5 min; then logged in as admin after 12 failed attempts — treat this device as compromised until proven otherwise.

Confidence is low / medium / high / confirmed. confirmed requires evidence that something actually happened — a success after failures, or a config change nobody can account for. Volume never confirms anything, however much of it there is.

A stage only ever escalates. An attacker who got in yesterday did not un-get-in because today's sweep saw only scanning.

Turning it on

{
  "attacks": {
    "enabled": true,
    "mode": "detect"
  }
}

Watch it for a week. Read what it finds. Then, if you want it to act:

{
  "attacks": {
    "enabled": true,
    "mode": "respond",
    "minConfidence": "high",
    "autoRespondTo": ["brute-force", "credential-spray"],
    "blockTimeout": "1h",
    "maxBlocksPerHour": 6,
    "neverBlock": ["198.51.100.0/24"]
  }
}

Full block:

KeyDefaultWhat it does
enabledfalseMaster switch
mode"detect"detect or respond
pollSeconds120Sweep interval
windowMinutes10How far back each sweep reads
concurrency4Devices swept at once
minConfidence"high"Floor for an automatic response
blockTimeout"1h"How long a block lasts
maxBlocksPerHour6Per-device response cap
neverBlock[]Your additions to the protected set
autoRespondTo["brute-force","credential-spray"]Detectors allowed to act
learningDays7Baseline window
readScanListtrueRead the on-device port-scan list
retainDays90Incident retention

The sweep window is deliberately longer than the poll interval: /log is a ring buffer with no cursor, so non-overlapping windows would silently drop everything in between. The overlap re-delivers lines, and every event is de-duplicated on (device, time, message).

Where the alerts go

Incidents are emitted into the existing alerting bus as attack events, so they reach whatever channels you already configured:

alerts:
  rules:
    - id: attack-confirmed
      when: { event: attack, isError: true }
      channels: [slack]

What it builds on

Nothing here re-implements what the router already does well:

Existing featureHow this uses it
add_port_scan_detection_rulesReads its address list. The router sees every packet; the host sees none.
harden_firewallRecommended from any volumetric incident — the right place for that defence.
subscribe_threat_feedAn attacker already on a feed is higher confidence.
Drift GuardThe input to post-compromise.
AlertingThe delivery path.
block_device, address listsThe blocking primitives. No new mechanism.

On this page