~/blog/twenty-findings-one-wordpress-sitePOSTED
by Shady Nathan Tawfik · May 30, 2026 · 4 min

Twenty Findings, One Platform: How Volume Changes The Remediation Order

An industrial supplier with a large content surface produced twenty findings across seven categories. The value was in the ordering, not the count.

Interactive[blog-2.0]

TL;DR: Twenty findings are usually four causes expressed across twenty pages. I grouped them, ordered the fixes by dependency instead of severity, and explain why mail authentication came first.

The scenario

I ran this as a black-box review: no credentials, only the publicly visible surface. Here is what I saw before I touched anything.

A global industrial supplier running a multilingual content platform with a large media library, an online catalogue and customer service integration. Assessment covered the public content surface, the media library, mail authentication, header policy and the plugin estate.

I removed all identifying information. The target is described only by sector and technology class so the pattern can be reused.

The pattern

The pattern I recognised — and that I keep finding in similar estates:

Twenty findings is not twenty problems. It is usually three or four problems expressed across twenty pages. The same missing header appears on every template, the same user enumeration endpoint serves the same eight accounts on every language variant, the same mail policy weakness affects every newsletter. Counting occurrences inflates the report and obscures the plan, which is why the useful output of an assessment this size is a grouping.

Once grouped, the ordering follows from dependencies rather than severity. Mail authentication is first because phishing against a supplier is the most plausible scenario and the fix is a DNS change nobody can revert by accident. Header policy is second because it is a one-line change per tier that stops a class of attacks permanently. User enumeration is third, because it has no direct exploit but is the entry condition for credential attacks, and it is the one finding that will come back if the site is redeployed from a template.

The category that is usually last and should not be is version hygiene. Plugins at current releases create no work; plugins past end of life create permanent work and are usually owned by an agency, which makes them a conversation rather than a task. Doing the permanent work early while the agency relationship is cooperative is worth more than doing it late when it is not.

TL;DR: An industrial supplier with a large content surface produced twenty findings across seven categories. The value was in the ordering, not the count.

Findings

SeverityFindingEvidence
HOCHUser enumeration across all language variantsSame accounts exposed on every locale
HOCHNo security headers on any templateTemplate-level gap, not a per-page gap
MEDIUMMail authentication not enforced for the sending domainNewsletter and order mail spoofable
MEDIUMMedia library enumerable without authenticationFull asset inventory readable
MEDIUMAdministrative paths reachable on a predictable prefixConsistent discovery path
LOWPlugin and core versions exposedExact build identifiable across locales

The attack path

Twenty findingsGroup by causeMail policyTemplate headersUser enumerationMedia accessFix firstFix secondFix third and monitorFix with ownership decision

Reproduction in my lab

Every command below targets a lab container I control. Nothing here is aimed at a live system.

Group your own findings by cause rather than by page

~/code/bash bash
# the same finding across every locale of your own site
$ for loc in en de fr it; do
    printf '%-4s users=%s headers=%s\n' "$loc" \
      "$(curl -s "https://example.org/$loc/wp-json/wp/v2/users" | grep -c '"slug"')" \
      "$(curl -sI "https://example.org/$loc/" | grep -ciE 'content-security-policy|x-frame-options')"
done

Detection in your own estate

Measure the template gap across every locale you own

~/code/bash bash
$ for loc in en de fr it es; do
    n=$(curl -sI "https://example.org/$loc/" | grep -ciE 'content-security-policy|x-frame-options|x-content-type-options|referrer-policy')
    printf '%-4s security-headers=%s/4\n' "$loc" "$n"
done

# and the media inventory
$ curl -s 'https://example.org/wp-json/wp/v2/media?per_page=100' \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); print("media objects:", len(d))'

Remediation

Fix the template once, not twenty pages

~/code/apache apache
# put the baseline in the virtual host, not in each template
<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Content-Security-Policy "default-src 'self'; frame-ancestors 'self'"
</IfModule>

ServerTokens Prod
ServerSignature Off

Once an attacker gains access, they can perform the following actions:

User enumeration across all language variants
No security headers on any template
Mail authentication not enforced for the sending domain
Media library enumerable without authentication
Administrative paths reachable on a predictable prefix

Takeaways

  • Twenty findings are usually four causes. Group before you prioritise.
  • Fix the template, not the pages; page-level fixes do not survive a redesign.
  • Mail authentication is DNS, it is hard to revert by accident, and phishing is the plausible threat.
  • Templates get redeployed from a clean copy. Add the header baseline to the repository, not the server.

For educational purposes only. Every reproducible command targets my own lab environment (localhost), never a live system. © 2026