If a visitor can put text on your page that other visitors will see, and that text is not escaped, they can put a script there instead. It runs as your site, with your visitor's session, and can do anything that visitor can do.
Where it comes from
- A comment, a name, a review - anything typed by one person and shown to another.
- A URL parameter printed back onto the page, which is the search box on almost every site.
- An uploaded file name.
- Data from an API you do not control.
The habit
Escape on OUTPUT, every time, in the context you are printing into. Not on input - the same data may be printed into HTML, into an attribute and into JavaScript, and each needs different escaping.
echo htmlspecialchars(, ENT_QUOTES, 'UTF-8');
- In HTML text - htmlspecialchars with ENT_QUOTES.
- In an attribute - the same, and always quote the attribute.
- In JavaScript - json_encode, never string concatenation.
- In a URL - urlencode.
A second layer
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
CSP tells the browser to run scripts only from your own origin, so an injected inline script does not execute even if one gets through. Introduce it in report-only mode first, or you will break your own page.
Test it
Put a harmless marker in every field and look at the SOURCE of the page that shows it. Seeing your marker as text is right; seeing it as a tag is the bug.