become a c00l operator. seriously though
Find a file
2026-06-06 10:21:35 +00:00
chadnet.py first commit 2026-06-06 10:21:35 +00:00
README.md first commit 2026-06-06 10:21:35 +00:00

chadnet

Production SSH abuse detection and automatic network dropping by v3ga.

chadnet is a single-file Linux defensive control for internet-facing SSH servers. It monitors authentication logs, scores hostile behavior with weighted rules, persists enforcement state across restarts, escalates repeat offenders, and applies timed network drops through nftables or ipset.

The default backend is nftables.

Capability

  • Live SSH monitoring through systemd-journald.
  • Historical log ingestion for backfilling enforcement state.
  • Weighted detection for OpenSSH and PAM abuse patterns.
  • Rolling score windows instead of fragile line counters.
  • Repeat-offender escalation with a maximum cap.
  • IPv4 and IPv6 enforcement.
  • Native nftables timed sets.
  • ipset timed sets for iptables-based hosts.
  • CIDR allowlists from flags or files.
  • Persistent state with active ban metadata.
  • Append-only JSON Lines audit events.
  • Active blocklist export.
  • Manual blocklist import.
  • One-address unban.
  • Human report output.
  • JSON status output for automation.
  • Firewall rule installation for the selected backend.

Requirements

Core:

  • Linux
  • Python 3.10 or newer
  • systemd-journald
  • Root privileges for enforcement

For nftables:

  • nft

For ipset:

  • ipset
  • iptables
  • ip6tables

No third-party Python packages are required.

Install

Recommended layout:

sudo install -d -m 0755 /opt/chadnet
sudo install -d -m 0750 /var/lib/chadnet
sudo install -m 0755 chadnet.py /opt/chadnet/chadnet.py

Install enforcement rules for the default nftables backend:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  install-firewall

The default nftables objects are:

table: inet chadnet
chain: input
IPv4 set: chadnet-drop
IPv6 set: chadnet-drop6

For iptables-based systems:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend ipset \
  --state-dir /var/lib/chadnet \
  install-firewall

The default ipset objects are:

chadnet-drop
chadnet-drop6

Firewall persistence is distribution-specific. Persist the generated nftables, iptables, or ip6tables rules using the standard firewall tooling for your operating system.

Run

Start live monitoring:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  monitor

Backfill from an existing log file and enforce any resulting bans:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  scan /var/log/auth.log

Show status:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  status

Show machine-readable status:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  status --json

Show a human operational report:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  report

Remove one address:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  unban 203.0.113.77

Export active backend drops:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  export /var/lib/chadnet/export.txt

Import an address list:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  --ban-seconds 604800 \
  import /root/blocklist.txt --reason external-feed

Clean expired state and rewrite the active file:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  cleanup

Commands

Command Purpose
monitor Watch SSH journal units and enforce bans in real time
scan Process an existing log file and enforce matching bans
status Print active drops, state paths, and counters
report Print top rules, top usernames, repeat sources, and totals
cleanup Expire stale state and rewrite the active file
unban Remove one address from enforcement and state
import Import and enforce an address blocklist
export Export active backend drops
install-firewall Create backend sets and install drop rules

Configuration

Global options must be placed before the command.

Option Environment Default Purpose
--backend CHADNET_BACKEND nftables Enforcement backend: nftables or ipset
--set-name CHADNET_SET chadnet-drop IPv4 set name; IPv6 appends 6
--nft-table CHADNET_NFT_TABLE chadnet nftables table name
--nft-chain CHADNET_NFT_CHAIN input nftables chain name
--threshold CHADNET_THRESHOLD 6 Score required before a ban
--window CHADNET_WINDOW 300 Rolling score window in seconds
--suppress CHADNET_SUPPRESS 2 Minimum seconds between counted hits per source
--ban-seconds CHADNET_BAN_SECONDS 259200 Initial ban length
--max-ban-seconds CHADNET_MAX_BAN_SECONDS 2592000 Maximum escalated ban length
--escalation CHADNET_ESCALATION 2.0 Repeat-offender multiplier
--sync-seconds CHADNET_SYNC_SECONDS 300 Background state and active-file sync interval
--state-dir CHADNET_STATE_DIR . Base directory for state files
--state-file CHADNET_STATE_FILE chadnet-state.json Persistent detector state
--active-file CHADNET_ACTIVE_FILE chadnet.txt Current active blocklist
--audit-file CHADNET_AUDIT_FILE chadnet-events.jsonl Audit event stream
--unit ssh, sshd Journal unit to monitor; may be repeated
--allowlist empty CIDR or address to ignore; may be repeated
--allowlist-file empty File containing CIDRs or addresses
--quiet off Reduce console output

Production profile:

sudo /usr/bin/python3 /opt/chadnet/chadnet.py \
  --backend nftables \
  --state-dir /var/lib/chadnet \
  --threshold 6 \
  --window 300 \
  --ban-seconds 259200 \
  --max-ban-seconds 2592000 \
  --escalation 2 \
  --allowlist 192.0.2.10 \
  --allowlist 2001:db8::/32 \
  monitor

Detection Model

Each matched SSH log event adds a score to the source address inside the rolling window. The address is banned when its score reaches --threshold.

Rule Score
Failed password 2
Invalid user 3
PAM authentication failure 2
Closed authenticating pre-auth connection 2
Closed invalid pre-auth connection 3
Disconnected invalid user 3
Missing identification string 1
Bad protocol identification 2
Unable to negotiate 2
Maximum authentication attempts exceeded 4
Disconnecting invalid user 3
Reverse mapping failure 1

This model catches credential spraying, brute force, invalid-user enumeration, noisy scanning, and repeated protocol abuse without treating every log line equally.

Repeat sources escalate. A source banned again after returning receives a longer ban according to:

next_ban = min(max_ban_seconds, ban_seconds * escalation ^ previous_bans)

Files

chadnet-state.json

Persistent detector state. It stores rolling hits, active ban metadata, repeat counts, top rule counters, top username counters, and total line/match/ban counters.

chadnet.txt

Current active blocklist, one IP address per line. It is regenerated from backend and state data by monitor, status, scan, and cleanup.

chadnet-events.jsonl

Append-only audit stream. Each line is a compact JSON object with time, action, ip, and details.

Example audit event:

{"action":"ban","details":{"repeat":1,"rule":"maximum-auth","score":9,"seconds":259200,"until":1780970421},"ip":"203.0.113.77","time":1780711221}

systemd

Example unit:

[Unit]
Description=chadnet SSH abuse dropper
Documentation=https://git.nadeko.net/legs/chadnet
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/chadnet/chadnet.py --backend nftables --state-dir /var/lib/chadnet monitor
Restart=always
RestartSec=5
WorkingDirectory=/var/lib/chadnet
NoNewPrivileges=yes
PrivateTmp=yes
ProtectHome=yes
ProtectSystem=full
ReadWritePaths=/var/lib/chadnet

[Install]
WantedBy=multi-user.target

Install:

sudo install -d -m 0750 /var/lib/chadnet
sudo install -m 0644 chadnet.service /etc/systemd/system/chadnet.service
sudo systemctl daemon-reload
sudo systemctl enable --now chadnet.service

View logs:

journalctl -u chadnet -f

Operations

Allowlists should cover only trusted administrative paths such as management VPNs, office egress addresses, monitoring nodes, and emergency access ranges. Keep them narrow.

Use status --json for automation and external monitoring. Use report during incident review to understand which rules, usernames, and sources are driving enforcement.

Use export before backend migrations. Use import after migrations or when applying a trusted blocklist. Use cleanup after manual firewall changes to refresh state-derived files.

If an administrator is blocked, run unban ADDRESS from a trusted console or out-of-band access path, then add that access path to the allowlist.

Upgrade Path

  1. Stop the service.
  2. Preserve /var/lib/chadnet.
  3. Replace /opt/chadnet/chadnet.py.
  4. Run install-firewall if backend names or firewall backend changed.
  5. Start the service.
  6. Review status and report.

Security Model

chadnet is a reactive network control for SSH abuse. It reduces repeated hostile authentication activity by blocking sources after observed behavior. It does not replace key-only SSH authentication, disabled password login, multi-factor access, patching, least privilege, restricted management networks, or a default-deny firewall policy.

Repository

Publication target:

git.nadeko.net/legs/chadnet

The local source provided for this rework contained attribution to DNSRail dropnet and a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license notice. If this code remains a derivative of that project, keep the required attribution and license obligations when publishing.