Skip to content

diagonalciso/wazuh-attackmap

Repository files navigation

wazuh-attackmap

lint

⚠️ BETA / PROOF-OF-CONCEPT

This is an early beta / proof-of-concept. It is a read-only visual toy for a SOC wall, not a hardened product. Expect rough edges, breaking changes, and approximate geolocation. Do not rely on it as a detection or alerting control. Run it on a trusted internal network only. No warranty.

Norse-style live attack map for Wazuh. A standalone, dependency-free Python service that polls your local wazuh-indexer for alerts carrying a routable source IP, geolocates each one, and streams animated arcs (attacker origin → your sensor "home") to a full-screen canvas page.

Attack map screenshot

Above: the map in demo mode. Arcs are coloured by attack type and converge on the configured sensor home (example: Amsterdam, NL).

Unlike a generic threat-feed map (which plots known-bad infrastructure from the public internet), this map sources its arcs from your own Wazuh alerts — so every arc is a real event recorded against your monitored fleet, not background noise.


Why

  • Real attribution, not feeds. Each arc = one Wazuh alert with a public data.srcip. It shows who is actually hitting your sensors.
  • Zero dependencies. Pure Python 3 stdlib. No pip, no Node, no database. One file runs the server; the page is a single self-contained HTML/canvas.
  • Read-only. Connects to wazuh-indexer with a read-only account. It never writes to Wazuh and needs no manager API access.
  • Upgrade-proof. It is not an OpenSearch Dashboards plugin, so a Wazuh upgrade can't break it. It just talks to the indexer's search API.

How it works

 wazuh-indexer (wazuh-alerts-*)
        │   read-only "monitor" account, basic-auth over TLS
        ▼
   mapserver.py ──► attackmap.py poller ──► geo.py (mmdb / synthetic)
        │                 │
        │                 ▼
        │            arc events (deduped by timestamp watermark)
        ▼                 │
   HTTP :8100 ◄───────────┘
   /attackmap  (canvas page)   /api/attackmap/stream (SSE)

Every MAP_POLL_INTERVAL seconds the poller runs a search for alerts newer than the last one it saw (range > watermark) that have a data.srcip. Each hit is turned into an event: the source IP is geolocated, the rule.groups are mapped to an attack-type bucket (bruteforce / webattack / malware / ransomware / ddos / recon / intrusion / other), and the event is pushed to all connected browsers over Server-Sent Events. The browser draws the arc.

Files

File Purpose
mapserver.py Standalone HTTP server + LocalIndexerClient (basic-auth → https://127.0.0.1:9200). Production entry point.
attackmap.py Emitter, alert poller, geo bucketing, SSE, the /attackmap canvas page, and the MAP_DEMO synthetic feeder.
geo.py Vendored MaxMind mmdb reader + synthetic fallback.
world.geojson Vendored low-res world outline for the canvas.
demo.py Run the map with synthetic data, no Wazuh needed (python3 demo.py).
tools/render_shot.py Render the static hero PNG (no browser) — used for docs/screenshot.png.
wazuh-attackmap.service systemd unit.
wazuh-attackmap.env.example Env template.
docs/INSTALL.md Step-by-step install manual.
docs/ADMIN.md Operator / admin manual (config, ops, troubleshooting).
docs/USER.md Viewer manual (reading the map, controls).

GeoLite2-City.mmdb is not shipped (gitignored, ~60 MB). geo.py auto-detects a copy beside the script, then $GEOIP_MMDB, else falls back to synthetic scatter. See docs/INSTALL.md.

Quick look (no Wazuh)

python3 demo.py          # then open http://127.0.0.1:8788/attackmap

Regenerate the screenshot (or the 1280×640 social-preview card):

python3 tools/render_shot.py docs/screenshot.png
SHOT_W=1280 SHOT_H=640 python3 tools/render_shot.py docs/social-preview.png

Install on the Wazuh server

See docs/INSTALL.md for the full walk-through. Short version:

sudo mkdir -p /opt/wazuh-attackmap && sudo chown "$USER":"$USER" /opt/wazuh-attackmap
cp mapserver.py attackmap.py geo.py world.geojson /opt/wazuh-attackmap/
# drop a GeoLite2-City.mmdb into /opt/wazuh-attackmap/ (optional but recommended)

sudo cp wazuh-attackmap.env.example /etc/wazuh-attackmap.env
sudo chmod 600 /etc/wazuh-attackmap.env      # set INDEXER_PASS to a read-only account
sudo cp wazuh-attackmap.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now wazuh-attackmap
curl -s http://127.0.0.1:8100/healthz

Routes

Route Description
/ 302 → /attackmap
/attackmap full-screen live map page
/api/attackmap/world vendored world geojson
/api/attackmap/recent?limit=N recent events + home coords (JSON)
/api/attackmap/stats running counters (JSON)
/api/attackmap/stream Server-Sent Events feed
/healthz 200 liveness probe

Configuration

All config is environment variables — see docs/ADMIN.md for the full table. Most-used:

Var Default Notes
INDEXER_URL https://127.0.0.1:9200 local wazuh-indexer
INDEXER_USER / INDEXER_PASS monitor / — read-only account
INDEXER_CA root-ca.pem for TLS verify; unset = verify off
MAP_PORT 8100 listen port
MAP_TLS_CERT / MAP_TLS_KEY set both → serve HTTPS (reuse the Wazuh cert; see docs/INSTALL.md)
MAP_HOME_LAT / MAP_HOME_LON 52.37 / 4.90 sensor "home" the arcs land on
MAP_INCLUDE_PRIVATE 0 1 = also plot internal/LAN source IPs (see below)
MAP_DEMO 1 = synthetic feeder instead of Wazuh

Internal / LAN attackers

By default the map plots only public source IPs — so an attacker already inside your LAN (lateral movement, internal brute-force, internal recon) using 192.168.x / 10.x / 172.16–31.x addresses produces no arc. RFC1918 addresses can't be geolocated, so they're dropped.

That is a visualization gap, not a detection gap — Wazuh still records those alerts; the map just doesn't draw them by default.

Set MAP_INCLUDE_PRIVATE=1 to surface them. Internal sources are drawn in a distinct magenta hue, clustered in a ring around your sensor home (each internal host gets a stable spot, hashed from its IP). So an internal host hammering your sensors shows up as magenta arcs near the centre — visually separate from the multicolour public arcs sweeping in from abroad. Events also carry "scope":"internal" in the API for downstream filtering.

MAP_INCLUDE_PRIVATE=1 python3 mapserver.py     # or set it in /etc/wazuh-attackmap.env

Heads-up: on a busy internal network this can be noisy (every internal auth failure becomes an arc). Tune it with MAP_INTERNAL_MIN_LEVEL — a rule-level floor that drops low-severity internal arcs (public arcs are never filtered). E.g. MAP_INTERNAL_MIN_LEVEL=8 keeps brute-force/notable internal events and drops routine single auth failures. See docs/ADMIN.md.

Known limitations (it's a POC)

  • Sources only routable public data.srcip by default (see Internal / LAN attackers above for MAP_INCLUDE_PRIVATE). A home/LAN-only Wazuh shows an empty map unless that flag is set — that is correct, not a bug. To see public arcs you need internet-facing sensors (or a honeypot) producing alerts with public source IPs.
  • Geolocation is approximate (city-level mmdb or, worse, synthetic scatter).
  • No auth on the map page itself. Defaults are lab-tuned (no auth, MAP_BIND=0.0.0.0, HTTP) for a SOC wall on a trusted internal network. For production, put it behind an authenticating reverse proxy, bind to localhost, turn on HTTPS — see SECURITY.md. Never expose it to the internet without proxy + auth.
  • In-memory only: the event ring buffer resets on restart. No history, no DB.
  • Test alerts injected via the analysisd queue carry an old timestamp and fall behind the poll watermark, so they never emit. Use real traffic or MAP_DEMO.

Security

Read-only, no dependencies, hardening headers, optional HTTPS, read-only indexer account, bind control. See SECURITY.md for the threat model and the accepted POC trade-offs (no page auth, 'unsafe-inline' CSP, etc.).

License / status

Proof-of-concept, provided as-is. No warranty, no support guarantee. Licensed under the MIT License.

About

Norse-style live attack map for Wazuh — a dependency-free Python service that streams animated arcs of real Wazuh alert source IPs onto a full-screen canvas. Read-only, no plugin, upgrade-proof.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages