DEV Community

Cover image for Symfony XSSI Attack: Fixes + Free Security Check
Pentest Testing Corp
Pentest Testing Corp

Posted on

Symfony XSSI Attack: Fixes + Free Security Check

Cross-Site Script Inclusion (XSSI) is a sneaky data-leak where an attacker <script>-loads your API and reads secrets if your endpoint returns executable JavaScript or unsafe JSON. Below is a concise, Symfony-focused guide with copy-paste fixes—perfect for dev readers and teams.

Symfony XSSI Attack: Fixes + Free Security Check

👉 Try our free website security scanner to spot XSSI and other issues.
Also see our blog: Pentest Testing Corp.


How XSSI happens (Symfony example)

Vulnerable: returning executable JS (not JSON). If an attacker includes it, your data becomes a global variable on their page.

// src/Controller/ProfileController.php
#[Route('/api/profile.js', name: 'api_profile_js', methods: ['GET'])]
public function profileAsJs(): Response {
    $data = ['email' => 'alice@example.com', 'role' => 'admin'];
    // ❌ Executable JavaScript — XSSI-prone
    $body = 'window.__profile = ' . json_encode($data) . ';';
    return new Response($body, 200, ['Content-Type' => 'application/javascript']);
}
Enter fullscreen mode Exit fullscreen mode

Attacker page:

<script src="https://victim.example.com/api/profile.js"></script>
<script>
// now readable cross-site — data leaked
console.log(window.__profile);
</script>
Enter fullscreen mode Exit fullscreen mode

📸 Screenshot of the Website Vulnerability Scanner tool homepage:

Screenshot of the free tools webpage where you can access security assessment tools.Screenshot of the free tools webpage where you can access security assessment tools.


Safer pattern #1: Strict JSON + anti-XSSI prefix

Return pure JSON (not JS) and add a harmless prefix that breaks execution if included via <script>.

// src/Controller/ProfileController.php
use Symfony\Component\HttpFoundation\Response;

#[Route('/api/profile', name: 'api_profile', methods: ['GET'])]
public function profile(): Response {
    $data = ['email' => 'alice@example.com', 'role' => 'admin'];

    $prefix = ")]}',\n"; // breaks script execution; clients strip it before JSON.parse
    $json = $prefix . json_encode($data, JSON_UNESCAPED_SLASHES);

    return new Response($json, 200, [
        'Content-Type' => 'application/json; charset=utf-8',
        'X-Content-Type-Options' => 'nosniff',
        'Cross-Origin-Resource-Policy' => 'same-site',
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Client parsing tip:

// strip prefix before JSON.parse
fetch('/api/profile').then(r => r.text()).then(t => {
  const clean = t.replace(/^\)\]\}',\n?/, '');
  return JSON.parse(clean);
});
Enter fullscreen mode Exit fullscreen mode

Safer pattern #2: Block cross-origin inclusion at the edge

Add security headers globally via an event subscriber.

// src/EventSubscriber/SecurityHeadersSubscriber.php
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;

final class SecurityHeadersSubscriber implements EventSubscriberInterface {
    public static function getSubscribedEvents(): array { return ['kernel.response' => 'onKernelResponse']; }
    public function onKernelResponse(ResponseEvent $event): void {
        $r = $event->getResponse();
        $r->headers->set('X-Content-Type-Options', 'nosniff');
        $r->headers->set('Cross-Origin-Resource-Policy', 'same-site');
        // Optional: CORP/CORB hardening
        $r->headers->set('Content-Type', $r->headers->get('Content-Type') ?: 'application/json; charset=utf-8');
    }
}
Enter fullscreen mode Exit fullscreen mode

Safer pattern #3: Avoid GET for sensitive data + CSRF

Return sensitive data via POST and require a CSRF token.

# config/routes.yaml
api_secure_profile:
  path: /api/secure/profile
  controller: App\Controller\SecureController::profile
  methods: [POST]
Enter fullscreen mode Exit fullscreen mode
// src/Controller/SecureController.php
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

public function __construct(private CsrfTokenManagerInterface $csrf) {}

public function profile(Request $req): JsonResponse {
    $token = $req->request->get('_token');
    if (!$this->csrf->isTokenValid(new CsrfToken('api_profile', $token))) {
        return $this->json(['error' => 'invalid_csrf'], 403);
    }
    return $this->json(['ok' => true]);
}
Enter fullscreen mode Exit fullscreen mode

📸 Sample assessment report from our tool to check Website Vulnerability:

Sample vulnerability assessment report generated with our free tool, providing insights into possible vulnerabilities.Sample vulnerability assessment report generated with our free tool, providing insights into possible vulnerabilities.


Bonus hardening checklist

  • Disable JSONP and never return executable JS from APIs.
  • Cookies: use SameSite=Lax or Strict, HttpOnly, Secure.
  • CORS: only allow trusted origins; avoid Access-Control-Allow-Origin: * with credentials.
  • CSP on your pages: script-src 'self' (reduces risky third-party scripts).
  • Audit endpoints with our free tool for a website security test.

Services from Pentest Testing Corp.


Newsletter: Subscribe on LinkedIn

Read more on our blog → https://www.pentesttesting.com/blog/.

Top comments (0)