# Security Hardening ## WordPress — Public File Exposure ### Problem Wordfence flags "Critical | Public Files" when these files are publicly accessible: - `license.txt` — reveals WP version - `wp-config-sample.php` — shouldn't be public - `readme.html` / `readme.[hash].html` — reveals WP version - `error_log` — exposes internal paths and PHP errors - `xmlrpc.php` — brute force / DDoS attack vector ### Fix applied 2026-05-18 (all 17 sites) 1. Deleted `license.txt`, `wp-config-sample.php`, `readme*.html`, `error_log` from all webroots 2. Added `xmlrpc.php` block to `.htaccess` on all sites (kaburu.co already had it) 3. Installed mu-plugin `remove-sensitive-files.php` on all sites — auto-removes sensitive files after any WP core update so they never come back ### mu-plugin location `wp-content/mu-plugins/remove-sensitive-files.php` on every managed site. Auto-loaded by WordPress, no activation needed. Hooks `upgrader_process_complete` and fires on `core` updates only. ### .htaccess xmlrpc block (added 2026-05-18) — ⚠️ PROVEN DEAD CODE ON LITESPEED The original 2026-05-18 fix used an Apache-style `.htaccess` block: ``` # Block xmlrpc Order Deny,Allow Deny from all ``` **This never actually worked.** LiteSpeed silently ignores Apache 2.2 `` / `Order Deny,Allow` syntax in `.htaccess`. Verified 2026-08-13: claybusters.scot had the block but `POST /xmlrpc.php` still returned HTTP 200. Wordfence's WAF was the only thing actually blocking xmlrpc attacks the whole time. ### ✅ Real fix applied 2026-08-13 — vhost context block (all 30 WP sites) Replaced the dead `.htaccess` block with a LiteSpeed-native vhost context (exact-path, not regex — regex form is unreliable on LiteSpeed): ``` context /xmlrpc.php { allowBrowse 0 accessControl { deny * } } ``` Rolled out via `scripts/estate-vhost-xmlrpc-block.sh` (same proven pattern as the debug.log rollout). Result: **28 sites added, 2 already had it (kaburu.co, rf.kaburu.co), 0 failed.** Full LiteSpeed restart (`lswsctrl stop && start`). Verified per-site at origin (`curl --resolve SITE:443:49.13.202.144`) → HTTP 403 on all WP sites. Edge cases (both fine): - `chippy.kaburu.co.uk` → 301 (redirect-only alias → thechippyvan.co.uk, which is 403) - `llmtest.kaburu.co` → 404 (xmlrpc not reachable; test-site vhost quirk, non-exploitable) Pre-flight verified 2026-08-13: **nothing legit uses xmlrpc.php** — MainWP no longer uses xmlrpc SSO (zero dashboard-IP traffic across weeks of logs), and all xmlrpc hits were bot traffic (Azure/DO/Oracle cloud IPs). ### Sites covered (25 — full fleet as of 2026-07-31) 24hrresponse.com, aberdeenrda.co.uk, bankhousecatering.co.uk, billing.myretonmarquees.co.uk, chippy.kaburu.co.uk, claybusters.scot, dm-ph.com, doctorfin.co.uk, friendscic.org, ggsgenerators.co.uk, jafricasafari.com, kaburu.co, kaburu.co.uk, kaburusvr.uk, l8.kaburu.co.uk, l8waterhygiene.co.uk, llmtest.kaburu.co, longniddrybowlingclub.com, longniddrybowlingclub.co.uk, myretonmarquees.co.uk, pilates-edinburgh.co.uk, rf.kaburu.co, test-md.kaburu.cc, touchwell.co.uk, womenunlimited.africa --- ## WordPress — Debug Log Exposure (wp-content/debug.log) ### Problem `wp-content/debug.log` is written by WordPress when `WP_DEBUG_LOG=true` is set in `wp-config.php`. Can leak full request/response payloads, database errors, stack traces, user data, installed plugin paths. ### Defence-in-depth (applied 2026-07-31) Three layers, cheapest → most reliable: 1. **`WP_DEBUG_LOG=false`** in wp-config.php (eliminates source). Flipped on every site 2026-07-31. 2. **.htaccess Log File Protection ``** — present on 23/25 sites. **DOES NOT WORK on LiteSpeed** (Apache ``/`` ignored on OLS). 3. **Vhost context block — the ONLY reliable block on LiteSpeed.** Added to all 25 sites 2026-07-31: ```apache context /wp-content/debug.log { allowBrowse 0 accessControl { deny * } } ``` - Appended to each `/usr/local/lsws/conf/vhosts/{site}/vhost.conf` - Backups at `vhost.conf.bak-20260731-0501` - **Requires full LiteSpeed restart** (`lswsctrl fullrestart`) — graceful SIGUSR1 does NOT pick up new contexts. - Verified: 23/25 sites return HTTP 403 for debug.log; 2 (friendscic.org, llmtest.kaburu.co) return 404 (no file on disk = block never matches; equally safe). - Belt-and-braces with Wordfence WAF (which also blocks the literal filename by name match). **Steve's rule 2026-07-31: "if WAF ever stops, we become exposed" — now mitigated by vhost context block.** --- ## CyberPanel — Port Lockdown - Port 8090 (CyberPanel): allow 100.64.0.0/10 (Tailscale), DROP rest — applied 2026-05-17 - Port 8081 (pdns API): allow 127.0.0.1, DROP rest — applied 2026-05-17 - Port 22 (SSH): allow Tailscale, DROP rest — applied 2026-05-17 - Rules persisted to /etc/iptables/rules.v4 --- ## Pending - [ ] Hetzner snapshot automation (manual snapshot done 2026-05-17) - [ ] TrueNAS offsite backup - [ ] FTP disabled / SFTP enforced - [ ] Hermes self-monitoring heartbeat (if Z840 goes down) - [ ] ufw proper firewall ruleset - [ ] CyberPanel backup verification - [ ] WordPress per-site backup plugin - [ ] Wordfence — investigate rainbowvapes.co.uk (High | Error), touchwell.co.uk (High | Server State)