{ "comment": "blockfast probelist - TEMPLATE. This file documents the format and how to generate a real one from server logs. blockfast loads it with --probelist=; it then REPLACES the built-in probe list entirely, so a generated file must include generic scanner rules too, not only setup-specific ones. All `comment` fields (and this whole header) are ignored by the loader; entries containing only a `comment` are section separators. Everything else must follow the format below.", "format": [ "each probe entry: { path, match?, status?, allowance?, comment? }", "path (required): the string to match against the request URI, matched case-insensitively", "match (optional, default 'contains'): 'contains' matches anywhere in the full URI including the query string; 'prefix' and 'exact' match against the query-stripped path only", "status (optional): restricts the probe to these response statuses, as a comma-separated string with trailing-x ranges, e.g. '401' or '403,5xx' or '40x'. Without it the probe matches ANY status - important, since SPAs with catch-all routes answer 200 to probe paths", "allowance (optional, 1-255): overrides blockfast's global --allowance for this probe, i.e. how many offences an IP may accumulate (within the jailtime window, shared across all probes) before being banned. Use a HIGHER value for endpoints legitimate users occasionally trip, a LOW value is implicit for pure probe paths" ], "howto_generate": [ "You are generating a ban-rule file for blockfast, a fail2ban-like daemon: it tails web server logs (Common Log Format or Caddy JSON), counts offences per client IP, and firewalls IPs that exceed their allowance. A wrong rule bans legitimate users, so precision beats recall.", "1. Group the log lines by client IP. Classify each IP: legitimate clients use real app routes and mostly get 2xx/3xx; scanners enumerate paths (/.env, /phpinfo.php, /wp-*, credential/config filenames) that the app never serves.", "2. Paths requested ONLY by scanners and NEVER by legitimate clients become unconditional probes (no status filter). Do not rely on the response status for these: a catch-all route may answer 200.", "3. Endpoints that legitimate users DO touch, but that indicate abuse in volume (login endpoints, session checks answering 401, method-probing answering 405), become narrow rules: match 'prefix' or 'exact' + a status filter + a generous allowance. Never add such an endpoint without a status filter.", "4. NEVER add: paths served by the app (assets, API routes), /favicon.ico, /robots.txt, /.well-known (ACME!), or anything a browser or well-behaved bot requests on its own. When unsure, leave it out.", "5. Prefer 'exact' for short or generic names ('/env', '/i.php') - as substrings they would match legitimate paths ('/i.php' is inside '/api.php'). Use 'contains' for names that are unambiguous anywhere in a URI ('/.env', 'phpinfo').", "6. Keep the generic scanner families in the output (see sections below), then append the setup-specific rules derived from the logs, each with a comment explaining the evidence.", "7. Sanity-check the finished list by replaying the logs against it: no IP classified as a legitimate client may match any rule." ], "probes": [ { "comment": "--- setup-specific rules, derived from the logs (EXAMPLES - replace with real findings) ---" }, { "path": "/api/auth", "match": "prefix", "status": "401", "allowance": 10, "comment": "example: the SPA answers 401 on session checks when logged out - normal a few times, credential stuffing in bulk. prefix + status + generous allowance" }, { "path": "/", "match": "exact", "status": "405", "comment": "example: scanners POST to the root fishing for handlers; browsers never trigger 405 there" }, { "path": "/backup.tar.gz", "match": "exact", "comment": "example: artifact hunted by scanners in these logs, never linked by the app" }, { "comment": "--- generic: secrets & dotfiles ---" }, { "path": "/.env", "comment": "also matches /.env.bak, /backend/.env, ..." }, { "path": "%2eenv", "comment": "url-encoded .env" }, { "path": "/.git", "comment": "/.git/config, /.git/HEAD, /.gitconfig" }, { "path": "/.svn" }, { "path": "/.hg/" }, { "path": "/.aws" }, { "path": "/.ssh" }, { "path": "/.docker" }, { "comment": "--- generic: cloud credentials & config dumps ---" }, { "path": "credentials.json" }, { "path": "-key.json", "comment": "/gcp-key.json, /firebase-key.json, ..." }, { "path": "/keyfile.json" }, { "path": "/sa.json" }, { "path": "service-account.json" }, { "path": "firebase-adminsdk.json" }, { "path": "gcp-sa.json" }, { "path": "/docker-compose.yml" }, { "path": "/appsettings.json" }, { "path": "application.yml" }, { "path": "parameters.yml" }, { "path": "/web.config" }, { "path": "/settings.py" }, { "path": "/wp-config.php", "comment": "never served, only probed - even on real wordpress sites" }, { "comment": "--- generic: php probes ---" }, { "path": "phpinfo" }, { "path": "phpmyadmin" }, { "path": "adminer.php" }, { "path": ".php.bak" }, { "path": ".php.old" }, { "path": ".php.save" }, { "path": ".php~" }, { "path": "eval-stdin.php", "comment": "phpunit RCE" }, { "path": "/vendor/phpunit" }, { "path": "/test.php", "match": "exact" }, { "path": "/info.php", "match": "exact" }, { "path": "/pinfo.php", "match": "exact" }, { "path": "/pi.php", "match": "exact" }, { "path": "/i.php", "match": "exact" }, { "path": "/p.php", "match": "exact" }, { "path": "/php.php", "match": "exact" }, { "path": "/debug.php", "match": "exact" }, { "path": "/database.php", "match": "exact" }, { "path": "/config.php", "match": "exact" }, { "path": "/shell.php", "match": "exact" }, { "path": "/upload.php", "match": "exact" }, { "comment": "--- generic: fingerprinting & framework debug endpoints ---" }, { "path": "wlwmanifest.xml", "comment": "wordpress fingerprinting, legit wp traffic never touches it" }, { "path": "/actuator/env", "comment": "spring boot - keep narrow, bare /actuator would catch legit health checks" }, { "path": "/_profiler", "comment": "symfony" }, { "path": "/_ignition", "comment": "laravel RCE" }, { "path": "/_environment", "comment": "cakephp" }, { "path": "laravel.log" }, { "path": "/env", "match": "exact" }, { "comment": "--- generic: server status, traversal, IoT/router botnets ---" }, { "path": "server-status" }, { "path": "server-info" }, { "path": "/../" }, { "path": "%2e%2e" }, { "path": "/etc/passwd" }, { "path": "/cgi-bin/" }, { "path": "/boaform" }, { "path": "/hnap1" }, { "path": "/gponform" }, { "comment": "--- aggressive extras: correct for many setups, but NOT in blockfast's built-in default because they match legit traffic on some stacks. Include them only when the logs show the stack does not use them ---" }, { "path": "/wp-login.php", "comment": "legit logins on real wordpress sites - only include if not hosting wordpress" }, { "path": "/xmlrpc.php", "comment": "used by jetpack & wordpress mobile apps" }, { "path": "rest_route=", "comment": "wordpress REST access with permalinks disabled" }, { "path": "/autodiscover/", "comment": "outlook clients innocently probe this on any domain" }, { "path": "/manager/html", "comment": "tomcat console - legit for tomcat admins" }, { "path": "/solr/admin" }, { "path": "/geoserver/web" }, { "path": "/telescope/requests", "comment": "laravel telescope - legit for its admins" } ] }