falco: add cryptominer/runtime-security scanner wired into netdata
Detects unauthorized miners (and other runtime threats) via Falco's syscall/eBPF monitoring, following the 2026-08-06 gitea/xmrig compromise. Wired into netdata rather than a new notification channel: Falco exposes a Prometheus /metrics endpoint, netdata scrapes it, and a new health.d alarm pages through netdata's already-configured Telegram bot - no new alerting infra needed. Includes a custom process-name rule for known miner binaries (the stock Stratum-protocol rule wouldn't have caught the actual gitea incident, which used a bare host:port with no scheme prefix), an outbound miner-pool-port rule as a second layer, and rule_matching: all in the Falco config - without it, Falco silently drops all but the first matching rule per event, which would have suppressed our custom rule whenever a stock rule also matched the same process.
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
# Overrides merged on top of Falco's built-in default config.
|
||||||
|
# Enables the Prometheus /metrics endpoint so netdata can scrape rule-trigger
|
||||||
|
# counts and alert via the existing Telegram notifier.
|
||||||
|
webserver:
|
||||||
|
enabled: true
|
||||||
|
listen_port: 8765
|
||||||
|
k8s_healthz_endpoint: /healthz
|
||||||
|
prometheus_metrics_enabled: true
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
interval: 15s
|
||||||
|
output_rule: true
|
||||||
|
rules_counters_enabled: true
|
||||||
|
|
||||||
|
# Falco's default is "first rule wins" per event type, for performance -
|
||||||
|
# confirmed by testing: an execve matching both the stock "Drop and execute
|
||||||
|
# new binary in container" rule AND our custom miner-detect rule only
|
||||||
|
# emitted the stock rule's alert, silently. `all` disables that
|
||||||
|
# short-circuit so every matching rule fires independently (added in
|
||||||
|
# Falco 0.36.0). Accepting the modest extra CPU cost - a security tool
|
||||||
|
# silently dropping the alert it exists to send is worse.
|
||||||
|
rule_matching: all
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
networks:
|
||||||
|
default:
|
||||||
|
# Falco's own default bridge network, plus netdata's network so netdata
|
||||||
|
# can scrape http://falco:8765/metrics by container name.
|
||||||
|
netdata_default:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
falco:
|
||||||
|
image: falcosecurity/falco:0.44.1
|
||||||
|
container_name: falco
|
||||||
|
restart: always
|
||||||
|
cap_drop:
|
||||||
|
- all
|
||||||
|
cap_add:
|
||||||
|
- sys_admin
|
||||||
|
- sys_resource
|
||||||
|
- sys_ptrace
|
||||||
|
security_opt:
|
||||||
|
- apparmor:unconfined
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- netdata_default
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8765:8765"
|
||||||
|
volumes:
|
||||||
|
- /sys/kernel/tracing:/sys/kernel/tracing:ro
|
||||||
|
- /var/run/docker.sock:/host/var/run/docker.sock:ro
|
||||||
|
- /proc:/host/proc:ro
|
||||||
|
- /etc:/host/etc:ro
|
||||||
|
- ./config/falco.yaml:/etc/falco/config.d/telemetry.yaml:ro
|
||||||
|
- ./rules/miner-detect.yaml:/etc/falco/rules.d/miner-detect.yaml:ro
|
||||||
|
- ./rules/miner-pool-ports.yaml:/etc/falco/rules.d/miner-pool-ports.yaml:ro
|
||||||
|
- ./rules/tune-noise.yaml:/etc/falco/rules.d/tune-noise.yaml:ro
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Custom rule: catch known cryptominer binaries by process name directly.
|
||||||
|
# Added after the 2026-08-06 gitea/xmrig compromise, where the miner was
|
||||||
|
# invoked as `--url=host:port` with no "stratum+tcp" scheme prefix, which
|
||||||
|
# would NOT have matched Falco's stock "Detect crypto miners using the
|
||||||
|
# Stratum protocol" rule. Matching the binary name is the reliable signal.
|
||||||
|
|
||||||
|
- list: miner_binary_names
|
||||||
|
items: [
|
||||||
|
xmrig, xmrig-notls, xmr-stak, xmr-stak-cpu, xmr-stak-rx,
|
||||||
|
minerd, cpuminer, ccminer, cgminer, bfgminer,
|
||||||
|
nanominer, t-rex, trex, teamredminer, phoenixminer, lolminer,
|
||||||
|
ethminer, nbminer, gminer, srbminer, xmrigMiner,
|
||||||
|
]
|
||||||
|
|
||||||
|
- rule: Detect crypto miner binary execution
|
||||||
|
desc: >
|
||||||
|
A process matching a known cryptocurrency miner binary name was executed.
|
||||||
|
Catches unauthorized miners even when the command line doesn't use a
|
||||||
|
stratum+tcp:// style URL.
|
||||||
|
condition: >
|
||||||
|
spawned_process and proc.name in (miner_binary_names)
|
||||||
|
output: >
|
||||||
|
Cryptominer binary execution detected
|
||||||
|
(user=%user.name command=%proc.cmdline container=%container.name
|
||||||
|
image=%container.image.repository pid=%proc.pid)
|
||||||
|
priority: CRITICAL
|
||||||
|
tags: [miners, process, mitre_impact]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Supplementary defense-in-depth: outbound connections to common mining-pool
|
||||||
|
# ports. NOTE: this image's shipped falco_rules.yaml has no built-in
|
||||||
|
# miner/stratum/pool rules to "enable" (checked directly - none exist), so
|
||||||
|
# this is written from scratch rather than overriding a stock rule. It's
|
||||||
|
# a weaker signal than miner-detect.yaml's process-name match (our actual
|
||||||
|
# incident used port 10128, which isn't in this list - pools use arbitrary
|
||||||
|
# ports), kept anyway as a second layer.
|
||||||
|
|
||||||
|
- list: miner_pool_ports
|
||||||
|
# Kept to well-corroborated fixed mining-pool ports only, all well below
|
||||||
|
# the Linux ephemeral port range (32768-60999). Two entries originally
|
||||||
|
# added here without solid sourcing (45560, 45700) fell inside that
|
||||||
|
# ephemeral range and produced an immediate false positive against
|
||||||
|
# qbittorrent_eXoDOS's own randomly-assigned outbound source port -
|
||||||
|
# removed.
|
||||||
|
items: [3333, 3334, 4444, 5555, 5556, 7777, 8888, 9999, 14444]
|
||||||
|
|
||||||
|
- rule: Detect outbound connection to common miner pool port
|
||||||
|
desc: Outbound connection to a TCP port commonly used by cryptomining pools.
|
||||||
|
condition: >
|
||||||
|
outbound and evt.type in (connect, sendto, sendmsg)
|
||||||
|
and fd.rport in (miner_pool_ports)
|
||||||
|
output: >
|
||||||
|
Outbound connection to common miner-pool port
|
||||||
|
(user=%user.name command=%proc.cmdline connection=%fd.name
|
||||||
|
container=%container.name image=%container.image.repository)
|
||||||
|
priority: WARNING
|
||||||
|
tags: [miners, network, mitre_impact]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Tune out stock-rule false positives observed on this specific host, so the
|
||||||
|
# broad netdata "any Falco match" alarm only pages for things worth paging
|
||||||
|
# for. Uses Falco's own user_* customization macros rather than disabling
|
||||||
|
# whole rules, so the underlying security check stays active for everything
|
||||||
|
# else. Add entries here as new noisy defaults turn up - don't let this list
|
||||||
|
# grow into blinding Falco to anything actually meaningful.
|
||||||
|
|
||||||
|
# "Sensitive file opened for reading by non-trusted program" fired every
|
||||||
|
# ~10s from inbox-zero-db's pg_isready healthcheck touching /etc/shadow -
|
||||||
|
# NSS/libc user-lookup behavior during process init, not credential
|
||||||
|
# harvesting. Scoped to just this binary, not a blanket rule disable.
|
||||||
|
- macro: user_known_read_sensitive_files_activities
|
||||||
|
condition: (proc.name = pg_isready)
|
||||||
Reference in New Issue
Block a user