Cache poisoning happens when malicious headers trick your CDN. Learn how to secure your Node.js and PHP apps against header injection and request smuggling.
During a late-night incident response session last year, we watched our main landing page serve a broken, minified version of an internal admin panel to thousands of users. It wasn't a database breach or a server compromise; it was a classic case of cache poisoning triggered by an unvalidated X-Forwarded-Host header.
The culprit was a misconfigured Varnish layer that trusted the incoming request headers implicitly. When an attacker sent a specifically crafted request, the CDN cached the response based on the malicious header, effectively serving a poisoned version of the site to everyone else. If you're building modern web apps, you need to understand that your CDN is only as secure as the weakest header it trusts.
At its core, cache poisoning occurs when a proxy or CDN caches a response that shouldn't be public, or when the cache key is manipulated by user-controlled input. In a typical scenario, an attacker injects headers like X-Forwarded-Host or X-Original-URL. If your application code uses these headers to generate absolute URLs or internal redirects, the proxy might store the result of that logic.
When you're dealing with header injection, you aren't just looking at one specific vulnerability. You're looking at a breakdown in the contract between the edge (your CDN) and the origin (your Node.js or PHP server).
We once tried solving this by simply stripping headers at the Nginx level. It worked for about two days until a new microservice requirement forced us to pass the X-Forwarded-Proto header through, which inadvertently opened a new request smuggling vector because the upstream service wasn't validating the header's format.
Your CDN security posture relies on two things: what you cache and how you calculate the cache key. Most CDNs—like Cloudflare, Fastly, or CloudFront—allow you to define a "Cache Key." If that key includes headers that are easily spoofed, you’re in trouble.
Before you start hardening your application, consider these three rules:
Securing your application against request smuggling and injection requires a defense-in-depth approach. You shouldn't just rely on the firewall; you need to sanitize the inputs within your application logic.
In Node.js, you're often tempted to use req.headers['x-forwarded-host']. Stop doing that directly. Instead, implement a middleware that validates the input against an expected list of domains.
JAVASCRIPT// A simple validation middleware const allowedHosts = [CE9178">'myapp.com', CE9178">'api.myapp.com']; function validateHost(req, res, next) { const host = req.headers[CE9178">'x-forwarded-host']; if (host && !allowedHosts.includes(host)) { return res.status(400).send(CE9178">'Invalid Host Header'); } next(); }
In PHP, the $_SERVER superglobal is populated by the web server. If you're using FPM, ensure your fastcgi_param settings in Nginx aren't passing untrusted data. If you have to handle dynamic headers, use a whitelist approach similar to the Node.js example above.
If you are dealing with more complex architectural risks, check out Preventing Uncontrolled Resource Consumption in Node.js and PHP Apps to ensure your validation logic doesn't introduce its own performance bottlenecks.
To stop these attacks, you need to audit your proxy configuration. Most developers focus too much on the code and ignore the infrastructure.
X-Forwarded-Prefix, strip it at the Nginx or Load Balancer level.Vary header properly. If your application logic changes based on a header, include that header in the Vary response header so the CDN knows to cache separate versions.HSTS to prevent protocol downgrade attacks that often accompany these header-based exploits.We’ve found that the most effective defense is a "deny-by-default" policy on headers. If you aren't sure if a header is needed, drop it. It's much easier to add a header back than it is to explain to your stakeholders why your site is serving malicious content to your users.
I’m still not entirely comfortable with how some modern serverless functions handle incoming headers by default, as they often automatically map every incoming header to a property. Always verify the platform-specific documentation for your runtime. Security is a moving target, and today's "safe" configuration might be tomorrow's exploit if the underlying proxy logic changes.
Dependency confusion attacks can silently compromise your app. Learn how to secure your Node.js and PHP supply chains using scoped registries and lockfiles.
Read moreLearn to stop open redirect vulnerabilities by validating destination URLs. Protect your Node.js and PHP apps from phishing attacks with these practical tips.