Prevent SQL Injection in PHP PDO

SQL injection happens when external data is joined directly into an SQL statement and changes the intended query structure. In PDO, the main protection is to use prepared statements with placeholders instead of concatenating input into SQL.

<?php
require 'config.php';

$id=filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT
);

if($id===false || $id===null){
    exit('Invalid student ID.');
}

$sql="SELECT id,name,class,mark
      FROM student
      WHERE id=:id";

$stmt=$dbo->prepare($sql);

$stmt->bindValue(
    ':id',
    $id,
    PDO::PARAM_INT
);

$stmt->execute();

$row=$stmt->fetch(PDO::FETCH_ASSOC);

The SELECT statement and WHERE condition remain fixed while the value is supplied separately.

Unsafe Query Built from User Input Top ↑

Suppose a student ID is received from the query string:

ID value passed through URL query string
$id=$_GET['id'] ?? '';

$sql="SELECT id,name,class,mark
      FROM student
      WHERE id=$id";

This is unsafe because the value of $id becomes part of the SQL text itself.

Do not build SQL statements by directly inserting values received from GET, POST, cookies, sessions or other external sources.

How SQL Injection Changes Query Logic Top ↑

A simple way to understand SQL injection is to look at a text value placed directly into a WHERE condition.

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

$sql="SELECT id,name
      FROM student
      WHERE name='$name'";

If specially constructed input changes the condition, the final SQL can become logically different from what the programmer intended.

SELECT id,name
FROM student
WHERE name=''
OR '1'='1'

Because the second condition is always true, the query can return records that were never intended to be selected.

The SQL section has a broader introduction to SQL security and safe query design.

Prevent SQL Injection with a Prepared Statement Top ↑

With a prepared statement, the SQL structure contains a placeholder and the value is sent separately.

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

$sql="SELECT id,name,class,mark
      FROM student
      WHERE name=:name";

$stmt=$dbo->prepare($sql);

$stmt->bindValue(
    ':name',
    $name,
    PDO::PARAM_STR
);

$stmt->execute();

while($row=$stmt->fetch(PDO::FETCH_ASSOC)){
    echo (int)$row['id'].' - ';

    echo htmlspecialchars(
        (string)$row['name'],
        ENT_QUOTES,
        'Windows-1252'
    ).'<br>';
}

The supplied value is treated as data rather than being parsed as part of the SQL syntax.

bindValue() and bindParam() Top ↑

Both methods can bind parameters. When the value is already available, bindValue() is usually straightforward:

$stmt->bindValue(
    ':id',
    $id,
    PDO::PARAM_INT
);

bindParam() binds a variable by reference, which is useful when the variable value may change before execution.

Validate Integer Input before Binding Top ↑

Prepared statements protect the SQL structure, but input should still be validated according to the application's rules.

$id=filter_input(
    INPUT_GET,
    'id',
    FILTER_VALIDATE_INT,
    [
        'options' => [
            'min_range' => 1
        ]
    ]
);

if($id===false || $id===null){
    exit('Invalid student ID.');
}

Then bind the validated integer:

$stmt->bindValue(
    ':id',
    $id,
    PDO::PARAM_INT
);

Use Prepared Statements for String Values Top ↑

String input should also use placeholders rather than being quoted and concatenated manually.

$class=trim($_GET['class'] ?? '');

$sql="SELECT id,name,class,mark
      FROM student
      WHERE class=:class";

$stmt=$dbo->prepare($sql);

$stmt->bindValue(
    ':class',
    $class,
    PDO::PARAM_STR
);

$stmt->execute();

Safe Login-Style Query with PDO Top ↑

Authentication queries are especially sensitive. Do not build a login query by placing the entered userid and password directly into SQL.

A safer pattern is to retrieve the account by userid using a prepared statement:

$userid=trim($_POST['userid'] ?? '');
$password=$_POST['password'] ?? '';

$sql="SELECT id,userid,password,name,status
      FROM pdo_admin
      WHERE userid=:userid";

$stmt=$dbo->prepare($sql);

$stmt->bindValue(
    ':userid',
    $userid,
    PDO::PARAM_STR
);

$stmt->execute();

$user=$stmt->fetch(PDO::FETCH_ASSOC);

if(
    $user &&
    password_verify(
        $password,
        $user['password']
    )
){
    echo 'Login successful.';
}else{
    echo 'Invalid login details.';
}
Store passwords using password_hash() and verify them with password_verify(). Do not store or compare plaintext passwords in new applications.

Placeholders Cannot Replace Table or Column Names Top ↑

PDO placeholders are for data values. They cannot normally be used to bind SQL identifiers such as table names, column names or keywords.

This does not work as intended:

$sql="SELECT :column
      FROM student";

If a column or sort direction must be selected dynamically, use an allowlist:

$allowed_columns=[
    'name',
    'class',
    'mark'
];

$column=$_GET['sort'] ?? 'name';

if(
    !in_array(
        $column,
        $allowed_columns,
        true
    )
){
    $column='name';
}

$sql="SELECT id,name,class,mark
      FROM student
      ORDER BY $column";

$stmt=$dbo->query($sql);

Here the SQL identifier comes only from a predefined list controlled by the application.

Injected Multiple Statements and DROP TABLE Top ↑

Older SQL injection examples often show input containing a second statement such as:

10; DROP TABLE student

Whether multiple statements can actually execute through one PDO call depends on the driver and connection configuration. Therefore, this should not be used as the main explanation of SQL injection.

The core problem exists even when a second statement cannot run: direct concatenation can still change a WHERE condition, reveal unintended rows, bypass application logic or alter other database operations.

For the SQL command itself, see DROP TABLE.

Prepared Statements Do Not Replace Validation Top ↑

Prepared statements and validation solve different problems:

  • Prepared statement: keeps supplied values separate from SQL syntax.
  • Validation: checks whether the value is acceptable for the application.
  • Output escaping: protects values when they are later displayed in HTML.

For example, a prepared statement can safely bind the integer -500, but the application may still reject it if a student ID must be positive.

Legacy Injection Demonstration Form Top ↑

The existing Plus2net demonstration page can be used to study how unsafe login-style input changes query logic.

This form is provided for understanding the tutorial's injection demonstration. New application code should use prepared statements and hashed passwords as shown above.

Common SQL Injection Prevention Mistakes Top ↑

Concatenating External Values into SQL Top ↑

Avoid code such as:

$sql="SELECT id,name
      FROM student
      WHERE id=$id";

Use a placeholder instead.

Escaping Values Instead of Using PDO Parameters Top ↑

When using PDO prepared statements, do not switch to MySQLi escaping functions. Supply the value through the PDO parameter interface.

Assuming Validation Alone Stops SQL Injection Top ↑

Validation is important, but it should not replace parameterized queries. Use both where appropriate.

Trying to Bind a Column Name Top ↑

Placeholders are for data values, not SQL identifiers. Use an allowlist for dynamic columns, table names or sort directions.

Displaying Passwords or Sensitive Fields Top ↑

Do not output password hashes or authentication credentials while demonstrating database records.

Showing Raw Database Errors Top ↑

Database errors can reveal SQL structure, table names and other internal details. Log technical errors and show a generic public message.

Assuming Prepared Statements Fix Every Security Problem Top ↑

Prepared statements protect query structure for bound values. Authentication, authorization, CSRF protection, validation, output escaping and password handling are separate security concerns.

PDO Errors PDO Transactions PDO fetch()

PDO Records PDO Connection SQL Security

Download the complete PDO examples from the main PDO tutorial.

Sample Student Table SQL Dump

Frequently Asked Questions Top ↑

Q1: What is SQL injection?

SQL injection occurs when supplied data becomes part of the SQL structure and changes the intended database query.

Q2: How does PDO prevent SQL injection?

PDO prepared statements use placeholders so supplied values are sent separately from the SQL statement and treated as data rather than SQL syntax.

Q3: Should I validate input if I already use prepared statements?

Yes. Prepared statements protect SQL structure, while validation checks whether supplied values satisfy the application's rules.

Q4: Can PDO placeholders be used for table or column names?

No. Placeholders are intended for data values. Dynamic SQL identifiers should be selected from an application-controlled allowlist.

Q5: Is bindValue() safer than bindParam()?

Both can safely bind values to placeholders. bindValue() supplies the current value directly, while bindParam() binds a variable by reference.

Q6: Should passwords be included directly in a login SQL query?

No. Retrieve the account using a prepared userid query and verify a stored password hash using password_verify().

Q7: Do prepared statements provide complete application security?

No. They protect SQL query structure for bound values, but applications still need validation, authentication, authorization, safe output, CSRF protection where appropriate and secure password handling.


PDO Errors PDO Transactions


Subscribe to our YouTube Channel here



plus2net.com











PHP video Tutorials
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
©2000-2026   plus2net.com   All rights reserved worldwide Privacy Policy Disclaimer