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";