I echo $_GET['q'] and comments from my database straight into HTML. Someone injected JavaScript in a comment. What is the correct escaping in PHP, and does it differ for attributes, JavaScript and URLs?
DEV3 Developer Forum
How do I prevent XSS when printing user input in PHP?
Every post is reviewed first. Usually reviewed within 24 hours.
1 reply
XSS happens when untrusted data is placed into a page without being encoded for the context it lands in. The rule is: escape on output, for the exact context.
HTML body and quoted attributes
function e(?string $s): string
{
return htmlspecialchars($s ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
<p>Search results for <?= e($_GET['q'] ?? '') ?></p>
<input type="text" name="q" value="<?= e($q) ?>">
Always pass ENT_QUOTES (so single quotes are encoded) and the charset. Since PHP 8.1 the default flags include ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, but being explicit keeps older servers safe. Always quote attribute values.
URLs
<a href="/search?q=<?= urlencode($q) ?>">...</a>
and validate scheme for user-provided links: allow only http/https, reject script-style schemes such as the javascript pseudo-protocol.
$scheme = parse_url($url, PHP_URL_SCHEME);
if (!in_array($scheme, ['http', 'https'], true)) { $url = '#'; }
JavaScript context
Never concatenate into script text. Pass data as JSON:
$json = json_encode($user, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_THROW_ON_ERROR);
// print it inside an inline script as: const user = <?= $json ?>;
Allowing some HTML (comments, rich text)
htmlspecialchars removes all formatting. For rich text use a sanitizer library like HTMLPurifier with an allow list, not strip_tags() or regex.
Defence in depth
- Content-Security-Policy header limiting scripts (a nonce-based policy; see the CSP headers generator).
- Session cookie with
HttpOnly, so scripts cannot read it. - Template engines such as Twig and Blade escape by default; raw output (
|raw,{!! !!}) is the dangerous path.
Pitfalls
- Do not escape on input and store the escaped text: you will double-escape and the data is wrong in other contexts (JSON, email, PDF).
addslashes,mysqli_real_escape_stringandstrip_tagsdo not prevent XSS.htmlentitiesis for rare cases;htmlspecialcharsis the right default.