TL;DR: A nonce in the page is not a secret, and the plugin knew it: a dormant submission feature published its request tokens and made the upload endpoint reachable by reading the page source. I show the durable fix — removal.
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 business innovation network with a public event calendar, newsletter integration and a caching layer, on a fully patched content platform. Assessment covered plugin configuration, third-party script integrity, mail authentication, header policy and user enumeration.
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:
A request token is a good design. The next question is where it lives. Nonces are designed to be handed to the browser and returned with the request, because the browser has to prove it is the one that loaded the form. That means the value is in the page source by design, which is fine as long as it is bound to a session and expires quickly. What makes it a finding is when the token is also bound to a capability the user is not supposed to have.
An event calendar with a front-end submission feature will accept file uploads from anonymous visitors, because the calendar is public and the submission form is how speakers propose a talk. The submission endpoint needs a token to prevent spam. The token is rendered into the page. The upload directory and the validation routine are then reachable by anyone who reads the page, and the form handler for a feature nobody uses is still deployed.
The general pattern is dormant functionality. Features added for a specific event, campaign or workflow tend to outlive their purpose inside the plugin, because removing a plugin setting is nobody's ticket. The security question is not whether the feature is intended, but whether it is still enabled, still exposed and still defended.
Findings
| Severity | Finding | Evidence |
|---|---|---|
| MEDIUM | Submission feature enabled with tokens published in the page | Upload endpoint reachable by reading the page source |
| MEDIUM | Content security policy absent across the site | No policy despite a consent plugin being installed |
| MEDIUM | Five third-party scripts without integrity attributes | No subresource integrity on any external script |
| MEDIUM | Mail authentication set to monitoring only | Sending domain spoofable |
| LOW | User enumeration via three separate paths | Read interface, login error text and author redirect |
| LOW | Caching layer exposes an internal header naming the software | Cache software and configuration flags visible |
The attack path
Reproduction in my lab
Every command below targets a lab container I control. Nothing here is aimed at a live system.
Find published request tokens in your own page source
$ curl -s https://example.org/events/ > /tmp/page.html
# nonce-like values in the markup
$ grep -oiE '(nonce|_wpnonce|mec[a-z_]*nonce|verification)["\x27:=\s]+[a-z0-9]{8,}' /tmp/page.html | head
# what forms and endpoints the page references
$ grep -oiE 'action=["\x27][^"\x27]+|admin-ajax\.php[^"\x27]*' /tmp/page.html | sort -u | head
Detection in your own estate
Inventory external scripts and check for integrity attributes
$ curl -s https://example.org/ | grep -oE '<script[^>]*src="[^"]*"[^>]*>' \
| grep -v 'src="/' \
| sed 's/.*src="\([^"]*\)".*/\1/' | sort -u
# and count how many of those carry integrity
$ curl -s https://example.org/ | grep -oE '<script[^>]*src="https?://[^"]*"[^>]*>' \
| grep -c integrity
Remediation
Turn the feature off if it has no owner
# the durable fix for dormant functionality is removal, not hardening
import subprocess
# what is enabled on this install
$ wp plugin list --status=active --field=name
$ wp option get mec_options --format=json | python3 -c '
import json,sys
cfg = json.load(sys.stdin)
fe = cfg.get("features", {})
for name, state in fe.items():
print(name, state)'
# disable the anonymous submission path and re-test
$ wp option patch update mec_options '{"features":{"fes_nonce":false,"fes_upload_nonce":false,"recaptcha_key":""}}'
Pin external scripts with integrity attributes
<!-- every third-party script gets an integrity attribute -->
<script src="https://cdn.example.com/analytics.js"
integrity="sha384-EXAMPLEHASHEXAMPLEHASHEXAMPLEHASHEXAMPLEHASHEXAMPLEHASH"
crossorigin="anonymous"></script>
Once an attacker gains access, they can perform the following actions:
Takeaways
- A token in the page is normal; a token guarding a capability nobody checked is a finding.
- Dormant features outlive their purpose inside the plugin.
- The durable fix for an unused feature is removal, not a configuration change.
- If a consent plugin is installed, the content security policy generator should be on.