July 3, 2026
The Methodology Behind CVE-2026–11395: An Unauthenticated SSRF Hiding in Plain Sight
Sports analytics and vulnerability research are the same job. You’re looking for the gap between what a system claims to guarantee and what…

By Lucius-logs
7 min read
Sports analytics and vulnerability research are the same job. You're looking for the gap between what a system claims to guarantee and what it actually does. This is the security side of that coin.
There's a specific half-second when an SSRF confirmation request actually lands — before your brain catches up, your stomach already knows. You're staring at a log line that shouldn't exist, and for one beat you're not sure if you're looking at a bug or a mistake you made in your own test rig. Then you check it again, and it's real, and now you've got a decision to make about what to do with it.
That half-second is why I do this. Earlier this year it led to my first published CVE — CVE-2026–11395, an unauthenticated Server-Side Request Forgery in the CF7 to Webhook plugin for WordPress, affecting every version up to 5.0.0. CVSS 7.2, High. The plugin runs on north of 30,000 sites.
I'm not going to hand you a working exploit chain here — that's not something I'd put on the public internet regardless of what any disclosure policy technically allows. What I want to walk through instead is the methodology: how I look at plugins like this one, why the bug was sitting there in plain sight, and what it taught me about where these things actually live. If you're trying to break into vulnerability research, this is closer to useful than a payload would be anyway — payloads get patched, methodology doesn't.
How a routine recon sweep found it
I didn't set out looking for this specific plugin. It came out of a broader recon sweep — I run scripts against batches of WordPress plugins looking for patterns worth a closer look by hand. The scripts don't find bugs. They triage which of the thousands of candidates deserve my actual time, which is a very different job.
CF7 to Webhook got flagged for the reason most of my real finds get flagged: it makes outbound HTTP requests, and it lets an admin configure part of that request dynamically. User-influenced input plus outbound network calls is close to the top of my personal watchlist, because that combination is exactly where SSRF and IDOR bugs like to nest. The script doesn't tell you there's a bug. It tells you where to go look — the same as a scouting report doesn't tell you who's going to miss a tackle, just which matchup to actually watch on Sunday.
Why plugins, and why webhooks specifically
If you're hunting bugs in WordPress, the core is a bad place to spend your time. It's been beaten on by thousands of researchers for two decades — anything easy to find there got found years ago. The plugin ecosystem is different: tens of thousands of them, wildly uneven code quality, and a huge chunk exist specifically to bridge WordPress to some other service — Zapier, webhooks, CRMs, payment processors, whatever. I've burned entire weekends auditing plugins that turned out clean, which is its own kind of useful. You start to feel the difference between code that was written carefully and code that was written to ship by Friday.
That bridge is the interesting part. Any time a plugin's whole job is "take data from over here and send it over there," you've got two trust boundaries touching each other, and somebody had to write the code that hands data across that seam. That's where I focus — not the core, not the popular security plugins everybody already picked clean, but the connector code nobody thinks is exciting enough to audit twice.
Think of it like scouting: everybody already knows what the star player does. You spend your film time on the backup right tackle nobody's game-planned for, because that's where the actual mismatch shows up on Sunday. Plugin connector code is the backup right tackle of the WordPress ecosystem.
What CF7 to Webhook actually does
CF7 to Webhook lets a site admin wire up Contact Form 7 so that whenever someone submits a form, the plugin fires an outbound HTTP request to a webhook URL — Zapier, Make, whatever automation tool the site owner's using. The design decision that mattered here: the plugin lets the admin build that webhook URL using CF7 field placeholders. Instead of a hardcoded destination, part of the URL can be dynamically filled in from whatever the form submitter typed.
That's a reasonable feature on its face — plenty of legitimate reasons to route submissions to different endpoints based on form content. The question I always ask when I see something like that: does the placeholder substitution happen anywhere that actually matters for security, or only in places that feel safe? If a placeholder lands in the query string or the body, that's mostly a data problem. If it can land in the host segment of the URL — the part that decides where the request actually goes — that's not a data problem anymore. That's a routing problem, and routing problems are how SSRF happens.
The actual gap
Turns out the plugin didn't distinguish between "this placeholder is filling in a value" and "this placeholder is determining the destination." If an admin configured a webhook URL with a CF7 field placeholder sitting in the host portion, whatever the form submitter typed got treated as a hostname when the plugin fired the request. No authentication required — the person triggering the request is just whoever's filling out the public contact form.
Practically, that means the server itself — not the visitor's browser, the server — can be made to send requests wherever an attacker points it. Internal services, loopback addresses, whatever's sitting on the private network the WordPress box happens to live on. The server is a trusted actor from the network's point of view, so it can reach things a random internet visitor never could.
Picture a mail carrier who'll deliver to any address written on the envelope, no questions asked, because delivering mail is literally his job. He's not the attacker. He's not doing anything wrong by his own logic — he's just following the address he was handed. The problem is nobody ever told him some addresses aren't supposed to be reachable from the sidewalk. SSRF works the same way: the server isn't "hacked" in the traditional sense, it's doing exactly what it's built to do — make outbound requests — except the address on the envelope came from a stranger instead of the person who's supposed to be in charge of where mail goes.
Two conditions had to be true for it to be exploitable: the admin's webhook config had to actually use a placeholder in the host segment, and the form had to be publicly reachable. Neither is an exotic setup — placeholders in URLs are exactly the feature the plugin advertises, and public contact forms are, well, the entire point of a contact form plugin.
A lot of vulnerabilities get downgraded in people's heads because they think "well, you need to be logged in as admin, so it's not that bad." This one flips that. The admin's the one who configures the vulnerable setup, but the attacker doesn't need to be the admin, or logged in as anything at all — they just need a browser and the ability to find a form. That's the difference between "theoretical" and "worth patching now, not next sprint." CVSS agreed: AV:N/AC:L/PR:N/UI:N is about as low-friction as an attack chain gets.
What confirming it actually looked like
This part doesn't need a payload to explain. Once I had a working theory, I built a small isolated lab — two throwaway containers on their own Docker network, one running the vulnerable plugin, one just listening for whatever showed up. No live sites, no real targets, nothing outside a sandbox I fully controlled.
Confirmation looked exactly like the theory predicted: a form submission on one container triggered an outbound request that landed on the second container — a box that was never supposed to be reachable from a public form. That's the whole test. Not exciting to watch, which is sort of the point. The best vulnerabilities usually aren't dramatic to prove, just quietly inevitable once you see the gap.
The fix, and why it's the right one
Wordfence and the plugin author shipped the patch by routing outbound webhook requests through wp_safe_remote_request(), which blocks requests to private, loopback, and link-local address ranges by default. That's the correct fix — not "sanitize the placeholder better," but "stop trusting the destination at all once user input can touch it." Think of it as putting a bouncer on the door who checks every address before the mail carrier's allowed to leave, instead of trusting him to know which streets are off-limits. If you want the old flexibility back for a genuinely trusted internal host, you have to explicitly opt back in through a filter. Default-safe, opt-in-dangerous — that's the pattern I want to see more plugins ship with.
What I actually look for now
If you're getting into this kind of research, here's the filter I run in my head on any plugin that moves data between systems:
- Does user-controllable input ever end up determining where a request goes, not just what's in it?
- Is there a feature that lets an admin configure something dynamic, and did the dev think through what happens when "dynamic" gets pointed somewhere they didn't expect?
- Is the trust boundary between "public form submitter" and "server-side outbound request" actually enforced, or just assumed?
- None of that requires exotic tooling. It requires reading connector code slower than the developer wrote it, and asking "what's the worst input this field will accept" instead of "what's the input this field expects."
That's the whole job, on either side of what I do — security research or sports data, doesn't matter. Find where the model of the system and the reality of the system come apart, and get there before someone with worse intentions does.
If you're working on plugin or connector-code audits yourself, I'd genuinely like to hear what your triage filter looks like — drop it in the comments. And if this kind of breakdown is useful, follow along; I've got more of these coming, both from the security side and the sports-analytics side of what I build.