Question

How to Sanitize User Input in PHP for SQL Injection and XSS

phpsecurityxsssql-injectionuser-input

Question

I want to sanitize user input in PHP in a way that protects against both SQL injection and XSS attacks. Is there a single catch-all function that can handle both problems effectively while still allowing certain HTML tags in the submitted content?

Short Answer

By the end of this page, you will understand why there is no single PHP function that safely handles all kinds of user input. You will learn the difference between validation, sanitization, SQL injection prevention, and XSS prevention, and how to correctly combine tools such as prepared statements, output escaping, and HTML allow-list filtering.

Concept

There is no single catch-all function in PHP that can safely solve SQL injection, XSS, and allowed HTML filtering at the same time.

These are different problems, and each requires a different solution.

The three separate concerns

1. SQL injection prevention

SQL injection happens when user input is mixed directly into an SQL query string.

The correct solution is:

  • Use prepared statements
  • Bind parameters
  • Do not manually escape SQL unless absolutely necessary

In modern PHP, this usually means using PDO or MySQLi prepared statements.

2. XSS prevention

XSS happens when unsafe content is displayed in HTML and the browser interprets it as code.

The correct solution is:

  • Escape output when rendering HTML
  • In PHP, this usually means htmlspecialchars()

Important: XSS prevention is generally an output problem, not an input problem.

3. Allowing some HTML tags

Sometimes you want to allow formatting tags such as <b>, <i>, or <p>.

This is more complex than simply stripping tags. If you allow HTML, you must make sure:

  • only approved tags are allowed
  • dangerous attributes like onclick are removed
  • dangerous URLs such as javascript: are blocked

For this, developers often use a dedicated HTML sanitizer such as HTML Purifier.

Why this matters

In real applications, user input can be used in many contexts:

  • SQL queries
  • HTML pages
  • JavaScript strings
  • JSON responses
  • URLs
  • email templates

A string that is safe in one context may be unsafe in another. That is why secure programming uses context-specific handling instead of one global sanitize function.

Key rule

A good mental rule is:

  • Validate input based on what your application expects
  • Use prepared statements for database queries
  • Escape output based on where the data is displayed
  • Sanitize HTML with an allow-list tool only when HTML input is intentionally allowed

Mental Model

Think of user input like a package arriving at a building.

  • Validation checks whether the package is the right type: is this really an email, age, or comment?
  • Prepared statements are like a secure loading dock for the database: the package contents cannot secretly rewrite the delivery instructions.
  • Output escaping is like wrapping dangerous items before bringing them into a room: the browser sees text, not executable code.
  • HTML sanitizing is like a security checkpoint that allows only approved items through, such as <b> and <p>, while removing dangerous ones.

The important lesson is that one security guard cannot do every job. Each step protects a different part of the system.

Take Quiz

Syntax and Examples

1. Prevent SQL injection with prepared statements

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');

$name = $_POST['name'] ?? '';

$stmt = $pdo->prepare('SELECT * FROM users WHERE name = :name');
$stmt->execute(['name' => $name]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

Here, :name is a placeholder. The input is sent separately from the SQL command, so it cannot change the query structure.

2. Prevent XSS with output escaping

<?php
$comment = $_POST['comment'] ?? '';

echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');

If the user submits:

<script>alert('xss')</script>

The browser will display it as text instead of running it.

3. Allow only plain text by stripping tags

<?php
$comment = $_POST['comment'] ?? '';
$plainTextComment = strip_tags($comment);

This removes HTML tags, but it is not enough when you want to safely allow some HTML.

4. Allow some HTML with a dedicated sanitizer

Using HTML Purifier:

<?php
require_once 'vendor/autoload.php';

$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,b,strong,i,em,a[href],ul,ol,li,br');

$purifier = new HTMLPurifier($config);
$safeHtml = $purifier->purify($_POST['comment'] ?? '');

echo $safeHtml;

This is much safer than using strip_tags() alone because it can also filter dangerous attributes and malformed HTML.

5. Validation example

<?php
$email = $_POST['email'] ?? '';

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo 'Invalid email address';
}

Validation checks whether the input matches your expected format. It does not replace SQL protection or XSS protection.

Step by Step Execution

Consider this example:

<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');

$comment = $_POST['comment'] ?? '';

$stmt = $pdo->prepare('INSERT INTO comments (body) VALUES (:body)');
$stmt->execute(['body' => $comment]);

echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');

What happens step by step

1. Read input

$comment = $_POST['comment'] ?? '';
  • If the form sent comment, its value is used.
  • Otherwise, an empty string is used.

Example input:

Hello <script>alert('bad')</script>

2. Prepare SQL safely

$stmt = $pdo->prepare('INSERT INTO comments (body) VALUES (:body)');
  • PHP creates a query template.
  • :body is a placeholder, not raw SQL text.

3. Execute with bound data

$stmt->execute(['body' => $comment]);
  • The comment is passed as data.
  • Even if it contains quotes or SQL-like text, it does not become part of the SQL command itself.

4. Escape before output

echo htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
  • < becomes &lt;
  • > becomes &gt;
  • quotes are also escaped
  • the browser displays the text instead of executing it

Result

Stored in the database:

Hello <script>alert('bad')</script>

Displayed in the browser:

Hello &lt;script&gt;alert(&#039;bad&#039;)&lt;/script&gt;

This shows a common secure pattern:

  • store raw or validated content
  • use prepared statements for the database
  • escape when outputting to HTML

Real World Use Cases

Comment systems

Users submit comments that may contain text, punctuation, quotes, and sometimes limited formatting. A typical approach is:

  • validate comment length
  • store using prepared statements
  • escape output for plain text comments
  • or sanitize with an HTML allow-list for rich text comments

Blog or CMS editors

An admin or trusted user may be allowed to use tags like <p>, <h2>, and <a>. In this case:

  • use a dedicated HTML sanitizer
  • restrict tags and attributes
  • reject dangerous protocols and event handlers

Search forms

A search term should not be inserted directly into SQL. Instead:

  • pass the search text as a bound parameter
  • escape the search text when redisplaying it in the page

Profile fields

Fields such as username, bio, and location all need different handling:

  • username: validate allowed characters
  • bio: maybe allow limited formatting
  • location: often plain text only

APIs and admin panels

Even internal tools need safe handling. Admin users can accidentally paste unsafe HTML or malformed data. Security rules should still apply.

Take Quiz

Real Codebase Usage

In real PHP projects, developers usually do not create one giant sanitize() function for everything. Instead, they use a few clear patterns.

1. Validate early

Developers check input as soon as it enters the application.

<?php
$title = trim($_POST['title'] ?? '');

if ($title === '') {
    throw new InvalidArgumentException('Title is required.');
}

This is often called validation at the boundary.

2. Use guard clauses

Guard clauses stop bad input early.

<?php
$age = $_POST['age'] ?? null;

if (!filter_var($age, FILTER_VALIDATE_INT)) {
    die('Age must be a valid integer.');
}

3. Store data safely, then escape on output

A common pattern is:

  • validate input shape
  • store with prepared statements
  • escape when rendering in HTML templates
<?php
function e(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

Then in templates:

<p><?= e($user['name']) ?></p>

4. Use dedicated sanitizers for rich text

For user-generated HTML, real projects often use libraries instead of hand-written regex or strip_tags().

5. Separate rules by field type

A real form may have different rules for each field:

  • email: validate as email
  • age: validate as integer
  • comment: plain text or sanitized HTML
  • user_id: integer only

6. Centralize helpers, not all logic

Many codebases create small helpers:

  • db() for database access
  • e() for HTML escaping
  • validation functions for common field types
  • a service for rich text sanitization

This keeps security consistent without pretending one function solves every context.

Common Mistakes

Mistake 1: Using one function for every security problem

Broken idea:

<?php
$input = addslashes($_POST['input']);

Why it is wrong:

  • addslashes() is not a complete SQL injection defense
  • it does not solve XSS
  • it depends on database context and is easy to misuse

Use instead:

  • prepared statements for SQL
  • htmlspecialchars() for HTML output

Mistake 2: Escaping input before storing it

Broken code:

<?php
$comment = htmlspecialchars($_POST['comment'], ENT_QUOTES, 'UTF-8');
// store $comment in database

Why it is a problem:

  • you mix storage with presentation
  • the data is now HTML-specific
  • output in another context becomes awkward or wrong

Better approach:

  • store raw or validated/sanitized data
  • escape when rendering

Mistake 3: Relying on strip_tags() for safe HTML

Broken assumption:

<?php
$safe = strip_tags($html, '<a><p><b>');

Why it is risky:

  • allowed tags may still contain dangerous attributes
  • malformed HTML can create unexpected results
  • it is not a full HTML sanitizer

Use a dedicated allow-list sanitizer for HTML input.

Mistake 4: Building SQL with string concatenation

Broken code:

<?php
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";

Why it is dangerous:

  • attackers can inject SQL fragments
  • even numeric-looking fields should be treated carefully

Safer version:

<?php
$id = $_GET['id'] ?? '';
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);

Mistake 5: Forgetting the output context

htmlspecialchars() is for HTML text output. It is not the correct solution for every place.

Examples of other contexts:

  • JavaScript strings
  • URL parameters
  • JSON
  • HTML attributes

Always escape for the context where the data is being used.

Mistake 6: Trusting admin input completely

Even trusted users can paste unsafe content accidentally. If HTML is allowed, sanitize it consistently.

Comparisons

ProblemWrong or weak approachBetter approachWhy
SQL injectionaddslashes() or string concatenationPrepared statementsSeparates SQL code from user data
XSS in HTML outputTrying to clean all input globallyhtmlspecialchars() on outputEscapes data for the browser context
Allowing some HTMLstrip_tags() onlyHTML Purifier or similar allow-list sanitizerRemoves dangerous tags and attributes safely
Checking input formatBlindly accepting everythingValidation with filter_var(), regex, or custom rulesEnsures input matches expected shape

htmlspecialchars() vs strip_tags()

FunctionPurposeGood forNot enough for
htmlspecialchars()Escapes characters for HTML outputPreventing XSS in plain text outputAllowing safe HTML
strip_tags()Removes HTML tagsConverting input to plain textFull HTML sanitization

Prepared statements vs manual escaping

ApproachRecommendationNotes
Prepared statementsPreferredSafe, standard, easier to maintain
Manual SQL escapingAvoid when possibleEasy to get wrong and database-specific

Input validation vs output escaping

ConceptWhen it happensExample
ValidationBefore processing or savingCheck whether email format is valid
Output escapingRight before displayEscape <script> so it shows as text
Take Quiz

Cheat Sheet

Core rules

  • There is no single catch-all sanitize function for all security problems.
  • Use prepared statements to prevent SQL injection.
  • Use htmlspecialchars() to prevent XSS in HTML output.
  • Use validation to make sure input matches expected formats.
  • If you want to allow some HTML, use an allow-list HTML sanitizer such as HTML Purifier.

Safe patterns

SQL query

$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);

HTML output

echo htmlspecialchars($text, ENT_QUOTES, 'UTF-8');

Email validation

filter_var($email, FILTER_VALIDATE_EMAIL)

Plain text only

$plain = strip_tags($input);

Important reminders

  • Do not use addslashes() as SQL protection.
  • Do not escape everything before saving to the database.
  • Do not assume strip_tags() makes HTML safe.
  • Do not build SQL queries by concatenating user input.
  • Escape based on output context.

Handy helper

function e(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

If HTML must be allowed

  • allow only required tags
  • allow only safe attributes
  • block dangerous protocols like javascript:
  • use a proven sanitizer library

FAQ

Is there a single PHP function to prevent both SQL injection and XSS?

No. SQL injection and XSS are different problems. Use prepared statements for SQL and output escaping for HTML.

Should I sanitize input before saving it to the database?

Usually, validate it before saving, but do not HTML-escape everything before storage. Escape when outputting the data.

Is htmlspecialchars() enough to stop XSS?

It is the standard solution for plain text in HTML output. If you want to allow HTML, use a proper HTML sanitizer instead.

Is strip_tags() safe for allowing a few tags?

Not by itself. It can remove tags, but it does not fully protect against dangerous attributes or complex HTML payloads.

What should I use in PHP for SQL injection prevention?

Use PDO or MySQLi prepared statements with bound parameters.

When should I use filter_var()?

Use it for validation, such as checking emails, integers, and URLs. It does not replace SQL protection or XSS protection.

Can I trust admin or logged-in users more?

You can trust them more for permissions, but their input should still be handled safely. Mistakes and unsafe pasted content can still happen.

Take Quiz

Mini Project

Description

Build a simple PHP comment form that accepts a user's name and comment, stores the data safely, and displays it without allowing XSS. The project demonstrates the correct separation of concerns: validation for expected input, prepared statements for database safety, and output escaping for HTML rendering.

Goal

Create a secure comment submission flow in PHP that prevents SQL injection and safely displays user content.

Requirements

  • Accept a name and a comment from an HTML form.
  • Reject empty values after trimming whitespace.
  • Insert the comment into a database using a prepared statement.
  • Display saved comments using safe HTML escaping.
  • Keep comments as plain text only for this version.
Take Quiz

Keep learning

Related questions

View more