The Exploit
No authentication required: any visitor can hit the plugin's proxy endpoint and make it fetch arbitrary URLs.
curl -i -sS 'https://TARGET/template-proxy/?remote_url=http://169.254.169.254/latest/meta-data/instance-id'
The plugin responds with 200 OK and the proxied body from the specified host. In a real attack, the same request can be used to reach internal-only services such as localhost, 127.0.0.1, or cloud metadata endpoints from the web server.
What the Patch Did
Before:
if ($is_proxy && $remote_url) {
$remote_url = urldecode($remote_url);
$response = wp_remote_get($remote_url);
After:
if ($is_proxy && $remote_url) {
// Security: Only allow admins to use this proxy
if (!current_user_can('manage_options')) {
wp_die('Unauthorized', 403);
}
$remote_url = urldecode($remote_url);
// Security: Validate URL format and scheme
$parsed_url = parse_url($remote_url);
if (!$parsed_url || !isset($parsed_url['scheme']) || !in_array($parsed_url['scheme'], ['http', 'https'], true)) {
wp_die('Invalid URL scheme', 400);
}
// Security: Strict Domain Allowlist
$allowed_hosts = [];
if (defined('PORTAL_API')) {
$allowed_hosts[] = parse_url(PORTAL_API, PHP_URL_HOST);
}
if (defined('WORDPRESS_SYNC_API')) {
$allowed_hosts[] = parse_url(WORDPRESS_SYNC_API, PHP_URL_HOST);
}
$remote_host = $parsed_url['host'];
The patch adds a WordPress capability check via current_user_can('manage_options'), explicit parse_url() validation with an http/https scheme whitelist, and a host allowlist derived from configured backend endpoints. These are the security controls that close the SSRF hole.
Root Cause
This is CWE-918: Server-Side Request Forgery. The attacker-controlled query parameter remote_url enters the plugin through the /template-proxy/ endpoint and is passed straight into wp_remote_get() after only urldecode(). There is no validation of URL scheme, host, or caller privileges, so the application trusts the user-supplied URL and makes an outbound request on the server's behalf.
Why It Works
The single load-bearing fix is the admin capability gate: if (!current_user_can('manage_options')) { wp_die('Unauthorized', 403); }. Without that check, an unauthenticated visitor can still trigger wp_remote_get() with any destination. The scheme-validation and allowlist code are important hardening layers, but the core exploit is stopped by preventing unprivileged access to the proxy path. The additional lines are defense-in-depth: they prevent abuse even if the endpoint is later exposed to administrators, and they block non-HTTP(S) wrappers that wp_remote_get() could otherwise follow.
Hardening Checklist
- Enforce endpoint privileges with
current_user_can('manage_options')or a similarly strict capability before performing network requests. - Validate remote URLs with
parse_url()and allow only explicithttp/httpsschemes. - Avoid calling
wp_remote_get()on user-supplied destinations unless the host is allowlisted. - Protect AJAX/proxy endpoints with capability checks and consider
wp_verify_nonce()for state-changing operations. - Do not expose generic proxy functionality through public endpoints like
/template-proxy/unless absolutely necessary.
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-0807