UnveilTech

UnveilScan Blog

← All articles

Try UnveilScan free

Why WordPress sites score lower (and what to fix)

Posted 2026-04-29 · 8 min read · case studyWordPress

In our scans of 1000 popular domains we noticed a clear bimodal distribution by stack. Static sites and modern frameworks cluster at 70-85/100. WordPress sites cluster at 50-65/100. The gap isn't WordPress-the-software being bad. It's the default configuration WordPress ships with, plus the typical plugin ecosystem, plus the kind of ops team that runs WordPress in 2026. Concrete findings and how to fix them.

What we see on the average WordPress install

FindingFrequencySeverity
WordPress version disclosed in <meta>~85%LOW (information disclosure → CVE targeting)
/readme.html reachable~70%LOW (full version disclosure)
/wp-json/wp/v2/users exposes usernames~60%MEDIUM (user enumeration → password attack)
/xmlrpc.php reachable~55%MEDIUM (brute-force amplification, pingback abuse)
Outdated jQuery in active plugins~75%MEDIUM (known XSS sinks)
No CSP header~80%MEDIUM (no defense in depth)
X-Powered-By, Server banners with versions~50%LOW (CVE targeting)
?author=1 redirect leaks admin username~40%MEDIUM (admin username for password spray)

Each of these is small. Stacked on a single install they account for the 15-20 point gap vs a hardened static site.

Fix #1: hide the version

The easy one. WordPress emits <meta name="generator" content="WordPress 6.4.3"> by default. Drop it via a single hook:

// in functions.php or a security plugin
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');

Also block /readme.html and /license.txt at the web server:

# nginx
location ~* /(readme|license)\.(html|txt)$ {
    return 404;
}

Net: the attacker doesn't know your exact version. CVE targeting becomes guesswork.

Fix #2: shut down user enumeration

Two endpoints leak usernames:

  1. /wp-json/wp/v2/users — JSON list of all users with login names.
  2. /?author=1 — redirects to /author/admin-username/.

Mitigation:

// Block /wp-json/wp/v2/users for unauthenticated requests
add_filter('rest_endpoints', function($endpoints) {
    if (isset($endpoints['/wp/v2/users'])) {
        unset($endpoints['/wp/v2/users']);
    }
    if (isset($endpoints['/wp/v2/users/(?P<id>[\\d]+)'])) {
        unset($endpoints['/wp/v2/users/(?P<id>[\\d]+)']);
    }
    return $endpoints;
});

// Block ?author= scans
if (preg_match('/\\?author=\\d+/', $_SERVER['REQUEST_URI'])) {
    wp_die('Not allowed', 403);
}

Or use a plugin (Wordfence, iThemes Security) that does both with a checkbox.

Fix #3: kill xmlrpc.php (or rate-limit it)

XML-RPC is rarely used in 2026 (REST API replaced its purpose). It's commonly abused for brute-force amplification (system.multicall tries 1000 password combos in one request) and pingback DDoS (use your site to launch attacks against others).

# Hard block at nginx
location = /xmlrpc.php { return 403; }

If a Jetpack feature you actually use needs XML-RPC, allowlist Jetpack's IP ranges and rate-limit aggressively (5 requests/minute per IP).

Fix #4: add security headers

WordPress doesn't ship security headers. Add at the web server (so they survive plugin deactivations):

# nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com" always;

Caveat: WordPress (and most plugins) inline scripts and styles. A strict CSP without 'unsafe-inline' breaks most themes. The CSP above is permissive but still a defense improvement over no CSP. Tighten over time as you replace inline scripts with external ones (or migrate to a static-export workflow).

Fix #5: source the plugin ecosystem

Most WordPress sites we scan run 15-30 plugins. The CVE feed (OSV.dev) tracks ~5000 WordPress plugins. The probability that some plugin you have installed has a known CVE in the past 90 days is high.

Practical maintenance:

Fix #6: source maps and dev leftovers

WordPress builds increasingly include webpack-bundled JS (Gutenberg blocks, page builders). About a third of installs we scan ship source maps (.map files) that expose plugin internals + sometimes API tokens. Block at nginx:

location ~* \.(map)$ { return 404; }

And block the usual leak suspects:

location ~ /\.(env|git|svn|aws) { return 404; }
location ~ \.(bak|backup|swp|sql)$ { return 404; }

What this gets you

A typical WordPress install in our 2026 dataset starts at ~58/100. Apply the 6 fixes above, no plugin updates, just config: ~74/100 (+16 points). Apply CVE patching on the plugin set: ~80-85/100. The gap from "default WordPress" to "hardened WordPress" is real and entirely on the operator side.

A static site with the same domain DNS/TLS posture and equivalent business value scores 80-90 from the start. The framework choice is a security choice. WordPress is fine when you accept the operational discipline; it's a liability when you don't.

What's your score?

Run a free Basic scan on your WordPress install. Get the specific findings + remediations.

Run a scan