Open Redirect, CRLF Injection, and Clickjacking: Fix All Three

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Open redirect, CRLF injection, and clickjacking appear together in scanner output for a reason: they share a common root cause pattern where user-supplied input affects output channels without sufficient validation or encoding. They are often dismissed as low severity in isolation, but each can be a critical building block in attack chains. An open redirect on an OAuth endpoint enables token theft. A CRLF injection in a redirect header enables session fixation. A clickjacking vulnerability on a payment confirmation page enables fraudulent transactions.
Fix all three. This guide covers the mechanics of each vulnerability, the specific code and header changes that eliminate them, and the HTTP security header policy that provides defense-in-depth against all three at the infrastructure level.
Open redirect: code-level fix
Open redirect is fixed at the application layer by replacing blocklist-based URL checks with an allowlist that explicitly permits only approved redirect destinations. Blocklists that attempt to detect malicious URL patterns fail reliably against encoding variants such as %2F%2F, protocol-relative URLs, and exotic schemes. The fix requires two steps: identifying every redirect sink in your codebase where user input determines the destination, then applying the appropriate allowlist validation strategy based on whether the redirect target is an internal path, your own domain, or a defined set of partner domains. Both Node.js and server-side frameworks provide URL parsing utilities that make host comparison straightforward, and the pattern applies regardless of framework.
Step 1: Identify all redirect sinks in your application
Search for every location in your codebase where a redirect is issued using user-supplied input: response.redirect(req.query.next), window.location = params.url, header('Location: ' . $_GET['url']). Grep for: redirect(, Location:, window.location, sendRedirect, response.sendRedirect. Each location is a potential open redirect. Categorize them: same-app navigation (post-login redirect to a page), OAuth callback, external link redirect. Different categories require different validation strategies.
Step 2: Apply allowlist validation by category
For same-app navigation: validate that the URL is a relative path (starts with /, does not start with //, does not contain a protocol like http://). Node.js example: const url = new URL(redirectTo, 'https://app.example.com'); if (url.origin !== 'https://app.example.com') throw new Error('Invalid redirect'). For OAuth callbacks: validate the redirect_uri is in a pre-registered allowlist before initiating the OAuth flow. For external link redirects: maintain an explicit allowlist of partner domains and validate the hostname against it before redirecting. Default behavior if validation fails: redirect to your application homepage, not to the supplied URL.
CRLF injection: stripping carriage returns and line feeds
CRLF injection is prevented by sanitizing user-controlled input before it appears in any HTTP response header value, including Location headers, Set-Cookie values, and any custom headers that reflect request parameters. The attack works because HTTP uses carriage return plus line feed sequences to separate header lines, so injecting those characters into a header value allows attackers to append arbitrary headers to your response. The fix has two layers: application-layer sanitization that strips %0D, %0A, \r, and \n from any value destined for a response header, and infrastructure-layer verification that your web framework and reverse proxy reject CRLF in headers by default. Both layers matter because legacy code paths may bypass framework protections.
Sanitize at the point of header construction
Any user-supplied value that is written into an HTTP response header must have CRLF characters stripped. JavaScript/Node.js: const sanitized = value.replace(/[\r\n]/g, ''); Python: sanitized = re.sub(r'[\r\n]', '', value); PHP: $sanitized = str_replace(array("\r", "\n"), '', $value). Apply this to: Location header values, Set-Cookie values constructed from user input, custom headers like X-Custom-Header that include user data, and any other header value derived from request parameters. This sanitization does not affect the carriage return and line feed that HTTP itself uses to separate headers — those are added by the HTTP layer, not by your application code.
Verify framework and server-level protection
Most modern frameworks reject CRLF in headers by default — verify yours does. Node.js http module throws an error if a header value contains \r or \n. Express.js inherits this protection. Python's http.server module raises an exception. Check your version: frameworks before ~2016 may not have this protection. At the nginx/Apache layer: add a WAF rule or mod_security rule to strip CRLF from query string parameters that could end up in headers, as defense-in-depth against legacy code paths that may not sanitize.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Clickjacking: implement frame-ancestors policy
Clickjacking is fully eliminated by a Content-Security-Policy frame-ancestors directive delivered as a real HTTP response header on every application response. The attack works by embedding your application in an invisible iframe, overlaid on a malicious page, so victim clicks land on your hidden UI elements instead of the attacker's visible ones. The frame-ancestors directive instructs browsers to refuse rendering your page inside any iframe that does not match the permitted origin list. Pairing it with the older X-Frame-Options header as a fallback ensures coverage across all browser versions. Both headers take under 30 seconds to add to your server configuration and should be applied to all routes, not just login or payment pages, because attackers can chain clickjacking with other vulnerabilities on any page they can embed.
Set Content-Security-Policy frame-ancestors on all responses
Add to every HTTP response (not just login or payment pages — attackers can chain clickjacking with other vulnerabilities on any page): Content-Security-Policy: frame-ancestors 'none'. If you have a legitimate use case for same-domain embedding (an admin dashboard that embeds your own app): Content-Security-Policy: frame-ancestors 'self'. If you need to allow a specific trusted partner to embed your app: frame-ancestors https://trusted-partner.example.com. Also add X-Frame-Options: DENY as a fallback for older browsers that do not support CSP frame-ancestors. Both headers take 30 seconds to add and eliminate clickjacking entirely.
Verify the header is present on all application routes
After deployment, verify the header is present on responses from every application route type: the HTML page responses, API responses that might return HTML, and administrative panel pages. Use curl -I https://app.example.com/login | grep -i 'frame\|content-security'. If your application has different response paths (a CDN serving some pages, your origin serving others), check the header is present on all response paths. A common gap: the CDN strips CSP headers from cached responses because the header was not configured to be forwarded — configure your CDN to preserve and pass through security response headers.
The bottom line
Open redirect, CRLF injection, and clickjacking each take less than a day to fix properly, but each requires a different fix: allowlist URL validation eliminates open redirect, CRLF character stripping eliminates header injection, and the frame-ancestors header eliminates clickjacking. Do not rely on WAF rules as a substitute for code-level and header-level fixes. After fixing, add automated checks to your CI pipeline: a link-checker that verifies redirect destinations match your allowlist, a header scanner that confirms Content-Security-Policy and X-Frame-Options are present on all production responses, and an integration test that confirms CRLF in redirect parameters is rejected. These three checks catch regressions before they reach production.
Frequently asked questions
What is an open redirect and why does it matter beyond phishing?
An open redirect occurs when an application uses user-supplied input to determine the redirect destination without validating that destination. Example: GET /login?next=https://evil.com — after successful login, the application redirects to the value of the 'next' parameter. On its own, an open redirect enables phishing (an email link to your trusted domain that then redirects to an attacker-controlled page). When chained: an open redirect on your OAuth authorization endpoint allows an attacker to capture OAuth access tokens by substituting their server for the legitimate redirect_uri. OAuth-specific open redirect leads to account takeover, not just phishing. Additionally, open redirects bypass browser warnings because users see your domain in the initial URL.
How do I fix an open redirect vulnerability?
The correct fix is allowlist-based validation: only permit redirects to URLs that match an approved list. (1) For redirects within your own application (post-login, post-logout): validate that the redirect target is a relative URL or matches your own domain exactly. A relative URL check: if (url.startsWith('/') && !url.startsWith('//')) { redirect(url) }. A relative URL starting with // is protocol-relative and will redirect to an external domain, so check for // prefix. (2) For redirects to a defined set of partner domains: maintain an explicit allowlist and check the redirect URL hostname against it. (3) Blocklist approaches (detecting http://, https://, javascript:, data:) fail against encoding variations like %2F%2Fevil.com. Use allowlists exclusively.
What is CRLF injection and how is it exploited?
CRLF injection (HTTP response splitting) occurs when user-supplied input that contains carriage return (\r, %0D) and line feed (\n, %0A) characters is reflected in an HTTP response header without encoding. In HTTP, headers are separated by CRLF sequences. If your application sets a header like Location: /redirect?next=[user-input] and the user-input contains %0D%0ASet-Cookie:+session=attacker, the resulting response includes an injected Set-Cookie header that the browser processes as legitimate. Exploitation enables: session fixation (injecting a known session cookie), cache poisoning, and cross-site scripting via injected Content-Type or script-src headers. Modern HTTP frameworks and reverse proxies strip or reject CRLF in header values, but raw header construction in legacy code is still vulnerable.
How do I fix CRLF injection vulnerabilities?
Prevention requires stripping or rejecting CRLF characters in all values that are used to construct HTTP response headers. At the application layer: sanitize any user-controlled input before using it in a redirect URL, cookie value, or other header. Strip %0D, %0A, \r, and \n from values before they appear in headers. Modern web frameworks (Express.js with recent versions, Spring Boot, ASP.NET Core, Django) reject CRLF in headers by default — validate that your framework version handles this. At the infrastructure layer: configure your web server or reverse proxy (nginx, Apache, Cloudflare) to strip CRLF sequences from response headers, providing a defense-in-depth layer even if application code is vulnerable.
What is clickjacking and what are the most effective defenses?
Clickjacking (UI redress attack) embeds your application in an invisible iframe on an attacker-controlled page, positioned so that clicks the victim believes are hitting the attacker's page actually hit hidden elements of your application. Attack scenarios: clicking 'Like' or 'Follow' on a social media account without consent, triggering a purchase confirmation, approving an OAuth consent dialog. Defenses in order of effectiveness: (1) Content-Security-Policy frame-ancestors directive (modern, granular): frame-ancestors 'none' blocks all embedding; frame-ancestors 'self' permits same-origin embedding only; frame-ancestors https://trusted.example.com permits specific trusted domains. (2) X-Frame-Options header (older, widely supported): DENY blocks all embedding, SAMEORIGIN permits same-origin embedding. Use both for maximum browser coverage. Frame-busting JavaScript is not an effective defense and is easily bypassed.
How do I implement Content-Security-Policy to prevent both clickjacking and other injections?
CSP frame-ancestors for clickjacking: Content-Security-Policy: frame-ancestors 'none' in your application server response headers. For nginx: add_header Content-Security-Policy "frame-ancestors 'none'" always; in the server block. For Apache: Header always set Content-Security-Policy "frame-ancestors 'none'". For Express.js: use the helmet middleware (helmet.frameguard({action: 'deny'}) sets X-Frame-Options, and configure frame-ancestors separately in contentSecurityPolicy). Note: frame-ancestors in a meta http-equiv tag is ignored by browsers — it must be set as a real HTTP response header. Do not use frame-ancestors 'self' unless you have a legitimate same-origin embedding use case (some admin dashboards do).
Can I fix open redirect, CRLF injection, and clickjacking with a WAF rule?
Partially. A WAF can provide detection and some mitigation: open redirect can be partially mitigated by WAF rules that detect common bypass patterns (%2F%2F, javascript:, data:) in redirect parameters, but WAF-based blocklists are not a complete fix because attackers find bypasses. CRLF injection can be blocked at the WAF by inspecting for %0D and %0A in query string parameters and blocking requests containing them in values that could end up in headers. Clickjacking cannot be fixed at the WAF — it requires the X-Frame-Options or Content-Security-Policy frame-ancestors header in your application responses. WAF rules are a detection and partial mitigation layer; proper code-level and header-level fixes are required for each vulnerability to eliminate them completely.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
