@usex/mikrotik-mcp 4.13.0 → 4.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +237 -130
- package/dist/index.js +1 -1
- package/dist/shared/{cli-p6yc41tg.js → cli-9s02dq5d.js} +1 -1
- package/dist/shared/{cli-07emrpkr.js → cli-w6ecrhxa.js} +1161 -1041
- package/dist/shared/{library-pczgrhdt.js → library-212xbkwr.js} +1037 -956
- package/dist/shared/{library-txj981yv.js → library-c0fxxq85.js} +1 -1
- package/dist/ui/observability.html +60 -60
- package/package.json +1 -1
- package/prompts/harden-against-bruteforce.md +140 -0
- package/schemas/tool-catalog.json +7 -9
- package/schemas/tools/create_filter_rule.json +4 -6
package/package.json
CHANGED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harden-against-bruteforce
|
|
3
|
+
title: Harden a router under SSH/Telnet brute-force attack
|
|
4
|
+
description: Stop an active SSH/Telnet/Winbox brute-force flood on a MikroTik — block the attacking IPs, install an auto-ban ladder + management lock-down, disable telnet — WITHOUT locking the operator out (trusted-IP gate, snapshot, Safe Mode, preview-then-confirm), then check for a compromised admin account.
|
|
5
|
+
arguments:
|
|
6
|
+
- name: device
|
|
7
|
+
description: Which configured device is under attack (config key or label from list_mikrotik_devices). Omit to discover and choose.
|
|
8
|
+
required: false
|
|
9
|
+
- name: management_ip
|
|
10
|
+
description: The IP or CIDR you manage this router from (e.g. 203.0.113.5 or 203.0.113.0/24). This is the anti-lockout gate — it is accepted before any drop. Omit and you will be asked; never proceed without it.
|
|
11
|
+
required: false
|
|
12
|
+
- name: attacker_ips
|
|
13
|
+
description: Comma-separated source IPs already known to be attacking (optional). Leave empty — this workflow reads the device's own error/critical/security logs and extracts the attacking IPs itself; anything you pass here is added on top.
|
|
14
|
+
required: false
|
|
15
|
+
- name: trusted_list_name
|
|
16
|
+
description: Name of the existing trusted/management address-list on this device (e.g. Trust-IP). Omit to use/create a list named "trusted".
|
|
17
|
+
required: false
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
You have MCP access to a MikroTik RouterOS device that is under an **active
|
|
21
|
+
SSH/Telnet/Winbox brute-force attack**. Harden it against the attack **without
|
|
22
|
+
locking the operator out**. Anti-lockout is the top priority: the operator's own
|
|
23
|
+
management access must be accepted BEFORE any drop, every write goes through a
|
|
24
|
+
snapshot + Safe Mode, and you PREVIEW then wait for explicit approval before
|
|
25
|
+
applying anything.
|
|
26
|
+
|
|
27
|
+
Target device: {{device}}
|
|
28
|
+
Management IP/CIDR (anti-lockout gate): {{management_ip}}
|
|
29
|
+
Trusted address-list: {{trusted_list_name}}
|
|
30
|
+
Extra attacker IPs (optional): {{attacker_ips}}
|
|
31
|
+
|
|
32
|
+
## 0. Discover the device (do this FIRST)
|
|
33
|
+
|
|
34
|
+
- Call `list_mikrotik_devices`; resolve `{{device}}` against it — never substitute
|
|
35
|
+
a similar name. If it was omitted, present the inventory and ask which router is
|
|
36
|
+
under attack. Pass that `device` on every subsequent call.
|
|
37
|
+
|
|
38
|
+
## 1. Read and PRINT the attack logs, then extract the attacking IPs
|
|
39
|
+
|
|
40
|
+
- Pull the device's own logs and show them to the operator:
|
|
41
|
+
- `get_security_logs` — login failures and auth events, and
|
|
42
|
+
- `get_logs_by_severity` with severity `critical` and again with `error` — the
|
|
43
|
+
critical/error lines (e.g. `login failure for user … from <IP> via ssh/telnet`,
|
|
44
|
+
`ssh-cmd:admin@<IP> …`, service malfunctions).
|
|
45
|
+
- **Print those error + critical/security lines back to the operator** so they can
|
|
46
|
+
see the attack, then **extract every distinct source IP** from them (the address
|
|
47
|
+
after `from ` on login-failure lines and after `ssh-cmd:<user>@` on script lines).
|
|
48
|
+
Add any IPs given in `{{attacker_ips}}`. Present the deduplicated candidate
|
|
49
|
+
blocklist to the operator with a one-line reason per IP (which log lines it came
|
|
50
|
+
from). Do not write anything yet.
|
|
51
|
+
|
|
52
|
+
## 2. Anti-lockout gate — make sure YOU are trusted (before any drop)
|
|
53
|
+
|
|
54
|
+
- Read the trusted list: `list_address_lists` filtered to `{{trusted_list_name}}`
|
|
55
|
+
(default `trusted`). Determine whether it exists and which IPs/CIDRs it contains.
|
|
56
|
+
- Decide the operator's whitelist:
|
|
57
|
+
- If `{{management_ip}}` was provided, `add_address_list_entry` it into the trusted
|
|
58
|
+
list (clear comment), then re-read and confirm it is present.
|
|
59
|
+
- If `{{management_ip}}` is empty AND the trusted list is missing or empty,
|
|
60
|
+
**STOP and ASK the operator**: "Which IP/CIDR do you manage this router from? I
|
|
61
|
+
must whitelist it before dropping anything, or you could be locked out." Do NOT
|
|
62
|
+
proceed to any drop until a trusted entry exists. If their IP is dynamic, ask for
|
|
63
|
+
the ISP CIDR block, or advise connecting from a fixed IP / VPN first.
|
|
64
|
+
- Confirm the trusted list now contains at least the operator's own address.
|
|
65
|
+
|
|
66
|
+
## 3. Block the attacking IPs — but NEVER an entry that is trusted
|
|
67
|
+
|
|
68
|
+
- Cross-check the candidate blocklist from step 1 against the trusted list from
|
|
69
|
+
step 2. **Remove from the blocklist any IP that is in — or is covered by a CIDR in —
|
|
70
|
+
the trusted list**, plus the operator's `{{management_ip}}` and any RFC1918/LAN
|
|
71
|
+
address. NEVER blacklist a trusted or management IP (that is a self-inflicted
|
|
72
|
+
lockout). Show the operator the final block list and what was excluded and why.
|
|
73
|
+
- For each remaining IP, `add_address_list_entry` into the `security-shield-blacklist`
|
|
74
|
+
list (the list `harden_firewall`'s drop rule matches) with a long timeout (e.g. `4w`).
|
|
75
|
+
|
|
76
|
+
## 4. Snapshot (rollback point)
|
|
77
|
+
|
|
78
|
+
- `capture_config_snapshot` and report the returned **snapshot_id**. Everything
|
|
79
|
+
after this is reversible via `diff_config_snapshots from=<snapshot_id> to=live`.
|
|
80
|
+
|
|
81
|
+
## 5. PREVIEW the hardening (no writes) — then wait for approval
|
|
82
|
+
|
|
83
|
+
- Call `harden_firewall` with **`apply: false`** and:
|
|
84
|
+
- `preset: "standard"` (accept established/related, drop invalid, SSH/Winbox
|
|
85
|
+
brute-force auto-ban ladder, DDoS connection cap, SYN-flood + ICMP rate limits,
|
|
86
|
+
port-scan detection, anti-spoof),
|
|
87
|
+
- `protections: { raw_drops: true, mgmt_lockdown: true }` (drop blacklisted
|
|
88
|
+
sources before connection tracking, and restrict management ports to trusted),
|
|
89
|
+
- `trusted_sources: "{{trusted_list_name}}"` (the trusted address-list name — or
|
|
90
|
+
the operator's `{{management_ip}}` directly),
|
|
91
|
+
- `ssh_ports: "22"`, `mgmt_ports: "22,8291,8728"`,
|
|
92
|
+
- `safe_mode: true`.
|
|
93
|
+
- Show the operator the exact rules it will add, and confirm the safety accepts
|
|
94
|
+
(established/related + trusted) come BEFORE any drop. Do not proceed until the
|
|
95
|
+
operator approves.
|
|
96
|
+
|
|
97
|
+
## 6. Apply (only after explicit approval)
|
|
98
|
+
|
|
99
|
+
- Re-issue the same `harden_firewall` call with **`apply: true`, `safe_mode: true`**.
|
|
100
|
+
Safe Mode auto-reverts if a rule cuts the operator's own access. Report what was
|
|
101
|
+
applied and on which device.
|
|
102
|
+
|
|
103
|
+
## 7. Kill telnet and harden SSH
|
|
104
|
+
|
|
105
|
+
- `disable_ip_service` name=`telnet` — telnet is cleartext and a top brute-force
|
|
106
|
+
target; disable it outright (check `list_ip_services` first).
|
|
107
|
+
- Enable strong SSH crypto: `harden_ssh_service` (or set `strong-crypto=yes` on
|
|
108
|
+
`/ip ssh`; read the current state with `get_ssh_settings` first).
|
|
109
|
+
|
|
110
|
+
## 8. Verify — and tell the operator to reconnect fresh
|
|
111
|
+
|
|
112
|
+
- `audit_firewall_hardening` (or `run_security_hardening_audit`) — confirm the
|
|
113
|
+
brute-force ladder, drop-blacklisted, raw drops and management lock-down are
|
|
114
|
+
present, and that the trusted/established accepts sit ABOVE the drops.
|
|
115
|
+
- **Instruct the operator to reconnect from a SEPARATE, FRESH session now** to
|
|
116
|
+
confirm they are not locked out. If anything is wrong, roll back with
|
|
117
|
+
`diff_config_snapshots from=<snapshot_id> to=live` and the snapshot tools.
|
|
118
|
+
|
|
119
|
+
## 9. Check for a compromised admin (if the log showed logins, not just failures)
|
|
120
|
+
|
|
121
|
+
Brute-force logs that show a source actually _executing commands_ as a user (e.g.
|
|
122
|
+
`ssh-cmd:admin@<ip>` running an unexpected command) can mean the account was
|
|
123
|
+
guessed — a known RouterOS botnet pattern. If so:
|
|
124
|
+
|
|
125
|
+
- `list_users` — flag any account the operator doesn't recognise.
|
|
126
|
+
- `list_schedulers` and `list_scripts` — flag anything they didn't create (botnet
|
|
127
|
+
backdoors hide here). Do NOT delete anything without the operator's explicit say-so.
|
|
128
|
+
- Recommend rotating the admin password to a long random one and renaming the
|
|
129
|
+
`admin` user. Consider enabling management-from-trusted-only (already covered by
|
|
130
|
+
`mgmt_lockdown` in step 5) so brute-force can't reach SSH/Winbox at all.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
Hard rules: read and show the logs before deciding anything; **never blacklist an IP
|
|
135
|
+
that is in the trusted list or is the operator's own management IP** — cross-check
|
|
136
|
+
every candidate against the trusted list first; never apply a firewall drop unless
|
|
137
|
+
the operator's IP is in the trusted list (ask for it if unknown, don't guess); always
|
|
138
|
+
snapshot + Safe Mode before writes; preview then wait for explicit approval; state
|
|
139
|
+
plainly which device each change ran on; never delete accounts, schedulers or scripts
|
|
140
|
+
without the operator's confirmation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"generated": "by scripts/gen-schemas.ts — do not edit by hand",
|
|
5
5
|
"toolCount": 821,
|
|
6
6
|
"tools": [
|
|
@@ -12737,18 +12737,14 @@
|
|
|
12737
12737
|
"destructiveHint": false,
|
|
12738
12738
|
"openWorldHint": false
|
|
12739
12739
|
},
|
|
12740
|
-
"description": "Creates an IPv4 firewall FILTER rule (`/ip firewall filter`) — the accept/drop/reject decision table for traffic TO the router (chain=input), THROUGH it (forward) or FROM it (output). Use this to allow or block traffic. For address translation use the NAT tools, for packet marking use mangle, for pre-connection-tracking drops use raw, and for IPv6 use create_ipv6_filter_rule. Rule order matters (first match wins) — use place_before or move_filter_rule to position it. Returns the created rule's detail including its `.id`. connection_state: comma-separated e.g. \"established,related,new,invalid\". limit: RouterOS rate/burst string e.g. \"10,5:packet\" or \"10/1s:packet\". tcp_flags: RouterOS flag expression e.g. \"syn,!ack\". place_before: rule number or ID (*N) to insert before e.g. \"0\" or \"*3\".",
|
|
12740
|
+
"description": "Creates an IPv4 firewall FILTER rule (`/ip firewall filter`) — the accept/drop/reject decision table for traffic TO the router (chain=input), THROUGH it (forward) or FROM it (output). Use this to allow or block traffic. For address translation use the NAT tools, for packet marking use mangle, for pre-connection-tracking drops use raw, and for IPv6 use create_ipv6_filter_rule. `chain` also accepts a CUSTOM sub-chain name (e.g. detect-portscan) reached by an action=jump rule, and `action` supports add-src-to-address-list / add-dst-to-address-list (with `address_list` + `address_list_timeout`) for tagging/detection rules. Rule order matters (first match wins) — use place_before or move_filter_rule to position it. Returns the created rule's detail including its `.id`. connection_state: comma-separated e.g. \"established,related,new,invalid\". limit: RouterOS rate/burst string e.g. \"10,5:packet\" or \"10/1s:packet\". tcp_flags: RouterOS flag expression e.g. \"syn,!ack\". place_before: rule number or ID (*N) to insert before e.g. \"0\" or \"*3\".",
|
|
12741
12741
|
"inputSchema": {
|
|
12742
12742
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
12743
12743
|
"type": "object",
|
|
12744
12744
|
"properties": {
|
|
12745
12745
|
"chain": {
|
|
12746
12746
|
"type": "string",
|
|
12747
|
-
"
|
|
12748
|
-
"input",
|
|
12749
|
-
"forward",
|
|
12750
|
-
"output"
|
|
12751
|
-
]
|
|
12747
|
+
"description": "The built-in `input`, `forward`, or `output` chain — OR a custom sub-chain name (e.g. `detect-portscan`). A custom chain runs only when a rule with action=jump and jump-target=<that chain> sends traffic into it; use this to build tagging/detection sub-chains."
|
|
12752
12748
|
},
|
|
12753
12749
|
"action": {
|
|
12754
12750
|
"type": "string",
|
|
@@ -12761,7 +12757,9 @@
|
|
|
12761
12757
|
"passthrough",
|
|
12762
12758
|
"return",
|
|
12763
12759
|
"tarpit",
|
|
12764
|
-
"fasttrack-connection"
|
|
12760
|
+
"fasttrack-connection",
|
|
12761
|
+
"add-src-to-address-list",
|
|
12762
|
+
"add-dst-to-address-list"
|
|
12765
12763
|
]
|
|
12766
12764
|
},
|
|
12767
12765
|
"src_address": {
|
|
@@ -16044,7 +16042,7 @@
|
|
|
16044
16042
|
"destructiveHint": true,
|
|
16045
16043
|
"openWorldHint": false
|
|
16046
16044
|
},
|
|
16047
|
-
"description": "Installs the user-selected port-scan detection signatures into a dedicated `detect-portscan` sub-chain, gated by a single input jump that EXCLUDES a trusted address-list (so trusted sources are never tagged). These rules only add the source to an address list — they NEVER drop or block. NEVER call this with a guessed or default set of rule_types: it must only be called with signature IDs the user explicitly chose after seeing list_port_scan_detection_signatures — there is no select-all. Requires `trusted_list_name` (must already exist and be non-empty on the device) and the human acknowledgement `confirmed_trusted_list_includes_my_ip=true`. Captures a config snapshot
|
|
16045
|
+
"description": "Installs the user-selected port-scan detection signatures into a dedicated `detect-portscan` sub-chain, gated by a single input jump that EXCLUDES a trusted address-list (so trusted sources are never tagged). These rules only add the source to an address list — they NEVER drop or block. NEVER call this with a guessed or default set of rule_types: it must only be called with signature IDs the user explicitly chose after seeing list_port_scan_detection_signatures — there is no select-all. Requires `trusted_list_name` (must already exist and be non-empty on the device) and the human acknowledgement `confirmed_trusted_list_includes_my_ip=true`. Captures a config snapshot, then applies every write inside Safe Mode (auto-revert on session drop) — or, if Safe Mode is unavailable or goes silent on the device (a known flaky case on some RouterOS SSH builds), applies the writes DIRECTLY instead of aborting, since these rules only tag and cannot lock you out (the snapshot is the rollback point). Idempotent — a second identical run adds nothing. Enforcement/blocking of the tagged list is intentionally out of scope.",
|
|
16048
16046
|
"inputSchema": {
|
|
16049
16047
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
16050
16048
|
"type": "object",
|
|
@@ -5,11 +5,7 @@
|
|
|
5
5
|
"properties": {
|
|
6
6
|
"chain": {
|
|
7
7
|
"type": "string",
|
|
8
|
-
"
|
|
9
|
-
"input",
|
|
10
|
-
"forward",
|
|
11
|
-
"output"
|
|
12
|
-
]
|
|
8
|
+
"description": "The built-in `input`, `forward`, or `output` chain — OR a custom sub-chain name (e.g. `detect-portscan`). A custom chain runs only when a rule with action=jump and jump-target=<that chain> sends traffic into it; use this to build tagging/detection sub-chains."
|
|
13
9
|
},
|
|
14
10
|
"action": {
|
|
15
11
|
"type": "string",
|
|
@@ -22,7 +18,9 @@
|
|
|
22
18
|
"passthrough",
|
|
23
19
|
"return",
|
|
24
20
|
"tarpit",
|
|
25
|
-
"fasttrack-connection"
|
|
21
|
+
"fasttrack-connection",
|
|
22
|
+
"add-src-to-address-list",
|
|
23
|
+
"add-dst-to-address-list"
|
|
26
24
|
]
|
|
27
25
|
},
|
|
28
26
|
"src_address": {
|