The stack we audited
A small-business WordPress site came in for a security audit, and the findings were the ordinary kind that quietly put a site one automated scan away from compromise. The page builder, Elementor, was pinned at version 3.11.1, carrying four critical CVEs including SQL injection and stored cross-site scripting. Contact Form 7 was running 5.8, exposed to CVE-2023-6449, an arbitrary file upload. Nothing exotic, just outdated plugins, which is the dominant way WordPress sites get breached.
The uncomfortable part is how normal this is. The site worked. It looked fine. The owner had no reason to suspect anything, because outdated plugins do not show symptoms until they are exploited. The audit existed to find the gap before someone else did.
Outdated plugins are the attack surface
A plugin is safe while it is patched. The moment a vulnerability is published as a CVE and you have not updated, the exploit is public knowledge and your installed version is a named target for automated scanners. Attacks on WordPress are overwhelmingly blind and automated. Bots sweep the network for known-vulnerable versions of popular plugins, so running an old Elementor or Contact Form 7 is not an abstract risk. It is an advertised one.
On this site:
- Elementor 3.11.1 carried four critical CVEs (SQL injection and stored cross-site scripting among them), while the patched line had already moved to 3.17.3. Every one of those four was live on production.
- Contact Form 7 5.8 was exposed to CVE-2023-6449, a drag-and-drop file-upload flaw that let an authenticated user with editor rights upload arbitrary files, a direct path toward remote code execution.
Neither plugin was unusual. Both sit on a huge share of WordPress sites. That is exactly why their known vulnerabilities are worth a scanner’s time. Patchstack’s State of WordPress Security in 2026 report measures the median time from public disclosure of a high-impact vulnerability to mass exploitation at five hours. The window between “CVE is public” and “a bot is already scanning your version” is measured in hours, not weeks.
Abandoned plugins make this worse. When the author stops shipping fixes, a CVE closes only by removal or by swapping the tool. Leaving a dead plugin “just in case” in wp-content/plugins/ keeps the attack surface alive even after deactivation, if the files still sit on disk and remain reachable via known paths.
CVE triage instead of an update-all queue
The WordPress admin shows updates alphabetically or by date. Security triage sorts differently:
- Severity and attack class. File upload, remote code execution and SQL injection go before XSS that needs a logged-in admin.
- Required privileges. An unauthenticated flaw, or one reachable as subscriber/editor, ranks above one that needs an administrator you have one of and protect with 2FA.
- Whether the feature is enabled. A CVE in a drag-and-drop upload module only matters on sites that have that module active. The audit checks whether the extension is even on disk.
- Whether a public exploit exists. When a proof of concept circulates in threat intel, response time shrinks to hours. Patchstack also notes that 46 percent of vulnerabilities have no patch at disclosure, so sometimes the only immediate answer is to disable the feature or the plugin, not “click Update”.
On the site in this case, triage was straightforward: Contact Form 7 first (upload), then Elementor (injection and stored XSS), then cleanup of remaining plugins without an active CVE. The CVE number and publish date do not replace that order.
Updates through staging, not live
Jumping Elementor from 3.11.1 to the 3.17+ line is not only a security patch. It changes renderers, widgets and often child-theme templates. A contact form with an old upload shortcode can throw a white screen after update or silently drop attachments. That is why remediation after the audit looked like this:
- Full copy of files and database to staging with the same PHP and the same constants in
wp-config.php(without productionWP_DEBUG_DISPLAY). - Update plugins with CVEs in triage order, then a manual pass: homepage, cart or lead form, Elementor edit screen, form submit with attachment.
- Compare PHP logs and HTTP 500s before pushing to production.
- On production, the same plugin version pair, a short smoke test, and only then removal of unused plugins.
Updating directly on the live site without a backup turns CVE patching into a second incident: a dead checkout or a broken hero. Staging is not an agency luxury. It is part of the process that official WordPress hardening describes at the maintenance layer, not only at the file layer.
WAF and virtual-patching limits
A WAF (Cloudflare, Sucuri, host rules) and virtual patching buy time between CVE disclosure and deploying a fix. Sucuri Website Firewall documentation describes blocking known request patterns, not removing vulnerable code from disk. That distinction matters when a site owner has heard “we have a firewall, so we are safe”.
The Patchstack State of WordPress Security in 2026 report indicates that typical hosting-plus-WAF setups stop about 12 percent of known WordPress attacks. The rest bypasses signatures, uses a logged-in session, hits an endpoint outside the rules, or targets a plugin with no rule yet. A WAF does not:
- remove an abandoned plugin from
wp-content/plugins/, - fix missing nonce and
current_user_canchecks in custom code, - replace a version cross-check against the CVE database,
- guarantee protection when the attack goes through a logged-in editor (as with CVE-2023-6449).
A sensible setup is WAF as the first line plus an update routine as the lasting line. A WAF alone without triage and staging leaves exactly the state we found: Elementor and the form unpatched for years because “the firewall should be enough”.
What the audit checks
A security audit is methodical, not clever. Its core:
- Inventory every installed plugin and theme, and cross-check each version against its known CVEs and minimum-safe version. That is what surfaced the Elementor and Contact Form 7 exposure here.
- Triage found CVEs by severity, privileges and whether the feature is enabled on production.
- Test custom and theme code for WordPress security basics: nonce and capability checks on actions, input sanitised before the database, output escaped before the page, and endpoints that require authorisation.
- Check server-level exposure: an open
xmlrpc.php, PHP errors on production leaking paths, missing security headers. - Review update, staging and backup policy, because an unmaintained site drifts back into this state within months.
- Assess whether the WAF actually sees the attack paths for the found CVEs, or only gives a sense of coverage.
The version cross-check is the unglamorous part that catches the most, because the most common WordPress vulnerability is not a clever zero-day. It is a popular plugin three versions behind its patch.
Where AI-assisted deploys make it worse
This audit was about outdated third-party plugins, a neglect pattern. AI-assisted deploys add a second layer. They install plugins fast, often without setting up an update and staging routine, so versions drift behind patches even faster. AI-generated custom code often ships without the nonce, capability and sanitisation checks WordPress expects, so you inherit both an outdated-plugin problem and a generated-code problem at once.
What fixing it looks like
Remediation order follows risk, not convenience:
- Patch or remove plugins with live CVEs immediately, starting with file-upload and injection paths, after a staging test.
- Remove abandoned or unused plugins, shrinking the surface permanently (files off disk, not only deactivation).
- Close server-level exposure (
xmlrpc.php, production error display, missing headers). - Set the WAF as a time buffer, not as a substitute for updates.
- Put a real update, staging and backup routine in place so the site does not return to four live CVEs within a year.
If you need to move from a CVE list to a remediation plan on a live site, start with a WordPress security audit.
Glossary
- CVE - a Common Vulnerabilities and Exposures identifier, a public catalogue entry for a specific known vulnerability.
- CVE triage - sorting flaws by severity, privileges and feature exposure, not by the order in the updates screen.
- SQL injection - an attack that smuggles database commands through unsanitised input.
- Stored cross-site scripting - malicious script saved on the site and served to other users.
- WAF - a web application firewall that filters HTTP requests; it narrows attack windows, it does not remove vulnerable code.
- Nonce / capability check - WordPress mechanisms that confirm a request is intentional and the user is allowed to make it.
The takeaway
A WordPress site does not have to be interesting to be attacked. It has to be running a known-vulnerable version of a popular plugin, which describes a large share of sites that have not been audited. The dominant risk is also the most boring to fix: cross-check every plugin version against its CVEs, triage, patch through staging, do not trust a WAF alone, and remove what you no longer use. The site in this audit was one scan away from trouble and did not know it. Most sites in that position do not.







