From ad0dbbe6a9b550b71c102bf91f8799e78441b483 Mon Sep 17 00:00:00 2001 From: Jasper Frumau Date: Wed, 28 Oct 2026 13:17:54 +0700 Subject: [PATCH] Remove the security guides, now posts on wpvilla.in The wp-cli/security README, SECURITY-GUIDE and SCANNER-SUMMARY and the trellis/security README, FAIL2BAN and MANUAL-IP-BLOCKING are covered by four posts and the existing fail2ban post. Point the Trellis README, root README and CLAUDE.md at the posts, and make the scanner wrapper's closing hint name the post. The scanners and AbuseIPDB scripts stay. --- CHANGELOG.md | 1 + CLAUDE.md | 2 +- README.md | 4 +- trellis/README.md | 16 +- trellis/security/FAIL2BAN.md | 142 ----- trellis/security/MANUAL-IP-BLOCKING.md | 568 ------------------ trellis/security/README.md | 610 ------------------- wp-cli/security/README.md | 778 ------------------------- wp-cli/security/SCANNER-SUMMARY.md | 274 --------- wp-cli/security/SECURITY-GUIDE.md | 586 ------------------- wp-cli/security/scanner-wrapper.php | 2 +- 11 files changed, 9 insertions(+), 2974 deletions(-) delete mode 100644 trellis/security/FAIL2BAN.md delete mode 100644 trellis/security/MANUAL-IP-BLOCKING.md delete mode 100644 trellis/security/README.md delete mode 100644 wp-cli/security/README.md delete mode 100644 wp-cli/security/SCANNER-SUMMARY.md delete mode 100644 wp-cli/security/SECURITY-GUIDE.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 86abb62..51aff89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +- **The security guides**, now four posts on wpvilla.in: [the two scanners and checksum verification](https://wpvilla.in/wordpress-malware-scanners-targeted-general-checksum-verification/), [reading scan results](https://wpvilla.in/reading-malware-scan-results-false-positives-wordpress-hardening/), [a permanent Nginx deny list](https://wpvilla.in/nginx-deny-list-trellis-cidr-whitelist-self-lockout/) and [a Trellis security routine](https://wpvilla.in/trellis-wordpress-security-routine-abuseipdb-fail2ban/), plus the existing [fail2ban post](https://wpvilla.in/fail2ban-wordpress-wp-login-trellis-nginx-deny-list/). The `README`, `SECURITY-GUIDE` and `SCANNER-SUMMARY` guides in `wp-cli/security/` and the `README`, `FAIL2BAN` and `MANUAL-IP-BLOCKING` guides in `trellis/security/` are gone; the scanners and the AbuseIPDB scripts stay. The scanner wrapper's closing hint now names the post. - **The provisioning and updater guides**, now four posts on wpvilla.in: [a new Mac for Trellis](https://wpvilla.in/new-mac-trellis-development-setup-lima-composer-node/), [cloning an existing project](https://wpvilla.in/clone-trellis-bedrock-project-lima-vm-first-provision/), [updating Trellis](https://wpvilla.in/update-trellis-without-losing-customisations-rsync-excludes/) and [provision tags and the PHP upgrade](https://wpvilla.in/trellis-provision-tags-php-version-upgrade-checklist/). The cron guide is covered by the existing [WP-Cron interval post](https://wpvilla.in/trellis-wp-cron-interval-scheduled-posts-late-cron-interval/). `trellis/provision/` is gone, as are the updater README and `manual-update.md`; the `trellis-updater` script stays. - **The content-creation guides**, now three posts on wpvilla.in: [creating pages from block markup](https://wpvilla.in/create-wordpress-pages-block-markup-wp-cli-trellis-vm-production/), [block patterns as PHP files](https://wpvilla.in/wordpress-block-pattern-php-files-headers-escaping-images/) and [pattern layout rules](https://wpvilla.in/wordpress-block-pattern-layout-alignfull-grid-spacing-rules/). `PAGE-CREATION.md`, `PATTERN-REQUIREMENTS.md` and the folder README are gone; the scripts and the example stay. - **The migration guides**, now four posts on wpvilla.in: [migrating a site to Trellis and Bedrock](https://wpvilla.in/migrate-wordpress-to-trellis-bedrock-step-by-step/), [testing before the DNS cutover](https://wpvilla.in/trellis-migration-test-etc-hosts-letsencrypt-dns-cutover/), [migrating several sites onto one server](https://wpvilla.in/migrate-multiple-wordpress-sites-one-trellis-server/) and [wp search-replace for domain and path changes](https://wpvilla.in/wp-search-replace-wordpress-url-domain-https-bedrock-paths/). `wp-cli/migration/` is gone, and the README, `CLAUDE.md` and `wp-cli/README.md` no longer point at it. diff --git a/CLAUDE.md b/CLAUDE.md index 906989f..e68ac57 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -129,7 +129,7 @@ trellis ssh production -- "cd /srv/www/example.com/current && \ - cPanel/Plesk terminal: Use version-specific PHP binaries (e.g., `/opt/plesk/php/8.2/bin/php`) - Browser access: Requires IP whitelist, delete files immediately after use (security risk) - Local scan: Download WordPress via FTP and scan locally (safest for restricted hosting) -- See `wp-cli/security/README.md` for complete installation and troubleshooting guide +- See [the scanner post](https://wpvilla.in/wordpress-malware-scanners-targeted-general-checksum-verification/) for installation and troubleshooting ### Image Optimization diff --git a/README.md b/README.md index 2ab49f8..555e778 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ wp-ops security-scan example.com production # wp-login/xmlrpc abuse, high-vol ``` Reads the site's Nginx logs on the server — no analytics plugin, no third-party -service. Pair it with the [fail2ban setup](trellis/security/README.md) to block what +service. Pair it with the [fail2ban setup](https://wpvilla.in/fail2ban-wordpress-wp-login-trellis-nginx-deny-list/) to block what the scan finds. ### 3. Publish and verify content without opening wp-admin @@ -132,7 +132,7 @@ sites are run with: | **Trellis** | Safe Trellis updates (`trellis-updater`) | [guide](https://wpvilla.in/update-trellis-without-losing-customisations-rsync-excludes/) | | **Backups** | Timestamped database and uploads backups from any environment | [→](trellis/backup/README.md) | | **Monitoring** | AI-crawler traffic, error surfacing, traffic by country, cron-scheduled reports | [→](trellis/monitoring/README.md) | -| **Security** | Malware scanners with checksum verification, fail2ban, manual IP blocking, AbuseIPDB lookup | [scanners](wp-cli/security/README.md) · [server](trellis/security/README.md) | +| **Security** | Malware scanners with checksum verification, fail2ban, manual IP blocking, AbuseIPDB lookup | [routine](https://wpvilla.in/trellis-wordpress-security-routine-abuseipdb-fail2ban/) · [scanners](https://wpvilla.in/wordpress-malware-scanners-targeted-general-checksum-verification/) | | **Content & SEO** | Page creation, block pattern validation, redirect/schema/orphan-page audits | [SEO](wp-cli/seo/README.md) | | **Nginx** | WebP/AVIF serving, browser caching and redirect templates for Trellis `nginx-includes/` | [images](docs/nginx/image-optimization/README.md) · [caching](docs/nginx/browser-caching/README.md) · [redirects](docs/nginx/redirects/README.md) | | **Bedrock** | Testing unreleased packages via Composer `path` repos, standard `wp-cli.yml` | [packages](docs/bedrock/local-package-development/README.md) · [wp-cli.yml](docs/bedrock/wp-cli-config/README.md) | diff --git a/trellis/README.md b/trellis/README.md index ccb2aa1..9e4fe81 100644 --- a/trellis/README.md +++ b/trellis/README.md @@ -209,14 +209,6 @@ WordPress security protection through fail2ban automatic IP blocking and manual - fail2ban log monitoring - Integration with malware scanners -### Documentation - -| File | Purpose | -|------|---------| -| `README.md` | Security overview and quick reference | -| `FAIL2BAN.md` | fail2ban setup, configuration, and monitoring | -| `MANUAL-IP-BLOCKING.md` | Permanent Nginx IP blocks for extreme cases | - ### Quick Start **Enable fail2ban WordPress protection:** @@ -258,10 +250,10 @@ ssh web@yoursite.com "grep 'POST.*wp-login' /srv/www/yoursite.com/logs/access.lo - Zero maintenance required **See also:** -- [security/README.md](security/README.md) - Complete security guide -- [security/FAIL2BAN.md](security/FAIL2BAN.md) - Automatic IP blocking setup -- [security/MANUAL-IP-BLOCKING.md](security/MANUAL-IP-BLOCKING.md) - Manual IP blocks -- [wp-cli/security](../wp-cli/security/) - Malware detection scanners +- [fail2ban on Trellis](https://wpvilla.in/fail2ban-wordpress-wp-login-trellis-nginx-deny-list/) +- [A permanent Nginx deny list](https://wpvilla.in/nginx-deny-list-trellis-cidr-whitelist-self-lockout/) +- [A weekly and monthly security routine](https://wpvilla.in/trellis-wordpress-security-routine-abuseipdb-fail2ban/) +- [Malware scanners](https://wpvilla.in/wordpress-malware-scanners-targeted-general-checksum-verification/) and [reading their results](https://wpvilla.in/reading-malware-scan-results-false-positives-wordpress-hardening/) --- diff --git a/trellis/security/FAIL2BAN.md b/trellis/security/FAIL2BAN.md deleted file mode 100644 index 923bfe4..0000000 --- a/trellis/security/FAIL2BAN.md +++ /dev/null @@ -1,142 +0,0 @@ -# fail2ban WordPress Protection - -Automatic IP blocking for WordPress brute force attacks using fail2ban. - -Part of the [wp-ops](https://github.com/imagewize/wp-ops) toolkit for WordPress operations and server management. - -**Why it matters, how it works, and the filter fix that stopped false bans:** -[fail2ban for wp-login.php on Trellis: Turn the Jail On, Anchor the Filter, Keep an Nginx Deny List for the Rest](https://wpvilla.in/fail2ban-wordpress-wp-login-trellis-nginx-deny-list/). -This page is the command reference. - ---- - -## SSH users - -- `admin_user` below is a placeholder for your Trellis admin user (from - `group_vars/{env}/users.yml`). It has sudo, which asks for a password, so use - `ssh -t` for one-off commands. -- The `web` user has no sudo but can read site logs. - -Trellis installs fail2ban and protects SSH by default. The WordPress jails ship -disabled. - ---- - -## Enable - -`trellis/group_vars/all/security.yml`: - -```yaml -fail2ban_bantime: 600 # seconds banned -fail2ban_maxretry: 6 # failures before a ban (global, incl. sshd) -fail2ban_findtime: 600 # window the failures are counted in - -fail2ban_services_custom: - - name: wordpress_wp_login - filter: wordpress-wp-login - enabled: "true" - port: http,https - logpath: "{{ www_root }}/**/logs/access.log" - # maxretry: 6 # optional per-jail override of the global value -``` - -Apply only the fail2ban role: - -```bash -cd trellis -trellis provision --tags fail2ban production -``` - -Re-run the same command after any change to `security.yml`. - -**`wordpress_xmlrpc`**: only enable it if `curl -I https://yoursite.com/xmlrpc.php` -does *not* return 403 or 444. If Nginx already refuses XML-RPC, the jail has -nothing to catch. - -### Never ban your own IP - -```yaml -ip_whitelist: - - 127.0.0.0/8 - - "{{ ipify_public_ip | default('') }}" # the machine you provision from - - 203.0.113.0/24 # office network (example) -``` - -`ip_whitelist` feeds fail2ban's `ignoreip`, so listed addresses are exempt from -the sshd and WordPress jails. - ---- - -## Check - -```bash -ssh -t admin_user@yoursite.com "sudo fail2ban-client status" -ssh -t admin_user@yoursite.com "sudo fail2ban-client status wordpress_wp_login" -ssh -t admin_user@yoursite.com "sudo tail -50 /var/log/fail2ban.log" -ssh -t admin_user@yoursite.com "sudo grep 'Ban ' /var/log/fail2ban.log | tail -20" -``` - -The jail status lists the monitored log files, failure and ban counts, and the -currently banned IPs. - -## Ban and unban - -```bash -sudo fail2ban-client set wordpress_wp_login banip 1.2.3.4 -sudo fail2ban-client set wordpress_wp_login unbanip 1.2.3.4 -sudo fail2ban-client unban --all -``` - -### Locked out - -1. Unban yourself from another connection (phone hotspot, VPN) with `unbanip` above. -2. Or, from the provider's server console: `sudo systemctl stop fail2ban`, log in, - add your IP to `ip_whitelist`, re-provision, `sudo systemctl start fail2ban`. - ---- - -## Troubleshooting - -Not banning attackers: - -```bash -# Jail enabled and running? -ssh -t admin_user@yoursite.com "sudo fail2ban-client status | grep wordpress" -ssh -t admin_user@yoursite.com "sudo systemctl status fail2ban" - -# Is the log being written? -ssh web@yoursite.com "tail /srv/www/yoursite.com/logs/access.log" - -# Does the filter match? Look for a non-zero "matched" count. -ssh -t admin_user@yoursite.com "sudo fail2ban-regex \ - /srv/www/yoursite.com/logs/access.log \ - /etc/fail2ban/filter.d/wordpress-wp-login.conf" - -# Config errors -ssh -t admin_user@yoursite.com "sudo fail2ban-client -d" -ssh -t admin_user@yoursite.com "sudo grep ERROR /var/log/fail2ban.log | tail -20" -``` - -Banning legitimate users: make sure the filter is anchored to the request field, -not the whole log line, or a `wp-login.php` referrer counts as a failure. The -anchored filter is in the post linked at the top: - -```ini -failregex = ^ .* "POST [^"]*wp-login\.php[^"]*" 200 -``` - -A failed login returns `200` (form shown again); a successful one returns `302`. - ---- - -## Beyond fail2ban - -- Persistent or high-volume attackers: permanent Nginx blocks in - [MANUAL-IP-BLOCKING.md](./MANUAL-IP-BLOCKING.md) -- Malware detection: [wp-cli/security](../../wp-cli/security/) scanners -- Attack patterns in the logs: `wp-ops security-scan example.com production` - -## Resources - -- [Trellis security documentation](https://roots.io/trellis/docs/security/) -- [fail2ban documentation](https://github.com/fail2ban/fail2ban/wiki) diff --git a/trellis/security/MANUAL-IP-BLOCKING.md b/trellis/security/MANUAL-IP-BLOCKING.md deleted file mode 100644 index 986797f..0000000 --- a/trellis/security/MANUAL-IP-BLOCKING.md +++ /dev/null @@ -1,568 +0,0 @@ -# Manual IP Blocking via Nginx - -Permanent IP blocking at the Nginx level for extreme cases. - -Part of the [wp-ops](https://github.com/imagewize/wp-ops) toolkit for WordPress operations and server management. - -**Note**: Commands in this guide use `admin_user` as a placeholder for your Trellis admin username. - -- **Replace `admin_user` with your configured username** (e.g., `admin`, `deploy`, `warden`, your name, etc.) -- Example: If your admin user is `admin`, use `ssh admin@yoursite.com` - ---- - -## Overview - -**Manual IP blocking** via Nginx `deny` directives provides **permanent** IP blocks at the web server level. This is an advanced technique that should only be used when [fail2ban](./FAIL2BAN.md) automatic blocking is insufficient. - -### When to Use Manual IP Blocks - -| Scenario | Use fail2ban | Use Manual Blocks | -|----------|--------------|-------------------| -| Brute force wp-login attempts | ✅ Recommended | ❌ Not needed | -| XML-RPC abuse | ✅ Recommended | ❌ Not needed | -| Extreme high-volume attack (1,000+ req/min) | ⚠️ May not be fast enough | ✅ Yes | -| Distributed attack (many IPs) | ✅ Catches them individually | ❌ Too many to manage | -| Known persistent attacker | ✅ Auto-rebans | ✅ For permanent block | -| Attack on non-WordPress URLs | ❌ Can't detect | ✅ Yes, if needed | - -### Pros and Cons - -**Manual IP Blocks:** -- ✅ Permanent blocks (persist across reboots) -- ✅ Very fast (processed early in Nginx request handling) -- ✅ Works for any URL pattern -- ✅ Version controlled (in Trellis repository) -- ❌ Requires manual maintenance -- ❌ Risk of blocking yourself -- ❌ Attackers rotate IPs anyway - -**fail2ban (Recommended):** -- ✅ Automatic detection and blocking -- ✅ Zero maintenance -- ✅ Temporary bans avoid false positives -- ✅ Already installed in Trellis -- ⚠️ Requires initial configuration - -**Recommendation**: Use fail2ban as primary defense. Only use manual blocks for extreme cases. - ---- - -## Implementation - -### 1. Create deny-ips.conf.j2 - -Create `trellis/nginx-includes/all/deny-ips.conf.j2`: - -```nginx -# Nginx IP Deny List for WordPress Security -# Managed via: trellis/nginx-includes/all/deny-ips.conf.j2 -# Last updated: {{ ansible_date_time.date }} -# -# Apply changes: cd trellis && trellis provision --tags nginx-includes production -# -# IMPORTANT: Always verify IPs before adding to avoid blocking legitimate traffic -# Check IP ownership: whois -# Check geolocation: https://whatismyipaddress.com/ip/ - -# Example format: -# deny 192.168.1.100; # Description/reason for block - -# Add your IP blocks below: -deny 62.60.130.228; # 1,420 wp-login attempts (Nov 2025) -deny 141.98.11.120; # 860 wp-login attempts (Nov 2025) -deny 91.224.92.114; # 742 wp-login attempts (Nov 2025) -``` - -**Template features:** -- Ansible variable `{{ ansible_date_time.date }}` shows when it was last deployed -- Comments document why each IP is blocked -- Lives in version control (Git) - -### 2. Apply Configuration - -```bash -cd trellis -trellis provision --tags nginx-includes production -``` - -This will: -1. Template `deny-ips.conf.j2` to `/etc/nginx/includes.d/all/deny-ips.conf` -2. Test Nginx configuration for syntax errors -3. Reload Nginx to apply new blocks - -Use the `nginx-includes` tag, not `nginx`. The includes are templated by the -`wordpress-setup` role, which `--tags nginx` skips, so that run finishes -without deploying the file. Check the deployed copy's timestamp under -`/etc/nginx/includes.d/` if a change seems not to apply. - -**Verify:** -```bash -# Check Nginx reloaded successfully -ssh admin_user@yoursite.com "sudo systemctl status nginx" - -# Is the rule in the live config, and does the config parse? -ssh admin_user@yoursite.com "sudo nginx -T 2>/dev/null | grep -n 'deny 62.60.130.228'" -ssh admin_user@yoursite.com "sudo nginx -t" -``` - -`deny` matches the connection's real address, so a spoofed `X-Forwarded-For` -header proves nothing. For an end-to-end test, add your own IP on staging, -confirm `curl -I https://staging.yoursite.com` returns `403 Forbidden`, then -remove it. - -### 3. Test Before Deploying - -**Always test Nginx config after editing:** - -```bash -# After editing deny-ips.conf.j2 locally -cd trellis -ansible-playbook server.yml -e env=production --tags nginx-includes --check -``` - -The `--check` flag runs in dry-run mode (no changes applied). - ---- - -## Adding/Removing IPs - -### Add IP Block - -1. **Research the IP first:** -```bash -# Check who owns it -whois 1.2.3.4 - -# Check geolocation -curl "https://ipapi.co/1.2.3.4/json/" -``` - -2. **Add to deny-ips.conf.j2:** -```nginx -deny 1.2.3.4; # Description: reason for block, date observed -``` - -3. **Deploy:** -```bash -cd trellis -trellis provision --tags nginx-includes production -``` - -### Remove IP Block - -1. **Delete or comment out the line:** -```nginx -# deny 1.2.3.4; # Removed: reason for unblocking -``` - -2. **Deploy:** -```bash -cd trellis -trellis provision --tags nginx-includes production -``` - -### Bulk Add IPs - -For multiple IPs from log analysis: - -```bash -# Example: Extract top 20 wp-login attackers -ssh web@yoursite.com "grep 'POST.*wp-login' /srv/www/yoursite.com/logs/access.log | awk '{print \$1}' | sort | uniq -c | sort -rn | head -20" - -# Output: -# 1420 62.60.130.228 -# 860 141.98.11.120 -# 742 91.224.92.114 -# ... -``` - -Add to `deny-ips.conf.j2`: -```nginx -deny 62.60.130.228; # 1,420 wp-login attempts -deny 141.98.11.120; # 860 wp-login attempts -deny 91.224.92.114; # 742 wp-login attempts -``` - ---- - -## File Organization - -### Directory Structure - -Trellis supports multiple nginx-includes locations: - -``` -trellis/nginx-includes/ -├── all/ # Applied to ALL sites (development, staging, production) -│ └── deny-ips.conf.j2 # ← IP blocks for all environments -├── development/ # Development-only -├── staging/ # Staging-only -└── production/ # Production-only - └── deny-ips.conf.j2 # ← Alternatively, production-only blocks -``` - -**Recommendation**: Use `all/` for IP blocks unless you have different attackers per environment. - -### Why .j2 Extension? - -`.j2` = Jinja2 template. Allows Ansible variables like: - -```nginx -# Last deployed: {{ ansible_date_time.iso8601 }} -# Server: {{ ansible_hostname }} -``` - -These get replaced with actual values during provisioning. - ---- - -## Advanced Usage - -### Block IP Ranges (CIDR) - -```nginx -# Block entire subnet -deny 192.168.1.0/24; # Blocks 192.168.1.0 - 192.168.1.255 - -# Block larger range -deny 10.0.0.0/8; # Blocks 10.0.0.0 - 10.255.255.255 -``` - -**Caution**: Only block ranges if you're certain. Blocking cloud provider ranges (AWS, DigitalOcean, etc.) will block many legitimate users. - -### Allow Specific IPs (Whitelist) - -```nginx -# Block everyone except whitelisted IPs -# (Use with extreme caution!) - -# Allow specific IPs -allow 1.2.3.4; # Your IP -allow 5.6.7.8; # Office IP - -# Block all others -deny all; -``` - -**Warning**: This blocks ALL traffic except whitelisted IPs. Only use for maintenance mode or staging sites. - -### Conditional Blocking - -Block IPs only for specific locations: - -Create `trellis/nginx-includes/all/deny-ips-wp-admin.conf.j2`: - -```nginx -# Block attackers from accessing /wp-admin only -location ~ ^/wp-admin { - deny 62.60.130.228; - deny 141.98.11.120; - - # Allow all others to proceed - try_files $uri $uri/ /index.php?$args; -} -``` - -**Use case**: Block known attackers from admin area while still allowing them to view public site. - ---- - -## Monitoring Blocked IPs - -### View Active deny Rules - -```bash -ssh admin_user@yoursite.com "cat /etc/nginx/includes.d/all/deny-ips.conf" -``` - -### Check Nginx Error Log - -When an IP is blocked, Nginx logs it: - -```bash -ssh admin_user@yoursite.com "sudo grep 'access forbidden' /var/log/nginx/error.log | tail -20" -``` - -**Example log entry:** -``` -2026/01/01 12:34:56 [error] 12345#12345: *67890 access forbidden by rule, -client: 62.60.130.228, server: example.com, request: "POST /wp-login.php HTTP/1.1" -``` - -### Count Blocked Requests - -```bash -# How many requests were blocked from each IP -ssh admin_user@yoursite.com "sudo grep 'access forbidden' /var/log/nginx/error.log | awk '{print \$13}' | sort | uniq -c | sort -rn" -``` - -**Example output:** -``` - 847 62.60.130.228, - 592 141.98.11.120, - 301 91.224.92.114, -``` - -Shows blocking is working! - ---- - -## Preventing Self-Lockout - -### Check Your Current IP - -**Before adding any blocks:** - -```bash -# From your local machine -curl https://ipinfo.io/ip -``` - -**Never block your own IP!** - -### Whitelist Your IP - -Add to top of `deny-ips.conf.j2`: - -```nginx -# WHITELIST - IPs that should NEVER be blocked -# (These must come BEFORE any deny rules) - -allow 1.2.213.123; # My Jakarta IP -allow 203.0.113.0/24; # Office network - -# BLOCKLIST - Attackers -deny 62.60.130.228; -deny 141.98.11.120; -``` - -**Order matters**: Nginx processes `allow` and `deny` in order. Put `allow` first. - -### Recovery If Locked Out - -**Option 1: Remove blocks via VPS console** - -Most VPS providers (DigitalOcean, Linode, etc.) offer web-based console access: - -```bash -# Login via VPS console -sudo nano /etc/nginx/includes.d/all/deny-ips.conf - -# Comment out your IP or delete the file -sudo systemctl reload nginx -``` - -**Option 2: Use fail2ban instead** - -fail2ban supports IP whitelisting via `ip_whitelist` in `security.yml` (see [FAIL2BAN.md](./FAIL2BAN.md)). - ---- - -## Troubleshooting - -### Nginx Won't Reload After Adding deny Rule - -**Cause**: Syntax error in deny-ips.conf.j2 - -**Solution**: -```bash -# Test Nginx config before deploying -ssh admin_user@yoursite.com "sudo nginx -t" - -# Look for error message -# Example: "unknown directive" means syntax error -``` - -**Common mistakes**: -```nginx -# ❌ Wrong - missing semicolon -deny 1.2.3.4 - -# ✅ Correct -deny 1.2.3.4; - -# ❌ Wrong - invalid IP format -deny 1.2.3.256; - -# ✅ Correct -deny 1.2.3.4; -``` - -### Legitimate User Blocked - -**Symptom**: User reports "403 Forbidden" on your site - -**Check if their IP is blocked:** -```bash -# Get their IP (ask them to visit https://ipinfo.io/ip) -# Example: 5.6.7.8 - -# Check if it's in deny list -ssh admin_user@yoursite.com "grep '5.6.7.8' /etc/nginx/includes.d/all/deny-ips.conf" -``` - -**If found, remove it:** - -1. Edit `trellis/nginx-includes/all/deny-ips.conf.j2` -2. Remove or comment out the line -3. Deploy: `cd trellis && trellis provision --tags nginx-includes production` - -### Block Not Working - -**Symptom**: Attacker still accessing site despite being in deny list - -**Check deployed config:** -```bash -ssh admin_user@yoursite.com "cat /etc/nginx/includes.d/all/deny-ips.conf | grep '62.60.130.228'" -``` - -**If not there:** -- Did you run `trellis provision --tags nginx-includes`? -- Check for typos in IP address -- Ensure file is in correct location (`nginx-includes/all/`) - -**If there but still not blocked:** -- Attacker might be using a different IP (check logs) -- Attacker might be using a proxy/VPN (check X-Forwarded-For header) - ---- - -## Reference: Known Attacker IPs (example.com) - -From production logs analysis (November-December 2025): - -```nginx -# Top 20 wp-login brute force attackers (by attempt count) -# These IPs would be automatically blocked by fail2ban after 6 attempts - -deny 62.60.130.228; # 1,420 attempts - Kamatera, Netherlands -deny 141.98.11.120; # 860 attempts - Giganet, UK -deny 91.224.92.114; # 742 attempts - G-Core Labs, Luxembourg -deny 80.94.92.167; # 628 attempts - Giganet, UK -deny 80.94.92.177; # 502 attempts - Giganet, UK -deny 88.119.161.116; # 467 attempts - M247, UK -deny 185.180.143.13; # 391 attempts - Hostwinds, US -deny 195.178.110.30; # 337 attempts - Giganet, UK -deny 80.94.92.168; # 305 attempts - Giganet, UK -deny 186.96.145.241; # 275 attempts - Telecentro, Argentina -deny 45.142.122.117; # 212 attempts - Aeza Group, Netherlands -deny 185.246.221.79; # 198 attempts - Stark Industries, Russia -deny 89.23.96.175; # 187 attempts - Giganet, UK -deny 103.214.8.216; # 164 attempts - Tata Communications, India -deny 103.21.161.207; # 153 attempts - UAB Rakrejus, Singapore -deny 205.210.31.52; # 142 attempts - QuadraNet, US -deny 45.142.122.86; # 128 attempts - Aeza Group, Netherlands -deny 167.99.215.209; # 118 attempts - DigitalOcean, US -deny 185.233.185.225; # 112 attempts - Giganet, UK -deny 185.246.221.80; # 97 attempts - Stark Industries, Russia -``` - -**Note**: This list is for reference only. With fail2ban enabled, these IPs would be automatically blocked after their 6th failed attempt. Manual blocking is not necessary. - ---- - -## Best Practices - -### 1. Prefer fail2ban Over Manual Blocks - -- fail2ban = automatic, zero maintenance -- Manual blocks = require constant updates as attackers rotate IPs - -**Use manual blocks only for**: -- Extreme high-volume attacks (1,000+ req/min) -- Known persistent attackers that keep returning -- Attacks on non-WordPress URLs that fail2ban can't detect - -### 2. Always Document Why IPs Are Blocked - -```nginx -# ✅ Good - includes context -deny 62.60.130.228; # 1,420 wp-login attempts, Nov 2025, Kamatera NL - -# ❌ Bad - no context -deny 62.60.130.228; -``` - -Future you (or team members) will thank you. - -### 3. Review Block List Regularly - -Quarterly, review your deny list and remove IPs that are no longer threats. - -**Why**: Attackers rotate IPs. Blocking old IPs has little value. - -### 4. Test Changes Before Deploying - -```bash -# Dry-run test -cd trellis -ansible-playbook server.yml -e env=production --tags nginx-includes --check - -# If no errors, deploy for real -trellis provision --tags nginx-includes production -``` - -### 5. Keep a Backup - -Before making large changes: - -```bash -# Backup current config -ssh admin_user@yoursite.com "sudo cp /etc/nginx/includes.d/all/deny-ips.conf /tmp/deny-ips.conf.backup" - -# If something breaks -ssh admin_user@yoursite.com "sudo cp /tmp/deny-ips.conf.backup /etc/nginx/includes.d/all/deny-ips.conf && sudo systemctl reload nginx" -``` - ---- - -## Quick Reference - -### Create deny-ips.conf.j2 - -```bash -# Create file -touch trellis/nginx-includes/all/deny-ips.conf.j2 - -# Edit and add: -# deny 1.2.3.4; # Description -``` - -### Deploy Changes - -```bash -cd trellis -trellis provision --tags nginx-includes production -``` - -### View Current Blocks - -```bash -ssh admin_user@yoursite.com "cat /etc/nginx/includes.d/all/deny-ips.conf" -``` - -### Test Nginx Config - -```bash -ssh admin_user@yoursite.com "sudo nginx -t" -``` - -### Monitor Blocked Requests - -```bash -ssh admin_user@yoursite.com "sudo grep 'access forbidden' /var/log/nginx/error.log | tail -20" -``` - ---- - -## Resources - -- [fail2ban WordPress Protection](./FAIL2BAN.md) - Automatic IP blocking (recommended) -- [Trellis nginx_includes Documentation](https://roots.io/trellis/docs/wordpress-sites/#nginx-includes) -- [Nginx ngx_http_access_module](http://nginx.org/en/docs/http/ngx_http_access_module.html) -- [wp-ops Security Scanners](../../wp-cli/security/) - Malware detection - ---- - -**Part of the [wp-ops](https://github.com/imagewize/wp-ops) toolkit for WordPress operations** diff --git a/trellis/security/README.md b/trellis/security/README.md deleted file mode 100644 index 8086b95..0000000 --- a/trellis/security/README.md +++ /dev/null @@ -1,610 +0,0 @@ -# Trellis Security - -WordPress security tools and configurations for Trellis-managed servers. - -Part of the [wp-ops](https://github.com/imagewize/wp-ops) toolkit for WordPress operations and server management. - ---- - -## Important: SSH User for Admin Commands - -**Note**: Commands in this guide use `admin_user` as a placeholder for your Trellis admin username. - -- Trellis creates admin users via `group_vars/{env}/users.yml` -- Admin users have sudo access (require password for sudo commands) -- **Replace `admin_user` with your configured username** (e.g., `warden`, `deploy`, your name, etc.) -- Example: If your admin user is `warden`, use `ssh warden@yoursite.com` - -For read-only operations (viewing logs), use the `web` user which doesn't require sudo: -- `ssh web@yoursite.com` - No sudo access, can read site files and logs - ---- - -## Overview - -This directory contains documentation and configurations for securing WordPress sites on Trellis infrastructure. - -### Security Layers - -``` -┌─────────────────────────────────────────┐ -│ Layer 1: Firewall (UFW) │ ← fail2ban blocks IPs here -├─────────────────────────────────────────┤ -│ Layer 2: Nginx (deny rules) │ ← Manual IP blocks -├─────────────────────────────────────────┤ -│ Layer 3: WordPress (plugins) │ ← Wordfence, etc. -├─────────────────────────────────────────┤ -│ Layer 4: Application (code) │ ← Security scanners -└─────────────────────────────────────────┘ -``` - -### Tools Covered - -| Tool | Purpose | Maintenance | Docs | -|------|---------|-------------|------| -| **fail2ban** | Automatic IP blocking for brute force attacks | Zero (once configured) | [FAIL2BAN.md](./FAIL2BAN.md) | -| **Manual IP blocks** | Permanent Nginx-level IP blocking | Manual updates required | [MANUAL-IP-BLOCKING.md](./MANUAL-IP-BLOCKING.md) | -| **IP reputation lookup** | Verify an IP is actually malicious before blocking it | Run as needed | [check-ips.sh](#ip-reputation-checking) | -| **Deny-list audit** | Bulk-check every IP already in `deny-ips.conf.j2` and flag stale ones | Run periodically | [check-deny-ips.sh](#deny-list-auditing) | -| **Security scanners** | Malware detection for WordPress | Run weekly/monthly | [wp-cli/security](../../wp-cli/security/) | - ---- - -## Quick Start - -### 1. Enable fail2ban WordPress Protection - -**Recommended first step** for all WordPress sites. - -```bash -# Edit trellis/group_vars/all/security.yml -# Set wordpress_wp_login enabled: "true" - -# Apply -cd trellis -trellis provision --tags fail2ban production -``` - -**See**: [FAIL2BAN.md](./FAIL2BAN.md) for complete setup guide. - -### 2. Monitor Security Activity - -```bash -# Check fail2ban status (admin user requires password for sudo) -ssh admin_user@yoursite.com -sudo fail2ban-client status wordpress_wp_login - -# View recent bans -sudo tail -50 /var/log/fail2ban.log - -# Analyze attack patterns (web user, no sudo needed) -ssh web@yoursite.com "grep 'POST.*wp-login' /srv/www/yoursite.com/logs/access.log | awk '{print \$1}' | sort | uniq -c | sort -rn | head -20" -``` - -### 3. Run Security Scans - -```bash -# Weekly malware scan -wp eval-file wp-cli/security/scanner-targeted.php - -# Monthly deep scan -wp eval-file wp-cli/security/scanner-general.php -``` - -**See**: [wp-cli/security](../../wp-cli/security/) for scanner documentation. - ---- - -## Documentation - -### Automatic Protection - -- **[FAIL2BAN.md](./FAIL2BAN.md)** - Automatic IP blocking for brute force attacks - - Setup and configuration - - Monitoring and management - - Troubleshooting - - Real-world statistics - -### Manual Protection (Advanced) - -- **[MANUAL-IP-BLOCKING.md](./MANUAL-IP-BLOCKING.md)** - Permanent Nginx IP blocks - - When to use manual blocks vs fail2ban - - Creating deny-ips.conf.j2 - - Managing IP lists - - Known attacker reference list - -### Application-Level Security - -- **[wp-cli/security](../../wp-cli/security/)** - WordPress malware scanners - - Targeted scanner (site-specific threats) - - General scanner (broad malware detection) - - Integration with fail2ban workflow - ---- - -## Recommended Security Strategy - -### For Most WordPress Sites - -**Primary Defense**: fail2ban (automatic) -```yaml -# trellis/group_vars/all/security.yml -fail2ban_services_custom: - - name: wordpress_wp_login - enabled: "true" # ← Enable this -``` - -**Regular Monitoring**: -- Weekly security scans with [scanner-targeted.php](../../wp-cli/security/) -- Monthly fail2ban log reviews -- Quarterly review of attack patterns - -**Manual IP blocks**: Only for extreme cases (see criteria below) - -### When to Add Manual IP Blocks - -Use manual Nginx IP blocks **only** when: - -1. ✅ **Extreme high-volume attack** - Single IP sending 1,000+ requests/minute -2. ✅ **Distributed attack** - Many IPs coordinating attack (doesn't trigger fail2ban thresholds) -3. ✅ **Known persistent attacker** - IP keeps returning after fail2ban bans expire - -**Don't use manual blocks for**: -- ❌ Regular brute force attempts (fail2ban handles this) -- ❌ One-time attackers (fail2ban temporary ban is sufficient) -- ❌ IPs that might be shared (VPNs, corporate networks, etc.) - -Before adding a manual block, run [`check-ips.sh`](#ip-reputation-checking) on the IP — -it's easy to mistake a search-engine crawler or a monitoring service's IP for an -attacker, especially in a distributed attack pattern. - ---- - -## IP Reputation Checking - -`check-ips.sh` looks up one or more IPs against [AbuseIPDB](https://www.abuseipdb.com/)'s -threat intelligence database, so you can confirm an IP is actually malicious before -adding it to a manual block list or investigating further. - -### Setup - -```bash -# Get a free API key (1,000 checks/day): https://www.abuseipdb.com/register -echo 'ABUSEIPDB_KEY=your_key_here' > trellis/security/.env -``` - -### Usage - -```bash -# Look up one or more IPs -./trellis/security/check-ips.sh 203.0.113.50 198.51.100.25 - -# Via wp-ops -wp-ops check-ips 203.0.113.50 -``` - -### Requirements - -```bash -brew install jq curl # macOS -``` - -### Example Output - -``` -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - 203.0.113.50 -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -{ - "score": 92, - "reports": 187, - "lastSeen": "2026-07-30T14:22:11+00:00", - "country": "CN", - "isp": "Example ISP", - "usageType": "Data Center/Web Hosting/Transit", - "domain": "example.net", - "tor": false -} -``` - -A high `score` (AbuseIPDB's 0-100 confidence rating) with recent, numerous -`reports` supports a manual block. A `score` of 0 with no reports is a strong -signal the IP is not actually malicious (a shared NAT gateway, a legitimate -crawler, etc.) — don't block it. - ---- - -## Deny-list Auditing - -`check-deny-ips.sh` bulk-checks every individual IP already in a Trellis -project's `nginx-includes/all/deny-ips.conf.j2` against AbuseIPDB, so blocks -that are no longer warranted (score dropped to 0, no recent reports) are easy -to spot. CIDR subnet entries are skipped — AbuseIPDB checks single IPs, not -ranges — and reported as a count instead. Uses the same `trellis/security/.env` -as `check-ips.sh`. - -```bash -# Compact summary (score, reports, lastSeen per IP) -TRELLIS_DIR=/path/to/trellis ./trellis/security/check-deny-ips.sh - -# Full JSON output per IP, via check-ips.sh -TRELLIS_DIR=/path/to/trellis ./trellis/security/check-deny-ips.sh --full - -# Via wp-ops -TRELLIS_DIR=/path/to/trellis wp-ops check-deny-ips -``` - ---- - -## Security Workflow - -### Weekly Routine - -```bash -# 1. Run security scanner (1-2 seconds) -wp eval-file wp-cli/security/scanner-targeted.php - -# 2. Check fail2ban activity (optional) -ssh admin_user@yoursite.com -sudo fail2ban-client status wordpress_wp_login -``` - -### Monthly Deep Dive - -```bash -# 1. Deep malware scan (2-3 seconds) -wp eval-file wp-cli/security/scanner-general.php - -# 2. Review fail2ban logs -ssh admin_user@yoursite.com -sudo tail -100 /var/log/fail2ban.log - -# 3. Analyze attack patterns (web user, no sudo) -ssh web@yoursite.com "grep 'POST.*wp-login' /srv/www/yoursite.com/logs/access.log | awk '{print \$1}' | sort | uniq -c | sort -rn | head -20" - -# 4. Update WordPress and plugins -wp core update -wp plugin update --all -``` - -### After Suspected Compromise - -```bash -# 1. Full security scan -wp eval-file wp-cli/security/scanner-wrapper.php - -# 2. Check for suspicious users -wp user list --role=administrator - -# 3. Verify core files -wp core verify-checksums - -# 4. Check plugin integrity -wp plugin verify-checksums --all - -# 5. Review recent file changes -ssh web@yoursite.com "find /srv/www/yoursite.com/current/web/wp-content -mtime -7 -type f" - -# 6. Analyze access logs for unusual patterns -ssh web@yoursite.com "tail -1000 /srv/www/yoursite.com/logs/access.log | grep -v 'GET /' | grep -v '.css' | grep -v '.js'" -``` - ---- - -## Real-World Examples - -The before-and-after numbers from a production server (1,420 login attempts from -a single IP before fail2ban, the first ban within seven hours after) are in -[fail2ban on Trellis](https://wpvilla.in/fail2ban-wordpress-wp-login-trellis-nginx-deny-list/). - -### Typical Attack Pattern - -**What fail2ban detects:** -``` -2026-01-01 02:26:08 [wordpress_wp_login] Found 167.99.215.209 -2026-01-01 02:26:15 [wordpress_wp_login] Found 167.99.215.209 -2026-01-01 02:26:22 [wordpress_wp_login] Found 167.99.215.209 -2026-01-01 02:26:29 [wordpress_wp_login] Found 167.99.215.209 -2026-01-01 02:26:36 [wordpress_wp_login] Found 167.99.215.209 -2026-01-01 02:26:43 [wordpress_wp_login] Found 167.99.215.209 ← 6th attempt -2026-01-01 02:26:43 [wordpress_wp_login] Ban 167.99.215.209 ← BANNED -``` - -**Without fail2ban** (before 2026-01-01): -- Same IP would continue attempting hundreds or thousands of times -- Server resources wasted processing login attempts -- Increased risk of successful brute force - ---- - -## Configuration Files - -### Trellis Security Configuration - -All security settings are in `trellis/group_vars/all/security.yml`: - -```yaml -# fail2ban settings -fail2ban_bantime: 600 # 10 minutes -fail2ban_maxretry: 6 # 6 failed attempts -fail2ban_findtime: 600 # 10 minute window - -# IP whitelist (never ban these IPs) -ip_whitelist: - - 127.0.0.0/8 - - "{{ ipify_public_ip | default('') }}" - - 1.2.213.123 # Your static IP (example) - -# fail2ban jails -fail2ban_services_custom: - - name: wordpress_wp_login - filter: wordpress-wp-login - enabled: "true" # ← Set to "true" to enable - port: http,https - logpath: "{{ www_root }}/**/logs/access.log" - - # Optional: XML-RPC protection (usually not needed if blocked at Nginx level) - # - name: wordpress_xmlrpc - # filter: wordpress-xmlrpc - # enabled: "false" - # port: http,https - # logpath: "{{ www_root }}/**/logs/access.log" -``` - -### Manual IP Blocks (Optional) - -For extreme cases only, create `trellis/nginx-includes/all/deny-ips.conf.j2`: - -```nginx -# Manual IP deny list -# Only use for extreme high-volume attacks - -deny 1.2.3.4; # Description: reason for block -``` - -**See**: [MANUAL-IP-BLOCKING.md](./MANUAL-IP-BLOCKING.md) for details. - ---- - -## Monitoring Commands - -### Quick Status Check - -```bash -# SSH in first (admin user requires password for sudo) -ssh admin_user@yoursite.com - -# Is fail2ban running? -sudo systemctl status fail2ban - -# WordPress jail status -sudo fail2ban-client status wordpress_wp_login - -# Currently banned IPs -sudo fail2ban-client get wordpress_wp_login banip -``` - -### Analyze Attack Activity - -```bash -# Top 20 IPs attempting wp-login -ssh web@yoursite.com "grep 'POST.*wp-login' /srv/www/yoursite.com/logs/access.log | awk '{print \$1}' | sort | uniq -c | sort -rn | head -20" - -# Recent fail2ban bans -ssh admin_user@yoursite.com "sudo grep 'Ban ' /var/log/fail2ban.log | tail -20" - -# How many requests were blocked by Nginx deny rules -ssh admin_user@yoursite.com "sudo grep 'access forbidden' /var/log/nginx/error.log | wc -l" -``` - -### Check for Malware - -```bash -# Quick scan (1-2 seconds) -wp eval-file wp-cli/security/scanner-targeted.php - -# Deep scan (2-3 seconds) -wp eval-file wp-cli/security/scanner-general.php -``` - ---- - -## Troubleshooting - -### I Locked Myself Out - -**Symptom**: Can't access your WordPress site (403 Forbidden) - -**If banned by fail2ban**: -```bash -# Unban your IP -ssh admin_user@yoursite.com "sudo fail2ban-client set wordpress_wp_login unbanip YOUR.IP.HERE" - -# Prevent future bans - add to whitelist in security.yml -ip_whitelist: - - YOUR.IP.HERE -``` - -**If blocked by manual deny rule**: -```bash -# Via VPS console (not SSH) -sudo nano /etc/nginx/includes.d/all/deny-ips.conf -# Comment out or delete your IP -sudo systemctl reload nginx -``` - -### Attacker Not Being Blocked - -**Possible causes**: - -1. **fail2ban jail not enabled** - ```bash - # Check status - ssh admin_user@yoursite.com "sudo fail2ban-client status" - # Should list "wordpress_wp_login" - if not, it's disabled - ``` - -2. **Attacker hasn't reached threshold yet** - - Default: 6 failed attempts required for ban - - Check: `sudo grep 'Found.*wp-login' /var/log/fail2ban.log | grep 'ATTACKER.IP.HERE'` - -3. **Attacker rotating IPs** - - Each new IP needs 6 attempts before ban - - Solution: Manual block for the IP range (if persistent) - -4. **Log file not being monitored** - ```bash - # Check which logs fail2ban is watching - ssh admin_user@yoursite.com "sudo fail2ban-client get wordpress_wp_login logpath" - ``` - -### fail2ban Not Starting - -```bash -# Check for errors -ssh admin_user@yoursite.com "sudo systemctl status fail2ban" -ssh admin_user@yoursite.com "sudo journalctl -u fail2ban -n 50" - -# Test configuration -ssh admin_user@yoursite.com "sudo fail2ban-client -d" - -# Restart -ssh admin_user@yoursite.com "sudo systemctl restart fail2ban" -``` - ---- - -## Best Practices - -### 1. Layer Your Security - -Don't rely on a single tool. Use multiple layers: - -- ✅ fail2ban (firewall-level blocking) -- ✅ Strong passwords (WordPress user accounts) -- ✅ 2FA (two-factor authentication) -- ✅ Security plugin (Wordfence, iThemes Security) -- ✅ Regular updates (WordPress core, plugins, themes) -- ✅ Security scans (weekly/monthly) -- ✅ Backups (automated daily/weekly) - -### 2. Monitor Regularly - -```bash -# Weekly: Check fail2ban activity -ssh admin_user@yoursite.com "sudo fail2ban-client status wordpress_wp_login" - -# Monthly: Analyze attack patterns -ssh web@yoursite.com "grep 'POST.*wp-login' /srv/www/yoursite.com/logs/access.log | awk '{print \$1}' | sort | uniq -c | sort -rn | head -20" -``` - -### 3. Keep Security Configs in Version Control - -All security configurations live in Git (Trellis repository): -- ✅ `group_vars/all/security.yml` (fail2ban config) -- ✅ `nginx-includes/all/deny-ips.conf.j2` (manual IP blocks) -- ✅ Changes tracked in Git history -- ✅ Easy rollback if needed - -### 4. Test Before Deploying - -```bash -# Always test Nginx config changes -cd trellis -ansible-playbook server.yml -e env=production --tags nginx-includes --check - -# Then deploy -trellis provision --tags nginx-includes production -``` - -### 5. Document Everything - -When adding manual IP blocks, always include: -- Why is this IP blocked? -- When was it observed? -- What was the attack pattern? - -```nginx -# ✅ Good -deny 62.60.130.228; # 1,420 wp-login attempts, Nov 2025, Kamatera NL - -# ❌ Bad -deny 62.60.130.228; -``` - ---- - -## Related Documentation - -### Security Tools -- **[wp-cli/security](../../wp-cli/security/)** - WordPress malware scanners -- **[troubleshooting/](../../docs/troubleshooting/)** - Server troubleshooting guides - -### Trellis Operations -- **[trellis/backup](../backup/)** - Database and file backups -- **[trellis/monitoring](../monitoring/)** - Server monitoring tools -- **[Provisioning guides](https://wpvilla.in/trellis-provision-tags-php-version-upgrade-checklist/)** - Server provisioning on the blog - -### External Resources -- [Trellis Security Documentation](https://roots.io/trellis/docs/security/) -- [fail2ban Official Documentation](https://www.fail2ban.org/wiki/index.php/Main_Page) -- [WordPress Security Best Practices](https://wordpress.org/support/article/hardening-wordpress/) -- [OWASP Top 10](https://owasp.org/www-project-top-ten/) - ---- - -## Quick Reference - -### Enable fail2ban - -```bash -# 1. Edit security.yml -# Set wordpress_wp_login enabled: "true" - -# 2. Apply -cd trellis && trellis provision --tags fail2ban production -``` - -### Add Manual IP Block - -```bash -# 1. Create/edit file -nano trellis/nginx-includes/all/deny-ips.conf.j2 - -# 2. Add IP -# deny 1.2.3.4; # Description - -# 3. Deploy -cd trellis && trellis provision --tags nginx-includes production -``` - -### Check IP Reputation - -```bash -./trellis/security/check-ips.sh 203.0.113.50 198.51.100.25 -``` - -### Run Security Scan - -```bash -# Quick scan -wp eval-file wp-cli/security/scanner-targeted.php - -# Deep scan -wp eval-file wp-cli/security/scanner-general.php -``` - -### Check Security Status - -```bash -# fail2ban status (admin user requires password for sudo) -ssh admin_user@yoursite.com -sudo fail2ban-client status wordpress_wp_login - -# Recent attacks (web user, no sudo) -ssh web@yoursite.com "grep 'POST.*wp-login' /srv/www/yoursite.com/logs/access.log | tail -50" -``` - ---- - -**Part of the [wp-ops](https://github.com/imagewize/wp-ops) toolkit for WordPress operations** diff --git a/wp-cli/security/README.md b/wp-cli/security/README.md deleted file mode 100644 index df4722d..0000000 --- a/wp-cli/security/README.md +++ /dev/null @@ -1,778 +0,0 @@ -# WordPress Security Scanners - -Comprehensive dual-scanner security suite for WordPress malware detection and security auditing. - -✨ **NEW in v2.0:** Automatic checksum verification eliminates false positives from unmodified WordPress core and plugin files! - -Part of the [wp-ops](https://github.com/imagewize/wp-ops) toolkit for WordPress operations and server management. - ---- - -## ⚙️ Requirements & Installation - -### Check if WP-CLI is Available - -```bash -# Test if WP-CLI is installed -wp --version - -# If you see version output, WP-CLI is available ✓ -# If "command not found", see installation below -``` - -### Installing WP-CLI - -#### VPS/Dedicated Server (with SSH root access) - -```bash -# Download WP-CLI -curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar - -# Make executable -chmod +x wp-cli.phar - -# Move to system PATH -sudo mv wp-cli.phar /usr/local/bin/wp - -# Verify installation -wp --version -``` - -#### Shared Hosting (SSH access, no root) - -```bash -# Download to home directory -cd ~ -curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -chmod +x wp-cli.phar - -# Create alias in ~/.bashrc or ~/.bash_profile -echo "alias wp='php ~/wp-cli.phar'" >> ~/.bashrc -source ~/.bashrc - -# Verify -wp --version -``` - -#### Trellis/Bedrock Projects - -WP-CLI is pre-installed ✓ - -### Execution Methods by Hosting Type - -Choose the method that matches your hosting environment: - -| Hosting Type | WP-CLI | Direct PHP | Browser | Recommended Method | -|--------------|--------|------------|---------|-------------------| -| **VPS/Dedicated** | ✅ Yes | ✅ Yes | ⚠️ Not recommended | `wp eval-file` | -| **Shared (SSH)** | Install it | ✅ Yes | ⚠️ Not recommended | Install WP-CLI or use `php` | -| **Shared (no SSH)** | ❌ No | ❌ No | ⚠️ Last resort | See [Browser Access](#browser-access-last-resort) | -| **Trellis/Bedrock** | ✅ Pre-installed | ✅ Yes | ⚠️ Not recommended | `wp eval-file` | -| **cPanel/Plesk** | Maybe | ✅ Via Terminal | ⚠️ Not recommended | See [cPanel/Plesk](#cpanelplesk-terminal) | - -### cPanel/Plesk Terminal - -Some shared hosts provide browser-based terminals: - -**cPanel:** -1. Login → Advanced → Terminal -2. Navigate to WordPress: `cd public_html` (or your WordPress directory) -3. Run scanner: -```bash -php scanner-targeted.php -# Or with full path: -php /home/username/public_html/wp-cli/security/scanner-targeted.php -``` - -**Plesk:** -1. Login → File Manager → Open in Terminal (or Tools & Settings → Scheduled Tasks → Terminal) -2. Navigate to WordPress directory -3. Run scanner: -```bash -php scanner-targeted.php -# Or use version-specific PHP binary: -/opt/plesk/php/8.2/bin/php scanner-targeted.php -``` - -### Browser Access (Last Resort) - -⚠️ **SECURITY WARNING**: Only use this method if you have NO other option! - -**Setup:** - -1. **Upload scanner files** to WordPress root via FTP or File Manager -2. **Add IP whitelist** - Edit the scanner file (e.g., `scanner-targeted.php`) around line 15: - -```php -// Find and update this section: -$allowed_ips = [ - '127.0.0.1', - 'YOUR.IP.ADDRESS.HERE', // ← Add your public IP address -]; -``` - -3. **Find your IP address**: Visit https://whatismyipaddress.com/ -4. **Access via browser**: -``` -https://yoursite.com/scanner-targeted.php -``` - -5. **CRITICAL - Delete immediately after use**: -```bash -# Via FTP/File Manager, delete: -scanner-targeted.php -scanner-general.php -scanner-wrapper.php -``` - -**Why browser access is dangerous:** -- ❌ Exposes security scanner to the public internet -- ❌ Could leak file structure and configuration details -- ❌ Scanner file itself could be exploited by attackers -- ❌ Often flagged/blocked by security plugins and WAFs -- ❌ May be logged by server access logs - -**Safer alternative for restricted hosting:** - -Download your WordPress files via FTP and scan them locally on your computer: - -```bash -# On your local machine (after downloading WordPress via FTP) -php scanner-targeted.php /path/to/downloaded-wordpress -``` - ---- - -## 🎯 Quick Start - -```bash -# From WordPress root directory - -# Run both scanners (recommended first scan) -wp eval-file wp-cli/security/scanner-wrapper.php - -# Or run individually -wp eval-file wp-cli/security/scanner-targeted.php # Quick check (1-2s, faster with checksum!) -wp eval-file wp-cli/security/scanner-general.php # Deep scan (2-3s, faster with checksum!) -``` - -✨ **Automatic Checksum Verification:** If WP-CLI is available, checksums are verified automatically before scanning. No extra steps needed! - ---- - -## 🔐 Checksum Verification (NEW in v2.0) - -### What is Checksum Verification? - -Checksum verification compares installed WordPress files against official release hashes from wordpress.org. This allows the scanners to: - -1. ✅ **Skip unmodified core files** - No more false positives from legitimate WordPress code -2. ✅ **Skip verified plugins** - Plugins from wordpress.org that pass checksum are skipped -3. ✅ **Prioritize modified files** - Files that fail checksum are flagged as HIGH PRIORITY - -### What Changed? - -**Before v2.0:** Every scan would flag legitimate WordPress core files like `wp-includes/kses.php`, `wp-includes/SimplePie/src/Misc.php`, etc. for patterns like `file_get_contents('php://input')` or `ob_start('ob_gzhandler')`. These were **documented false positives** that appeared on every scan. - -**After v2.0:** These files are automatically skipped if they pass checksum verification, reducing false positives to near zero for core files! - -### How It Works - -1. **Automatic Detection:** When a scan starts, the system checks if WP-CLI is available -2. **Core Verification:** Runs `wp core verify-checksums` to verify WordPress core integrity -3. **Plugin Verification:** Runs `wp plugin verify-checksums --all` to verify plugins -4. **Smart Filtering:** Files that pass verification are excluded from pattern matching -5. **Priority Alerts:** Files that fail checksum are flagged as HIGH PRIORITY threats - -### What Gets Verified? - -| Type | Verification | Result | -|------|-------------|--------| -| WordPress Core | `wp core verify-checksums` | ✅ Verified files skipped | -| WP.org Plugins | `wp plugin verify-checksums --all` | ✅ Verified files skipped | -| Premium/Custom Plugins | N/A (no checksum source) | ⚠️ Pattern scan only | -| Themes | N/A (custom code) | ⚠️ Pattern scan only | -| Uploads | N/A (user content) | ⚠️ Pattern scan only | -| mu-plugins | N/A (custom code) | ⚠️ Pattern scan only | - -### Fallback Behavior - -If WP-CLI is **not available**, the scanners automatically fall back to pattern-only mode with no checksum verification. The scan still works, but you'll see the same false positives as before. - -To check if WP-CLI is available: -```bash -wp --version -``` - -If not installed, see [Requirements & Installation](#-requirements--installation) below. - -### Direct PHP Usage - -```bash -# If WP-CLI is not available -php wp-cli/security/scanner-wrapper.php -php wp-cli/security/scanner-targeted.php -php wp-cli/security/scanner-general.php - -# Scan specific directory -php wp-cli/security/scanner-targeted.php /path/to/wordpress -``` - -### Via wp-ops - -These scanners are also runnable through the [`wp-ops`](../../README.md) CLI (`wp-ops wp-cli --help` to list them). Set `WP_SITE_DIR` to the WordPress/Bedrock project to scan: - -```bash -export WP_SITE_DIR=/path/to/your/bedrock-site -wp-ops wp-cli scanner-wrapper -wp-ops wp-cli scanner-targeted /custom/path/to/scan -``` - ---- - -## 📁 What's Included - -``` -wp-cli/security/ -├── checksum-verify.php # Checksum verification module (NEW!) -├── malware-scan.sh # Scan a remote site over SSH (NEW!) -├── scanner-wrapper.php # Wrapper (runs both scanners) -├── scanner-targeted.php # Site-specific threat detection v2.0 -├── scanner-general.php # Broad malware detection v2.0 -├── SECURITY-GUIDE.md # Complete documentation -├── SCANNER-SUMMARY.md # Quick reference guide -└── README.md # This file -``` - ---- - -## 🔍 Two-Scanner Strategy - -### Targeted Scanner (Site-Specific) -**Purpose:** Fast detection of common WordPress vulnerabilities -**Speed:** ~1.7 seconds for 6,600 files - -**Detects:** -- Facebook redirect attempts -- File disclosure vulnerabilities -- WordPress-specific exploits (unauthenticated AJAX) -- SQL injection patterns -- PHP malware (eval, base64_decode) -- Code obfuscation - -**Use:** Weekly monitoring, post-deployment checks - -### General Scanner (Broad Detection) -**Purpose:** Comprehensive malware detection -**Speed:** ~2.5 seconds for 7,400 files - -**Detects:** -- Known malware filenames (c99.php, r57.php, shell.php, etc.) -- Pharmaceutical spam injection -- SEO spam and hidden iframes -- Webshell signatures (FilesMan, WSO, etc.) -- Multiple encoding layers -- Backdoor functions -- Long suspicious base64/hex strings - -**Use:** Monthly deep scans, after suspected compromise - ---- - -## 🚀 Usage Examples - -### Local Development (Bedrock) - -```bash -# From Bedrock site directory -wp eval-file wp-ops/wp-cli/security/scanner-targeted.php -``` - -### Remote sites - -```bash -# One command, from your own machine — no copying, no cleanup -wp-ops malware-scan example.com production - -# Both scanners -wp-ops malware-scan example.com production --mode both -``` - -`malware-scan` streams the scanner source to the target's PHP over stdin, so -nothing is ever written to the server. It resolves the SSH host and WordPress -path from the MCP server's site registry (`mcp-server/config/sites.json`, or -`$WP_OPS_SITES_CONFIG`) when one exists, and otherwise defaults to the stock -Trellis layout — `web@` and `/srv/www//current/web/wp`. - -Only those defaults assume Trellis. The scan itself is plain SSH plus a PHP -CLI, so any WordPress you can SSH into works once you say where it is: - -```bash -wp-ops malware-scan example.com production \ - --host deploy@example.com --path /home/deploy/public_html -``` - -Point `--path` at the **WordPress core directory** (`web/wp` on Bedrock, the -docroot on a classic install). Checksum verification `chdir`s into that path to -run `wp core verify-checksums`, so aiming at a Bedrock site root silently loses -core verification. Checksum verification also needs WP-CLI *on the target*; the -scan still runs without it, in pattern-only mode. - -Ignore the scanner's closing "DELETE THIS FILE after use" warning here — it is -written for the copy-to-the-server workflow below. Nothing was copied. - -> **Copying the scanner to the server by hand is no longer enough.** Since -> v2.0 both scanners `require_once` the sibling `checksum-verify.php`, so a -> lone `scp` of `scanner-targeted.php` fatals on the missing file. Copy both -> files, or use `wp-ops malware-scan`. - -### Scan Multiple Sites - -```bash -#!/bin/bash -# weekly-scan.sh - -sites=( - "/var/www/site1.com" - "/var/www/site2.com" - "/var/www/site3.com" -) - -for site in "${sites[@]}"; do - echo "Scanning $site..." - wp eval-file wp-cli/security/scanner-targeted.php --path="$site" -done -``` - -### Recommended Schedule - -| Frequency | Scanner | Command | -|-----------|---------|---------| -| **Weekly** | Targeted | `wp eval-file wp-cli/security/scanner-targeted.php` | -| **Monthly** | General | `wp eval-file wp-cli/security/scanner-general.php` | -| **After Deployment** | Targeted | `wp eval-file wp-cli/security/scanner-targeted.php` | -| **After Incident** | Both | `wp eval-file wp-cli/security/scanner-wrapper.php` | - -✨ **With Checksum Verification:** Scans are faster and produce fewer false positives! - ---- - -## 📊 Sample Output - -``` -============================================ - SECURITY SCAN COMPLETE -============================================ - -SCAN SUMMARY: - Directories scanned: 1,828 - Files scanned: 6,638 - Files with matches: 79 - Total matches: 86 - Errors: 0 - Scan time: 1.69 seconds - -✓ No suspicious patterns detected! -``` - ---- - -## 🔗 Integration with wp-ops Workflows - -### Pre-Deployment Security Check - -```bash -# Before pushing database to staging -wp eval-file wp-cli/security/scanner-targeted.php -ansible-playbook trellis/backup/database-push.yml -e site=example.com -e env=staging -``` - -### Post-Deployment Verification - -```bash -# After production deployment -ansible-playbook deploy.yml -e env=production - -# Scan production from your own machine -wp-ops malware-scan example.com production -``` - -### Incident Response Workflow - -```bash -# 1. Scan for malware -wp eval-file wp-cli/security/scanner-general.php - -# 2. Backup before cleanup (see trellis/backup/) -ansible-playbook trellis/backup/database-backup.yml -e site=example.com -e env=production -ansible-playbook trellis/backup/files-backup.yml -e site=example.com -e env=production - -# 3. Clean malware (manual remediation) - -# 4. Verify clean -wp eval-file wp-cli/security/scanner-targeted.php -``` - ---- - -## ⚠️ Security Notes - -**IMPORTANT:** -1. **Never commit scanner results** to version control -2. **Delete scanner files from production** after use (if copied temporarily) -3. **Use WP-CLI method** (recommended) instead of browser access -4. **Review output carefully** - not all matches are threats (see false positives) - -### Browser Access (Not Recommended) - -If you must access via browser: - -1. Edit scanner file and add your IP: -```php -$allowed_ips = [ - '127.0.0.1', - 'YOUR.IP.ADDRESS.HERE', // Add your IP -]; -``` - -2. Navigate to: -``` -https://yoursite.com/wp-cli/security/scanner-wrapper.php -``` - -3. **DELETE immediately after use!** - ---- - -## 🎓 Understanding Results - -### Severity Levels - -- **CRITICAL** - Investigate immediately (malware signatures, backdoors) -- **HIGH** - Review within 24 hours (suspicious redirects, file operations) -- **MEDIUM** - Review as time permits (WordPress exploits, obfuscation) - -### Common False Positives - -**These are SAFE and can be ignored:** - -✅ `xmlrpc.php` - Legitimate WordPress XML-RPC -✅ `wp-includes/rest-api/*` - Legitimate REST API -✅ LiteSpeed Cache files - Legitimate optimization -✅ ACF Pro AJAX - Legitimate frontend functionality -✅ Gravity Forms - Legitimate form handling -✅ SimplePie/MySQL.php - Legitimate library - -**See [SCANNER-SUMMARY.md](SCANNER-SUMMARY.md) for complete false positive list** - -### Real Threats (Examples) - -**Investigate these immediately:** - -❌ `c99.php`, `r57.php`, `shell.php` in uploads -❌ `eval(base64_decode(...))` in theme files -❌ `system($_GET['cmd'])` anywhere -❌ Files modified in last 24 hours with suspicious names -❌ New PHP files in `/wp-content/uploads/` - ---- - -## 🛠️ Advanced Usage - -### Automated Cron Job - -```bash -# Add to crontab: crontab -e -# Weekly scan every Monday at 3am -0 3 * * 1 /usr/bin/wp eval-file /path/to/wp-ops/wp-cli/security/scanner-targeted.php --path=/var/www/wordpress > /var/log/wp-scan.log 2>&1 -``` - -### Customization - -#### Add Custom Patterns - -Edit the scanner file and add your patterns: - -```php -// In scanner-targeted.php or scanner-general.php -$patterns = [ - // ... existing patterns ... - - 'custom_threat' => [ - 'name' => 'My Custom Threat', - 'description' => 'Description of what this detects', - 'patterns' => [ - '/your-regex-pattern-here/i', - ], - 'severity' => 'CRITICAL', - ], -]; -``` - -#### Exclude Directories - -```php -$config = [ - 'exclude_dirs' => [ - 'node_modules', - '.git', - 'vendor', - 'your-custom-dir', // Add your exclusions - ], -]; -``` - ---- - -## 🐛 Troubleshooting - -### "wp: command not found" - -**Cause:** WP-CLI is not installed or not in your system PATH - -**Solutions:** - -```bash -# Option 1: Install WP-CLI (see Requirements & Installation section) -curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -chmod +x wp-cli.phar -sudo mv wp-cli.phar /usr/local/bin/wp - -# Option 2: Use full path to wp-cli.phar -php ~/wp-cli.phar eval-file wp-cli/security/scanner-targeted.php - -# Option 3: Use direct PHP method instead -php wp-cli/security/scanner-targeted.php -``` - -### "PHP: command not found" (Shared Hosting) - -**Cause:** PHP is not in your PATH or has a different binary name - -**Solutions:** - -```bash -# Try alternative PHP binary names -/usr/bin/php scanner-targeted.php -/usr/local/bin/php scanner-targeted.php -php-cli scanner-targeted.php -php8.2 scanner-targeted.php # Version-specific (common on cPanel) - -# Find PHP location -which php -whereis php -find /usr -name "php*" -type f 2>/dev/null | grep bin -``` - -### cPanel/Plesk PHP Version Issues - -**cPanel - Select PHP version:** - -1. MultiPHP Manager → Select desired PHP version for domain -2. Or use version-specific binary: -```bash -# Check available PHP versions -ls /opt/cpanel/ea-php*/root/usr/bin/php - -# Use specific version -/opt/cpanel/ea-php82/root/usr/bin/php scanner-targeted.php -``` - -**Plesk - Use specific PHP version:** - -```bash -# List available PHP versions -ls /opt/plesk/php/ - -# Use specific version -/opt/plesk/php/8.2/bin/php scanner-targeted.php -``` - -### File Upload Restrictions - -**Problem:** Cannot upload `.php` files via File Manager (security restriction) - -**Workaround:** - -```bash -# Method 1: Upload as .txt, then rename -1. Upload as: scanner-targeted.txt -2. Rename via File Manager to: scanner-targeted.php - -# Method 2: Use FTP instead (usually less restricted than web-based File Manager) -# FTP clients like FileZilla typically allow .php uploads - -# Method 3: Upload via SSH/SCP (if available) -scp scanner-targeted.php user@yoursite.com:~/public_html/ -``` - -### Scanner Reports No Files Found - -**Cause:** Incorrect path or scanner not in WordPress root - -**Solutions:** - -```bash -# Method 1: Specify full WordPress path -php scanner-targeted.php /full/path/to/wordpress - -# Method 2: Navigate to WordPress root first -cd /var/www/html # or your WordPress directory -php /path/to/scanner-targeted.php - -# Method 3: Use pwd to verify current directory -pwd # Should show WordPress root with wp-config.php -ls -la wp-config.php # Verify you're in the right place -``` - -### Timeout Errors - -**Problem:** Scanner times out on large WordPress installations - -**Solutions:** - -```bash -# Increase PHP timeout -php -d max_execution_time=600 wp-cli/security/scanner-wrapper.php - -# Or edit scanner file directly (add at top after scan-results.txt -cat scan-results.txt -``` - -**Option 2: Cron Job (Scheduled Scans)** -```bash -# Add to crontab -0 3 * * 1 /usr/local/bin/wp eval-file /path/to/security-scanner.php --path=/path/to/wordpress | mail -s "Weekly Security Scan" admin@yoursite.com -``` - -**Option 3: Server Script** -```bash -#!/bin/bash -# /usr/local/bin/weekly-scan.sh - -cd /path/to/wordpress -php wp-content/themes/client/security-scanner.php > /tmp/scan-$(date +%Y%m%d).txt - -# Email results -mail -s "Security Scan Results" admin@yoursite.com < /tmp/scan-$(date +%Y%m%d).txt - -# Keep last 10 scans -ls -t /tmp/scan-*.txt | tail -n +11 | xargs rm -f -``` - ---- - -## Customization - -### Add Custom Patterns - -Edit `security-scanner.php` around line 84: - -```php -$patterns = [ - // ... existing patterns ... - - // Add your custom pattern - 'custom_threat' => [ - 'name' => 'Custom Threat Pattern', - 'description' => 'Describe what this detects', - 'patterns' => [ - '/pattern1/i', - '/pattern2/i', - ], - 'severity' => 'CRITICAL', - ], -]; -``` - -### Adjust Scan Configuration - -Edit around line 65: - -```php -$config = [ - 'start_path' => dirname(__FILE__, 4), - 'exclude_dirs' => [ - 'node_modules', - '.git', - 'vendor', - 'wp-content/uploads/cache', - 'wp-content/cache', - 'custom-exclude-dir', // ← Add custom exclusions - ], - 'file_extensions' => ['php', 'js', 'txt', 'htm', 'html'], // ← Add extensions - 'max_execution_time' => 300, - 'max_file_size' => 5 * 1024 * 1024, // ← Adjust max file size -]; -``` - ---- - -## Troubleshooting - -### Timeout Errors - -If scanning times out: - -```php -// Increase limits in security-scanner.php -$config = [ - 'max_execution_time' => 600, // 10 minutes instead of 5 -]; -``` - -Or run via command line (no timeout limits): -```bash -php -d max_execution_time=600 security-scanner.php -``` - -### Memory Errors - -```bash -php -d memory_limit=512M security-scanner.php -``` - -### Permission Errors - -```bash -# Run as web server user -sudo -u www-data php security-scanner.php - -# Or fix permissions temporarily -chmod +r -R /path/to/scan -``` - -### Too Many False Positives - -Edit patterns to be more specific: - -```php -// Instead of: -'/eval\s*\(/i', // Matches ALL eval() - -// Use: -'/eval\s*\(\s*base64_decode/i', // Only matches eval(base64_decode(...)) -``` - ---- - -## Best Practices - -### Regular Scanning Schedule - -- **Development:** Before each deployment -- **Staging:** After each deployment -- **Production:** Weekly via cron job -- **After Updates:** After WordPress/plugin updates -- **After Incidents:** Immediately after any security concern - -### Scan Checklist - -- [ ] Run scanner on development -- [ ] Review all CRITICAL and HIGH matches -- [ ] Verify core file integrity: `wp core verify-checksums` -- [ ] Check plugin integrity: `wp plugin verify-checksums --all` -- [ ] Review recent file modifications -- [ ] Check database for suspicious content -- [ ] Review access logs for unusual requests -- [ ] Document any findings - -### Security Hardening - -After scanning, implement these protections: - -1. **File Integrity Monitoring** - ```bash - # Install Wordfence or Sucuri - wp plugin install wordfence --activate - ``` - -2. **Disable File Editing** - ```php - // wp-config.php - define('DISALLOW_FILE_EDIT', true); - ``` - -3. **Limit Login Attempts** - ```bash - wp plugin install limit-login-attempts-reloaded --activate - ``` - -4. **2FA Authentication** - ```bash - wp plugin install two-factor --activate - ``` - -5. **Security Headers** (see [LOADING-ISSUES.md](LOADING-ISSUES.md) for .htaccess examples) - ---- - -## Related Documentation - -- **[LOADING-ISSUES.md](LOADING-ISSUES.md)** - Full investigation of November 2025 Facebook redirect issue -- **[LITESPEED-CACHE-TRADEOFF.md](LITESPEED-CACHE-TRADEOFF.md)** - Cache performance analysis -- **[DEPLOYMENT-GUIDE.md](DEPLOYMENT-GUIDE.md)** - Webhook rate limiting deployment - ---- - -## Changelog - -### Version 1.0.0 (November 5, 2025) - -**Initial Release:** -- Facebook redirect pattern detection (from November 2025 investigation) -- PHP malware detection (eval, base64, gzinflate, etc.) -- File disclosure vulnerability detection -- SQL injection pattern detection -- WordPress-specific exploit detection -- Code obfuscation detection -- Multiple severity levels (CRITICAL, HIGH, MEDIUM) -- Command-line directory scanning support -- IP whitelist for browser access -- Colored CLI output -- HTML output for browser access -- Scan statistics and timing -- False positive documentation - -**Tested On:** -- WordPress 6.4.x -- PHP 8.1/8.2 -- 6,600+ files scanned in ~1.7 seconds -- Zero false negatives on known patterns - ---- - -## Support & Contact - -### Report False Positives - -If you encounter a legitimate pattern flagged as suspicious: - -1. Document the file path and line number -2. Verify the code is from official WordPress/plugin source -3. Add to "Common False Positives" section above -4. Consider adjusting the pattern regex to be more specific - -### Report Security Issues - -If you find an actual security vulnerability: - -1. **DO NOT commit to public repository** -2. Document the vulnerability privately -3. Fix immediately -4. Review Git history for when it was introduced -5. Check if it was exploited (access logs) -6. Update passwords/secrets if compromised - ---- - -## License - -This scanner is part of the Client WordPress theme. -Copyright © 2025. All rights reserved. - -Based on malware detection patterns from: -- WordPress Security Best Practices -- OWASP Top 10 -- GitHub: jasperf/lookforbadguys.php -- Real-world WordPress security investigations - ---- - -**Document Version:** 1.0 -**Last Updated:** November 5, 2025 -**Next Review:** After next security incident or quarterly diff --git a/wp-cli/security/scanner-wrapper.php b/wp-cli/security/scanner-wrapper.php index 0ac8c24..382b0d0 100644 --- a/wp-cli/security/scanner-wrapper.php +++ b/wp-cli/security/scanner-wrapper.php @@ -91,5 +91,5 @@ } echo "\n"; -echo "\033[0;33mFor documentation, see: wp-cli/security/SECURITY-GUIDE.md\033[0m\n"; +echo "\033[0;33mFor documentation, see: https://wpvilla.in/wordpress-malware-scanners-targeted-general-checksum-verification/\033[0m\n"; echo "\n";