XSS in Laravel Applications
Laravel's Blade templates auto-escape output with {{ }}, but the {!! !!} syntax renders raw HTML. Combined with rich text editors, markdown rendering, and user-uploaded SVGs, Laravel apps face multiple XSS vectors that require careful mitigation.
Scan Your Laravel AppHow XSS Manifests in Laravel
The {!! !!} Blade syntax is Laravel's primary XSS vector. Developers use it for WYSIWYG editor content, markdown HTML output, and formatted emails. Unlike {{ }}, it performs zero escaping. Laravel's @json directive can lead to XSS in JavaScript contexts if not used carefully. Embedding user data in inline scripts requires JSON encoding plus HTML entity escaping. File upload endpoints that accept SVG files are another vector, as SVGs can contain embedded JavaScript. Laravel's validation rules do not check SVG content for scripts by default.
Real-World Impact
A Laravel SaaS application allowed users to customize email templates using a WYSIWYG editor. The templates were stored and rendered with {!! !!}. An attacker injected JavaScript into a template that executed when customer support previewed the email, compromising the admin panel.
Step-by-Step Fix
Sanitize HTML before unescaped output
Use HTMLPurifier to clean user HTML content.
// Install: composer require mews/purifier
// In your controller or service:
use Mews\Purifier\Facades\Purifier;
$cleanHtml = Purifier::clean($request->input('content'), [
'HTML.Allowed' => 'b,i,em,strong,a[href|target|rel],p,br,ul,ol,li',
]);
// Store the clean HTML
$post->content = $cleanHtml;
$post->save();
{{-- In Blade template --}}
{!! $post->content !!} {{-- Safe because already sanitized --}}Add CSP middleware
Create middleware to add Content Security Policy headers.
// app/Http/Middleware/SecurityHeaders.php
namespace App\Http\Middleware;
class SecurityHeaders
{
public function handle($request, $next)
{
$response = $next($request);
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
);
$response->headers->set('X-Content-Type-Options', 'nosniff');
return $response;
}
}
// Register in app/Http/Kernel.php
protected $middleware = [
\App\Http\Middleware\SecurityHeaders::class,
];Validate SVG uploads
Check SVGs for embedded scripts before accepting uploads.
// app/Rules/SafeSvg.php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class SafeSvg implements Rule
{
public function passes($attribute, $value): bool
{
if ($value->getClientMimeType() !== 'image/svg+xml') {
return true;
}
$content = file_get_contents($value->getRealPath());
$dangerous = ['<script', 'onload=', 'onerror=', 'onclick=',
'javascript:', 'eval(', 'expression('];
foreach ($dangerous as $pattern) {
if (stripos($content, $pattern) !== false) return false;
}
return true;
}
public function message(): string
{
return 'SVG file contains potentially dangerous content.';
}
}Use @json safely in Blade
Properly encode data for JavaScript contexts.
{{-- UNSAFE: XSS in script context --}}
<script>
var user = {!! json_encode($user) !!};
</script>
{{-- SAFE: @json with Js::from() --}}
<script>
var user = {{ Js::from($user) }};
</script>Prevention Best Practices
1. Use {{ }} for all user data in Blade templates. Only use {!! !!} with sanitized content. 2. Sanitize HTML with HTMLPurifier before storing and rendering. 3. Validate SVG uploads for embedded scripts. 4. Use @json directive for JavaScript context data, wrapped in single quotes. 5. Add CSP headers via middleware.
How to Test
1. Search for {!! in Blade templates: grep -r "{!!" --include="*.blade.php" 2. Test each unescaped output with <script>alert(1)</script>. 3. Upload an SVG containing <script>alert(1)</script> to file upload endpoints. 4. Check for user data embedded in inline <script> tags. 5. Use Vibe App Scanner to automatically detect XSS patterns in your Laravel application.
Frequently Asked Questions
Does Laravel Blade auto-escape output?
Yes, {{ $variable }} auto-escapes HTML entities. However, {!! $variable !!} renders raw HTML without escaping. Always use {{ }} for user data and only use {!! !!} with content that has been sanitized with HTMLPurifier or a similar library.
Is HTMLPurifier better than strip_tags in Laravel?
Yes. strip_tags() is unreliable for security - it can be bypassed with malformed HTML. HTMLPurifier properly parses HTML and removes dangerous elements while preserving allowed formatting. Always use HTMLPurifier (via mews/purifier package) instead of strip_tags() for security.
How do I handle SVG XSS in Laravel?
Create a custom validation rule that scans SVG content for script tags, event handlers, and javascript: URLs. Alternatively, convert SVGs to PNG on upload using a library like Intervention Image, which removes any embedded scripts.
Related Security Resources
Is Your Laravel App Vulnerable to XSS?
VAS automatically scans for xss vulnerabilities in Laravel applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Laravel stack.