Red-Teaming Our Own HA Cluster — What We Found and What We Fixed

We built a 10-server high-availability WordPress cluster across three Australian cities. We hardened it with CIS benchmarks, AppArmor, auditd, and a Wazuh SIEM. Then we asked the obvious question: does it actually hold up?

So we red-teamed ourselves — running a full penetration test from an external attack server against our own infrastructure. Here is what we found, what we fixed, and what we learned.

The Setup

Our HA cluster runs across Sydney, Brisbane, and Melbourne on BinaryLane infrastructure:

  • 6 web servers (nginx + PHP-FPM + WordPress) behind an anycast load balancer
  • 3 MariaDB servers (primary + 2 replicas) on a private VPC with no public IPs
  • 1 jumpbox running Wazuh SIEM, Ansible monitoring, and centralized logging
  • 1 encrypted file server (LUKS + SMB3) accessible only via WireGuard

Before the pentest, we had already applied significant hardening:

  • CIS Ubuntu 24.04 benchmark at 72.2%
  • AppArmor enforcing on all servers
  • auditd with 54 audit rules
  • Wazuh SIEM with 10 agents
  • Centralized rsyslog forwarding
  • Unattended security updates
  • SSH key-only authentication
  • Dedicated Ansible user (not root)

We felt good about it. The question was: would an external attacker agree?

The Red Team Approach

We used a separate BinaryLane server (outside the VPC) as our attack box. No credentials, no insider knowledge — pure black-box external assessment. We built a Python-based pentest suite that ran 7 phases autonomously:

  1. Full port scan — all 65,535 TCP ports on every public-facing server (655,350 total port checks)
  2. SSH analysis — banner grabbing, authentication method enumeration
  3. SSL/TLS analysis — protocol versions, cipher suites, certificate validation
  4. HTTP header audit — security headers across all web endpoints
  5. WordPress-specific tests — 23 endpoint probes, XML-RPC, REST API, version fingerprinting, cookie analysis
  6. Standalone server analysis — our landing page servers
  7. DNS reconnaissance — records, SPF, DMARC, WHOIS

What the Infrastructure Got Right

The network layer was rock solid:

  • Jumpbox: zero open ports. All 65,535 ports showed as filtered. Completely invisible from the outside.
  • Web servers: only ports 80 and 443. Nothing else visible. SSH was completely dark.
  • Database servers: unreachable. No public IP, no way to even attempt a connection.
  • SSL/TLS: TLS 1.2 and 1.3 only. AES-256-GCM preferred. No weak ciphers, no deprecated protocols.

All the sysctl hardening, firewall rules, and BinaryLane hypervisor-level filtering did exactly what it was supposed to do. From the outside, this looked like a minimal web-only surface.

What We Found (The Bad News)

The infrastructure was solid. The WordPress application layer was not.

Our initial scan found 11 findings — including 2 critical:

Critical: XML-RPC Wide Open

WordPress ships with xmlrpc.php enabled by default. This endpoint exposes over 80 methods including system.multicall — which lets an attacker try hundreds of password guesses in a single HTTP request. It bypasses rate limiting, it bypasses fail2ban, and it bypasses login lockout plugins. It is the number one WordPress brute-force vector.

Critical: REST API Leaking Usernames

The WordPress REST API endpoint /wp-json/wp/v2/users was publicly returning our admin username, user ID, and Gravatar email hash — with no authentication required. Combined with XML-RPC, an attacker now has both the username AND an unlimited brute-force channel.

The Attack Chain

Put these together and you get a textbook WordPress compromise:

  1. Hit /wp-json/wp/v2/users → get username “admin
  2. Brute-force password via xmlrpc.php using system.multicall
  3. Login to wp-admin → install a malicious plugin or edit theme PHP
  4. Achieve code execution as www-data
  5. Read wp-config.php for database credentials
  6. Access the database via the VPC

No amount of sysctl hardening or AppArmor would have stopped this. It is a completely different attack surface.

Other Findings

  • No HSTS header — browsers could be tricked into connecting via HTTP first
  • No Content Security Policy — XSS attacks not mitigated at the header level
  • WordPress version disclosed — via RSS feed, asset URLs, and readme.html
  • wp-cron.php externally accessible — potential DoS vector
  • Plugin/theme directories listing — fingerprinting surface
  • No SPF/DMARC — email spoofing possible on all domains

The Fix (Same Day)

We remediated everything within the same session and deployed across all 6 web servers:

Finding Severity Fix
XML-RPC Critical Blocked in nginx → 403
User enumeration Critical REST API users endpoint blocked → 403
Version disclosure High Generator tag removed, version strings stripped, readme.html deleted
No HSTS High Strict-Transport-Security header added
No CSP High Content-Security-Policy deployed
wp-cron exposed Medium Blocked externally, server-side cron instead
Directory listing Medium Plugin/theme directories return 403

The Re-Test

After remediation, we ran the entire 7-phase pentest again. Results:

  • 0 critical findings (down from 2)
  • 0 high findings (down from 3)
  • 1 medium finding — WordPress installer page accessible (low risk, WP already installed)
  • 6 low findings — missing SPF/DMARC DNS records

The entire XML-RPC → user enumeration → brute-force attack chain is now dead. Every critical endpoint returns 403.

The Lesson

We spent a morning hardening the infrastructure layer — kernel parameters, audit rules, AppArmor profiles, CIS benchmarks. All valuable work. But the red team found that the real risk was in the application layer, not the infrastructure.

The two critical findings (XML-RPC and user enumeration) are default WordPress behaviour. They ship enabled on every WordPress installation. They are not bugs — they are features that most sites do not need and should disable.

Infrastructure hardening and application hardening are different disciplines that protect against different threats. You need both. CIS benchmarks will not save you from an open XML-RPC endpoint, and blocking XML-RPC will not save you from a weak kernel configuration.

Red-teaming your own infrastructure is the only way to see what an attacker actually sees. Everything else is assumptions.

Our Security Stack (Post-Remediation)

Layer Controls
Network VPC isolation, hypervisor firewalls, nftables, no public IPs on DB/file servers
Access SSH key-only, AllowUsers whitelist, dedicated ansible user, no persistent keys on servers
Encryption TLS 1.2/1.3, LUKS at rest, SMB3 transport encryption, WireGuard tunnel
Monitoring Wazuh SIEM (10 agents), auditd (54 rules), centralized rsyslog, Ansible health checks
Hardening CIS 72.2%, AppArmor enforcing, sysctl hardened, kernel modules disabled
Application XML-RPC blocked, user enum blocked, HSTS, CSP, version hidden, wp-cron internal
Patching Unattended security updates (daily), kernel current (6.8.0-106)

Total monthly cost for all of this: ~$66.50 AUD.

If you are running WordPress on any hosting provider — check if your xmlrpc.php is accessible. It probably is.