An SQL injection occurs when an attacker manages to insert arbitrary SQL code into a query meant for the database, exploiting a form field, a URL parameter, or any other entry point whose content is directly inserted into the query without precaution. This flaw, known for decades, remains one of the most frequently exploited on the web, particularly on custom PHP builds where the SQL query is constructed manually rather than through a framework that enforces default protection.

What makes a query vulnerable

The problem arises as soon as a value provided by the user is directly concatenated into a string forming the SQL query. If a search field accepts any text and that text is inserted as is into the query, a malicious visitor can enter a value that alters the logical structure of the query itself, rather than simply supplying data to search for. This manipulation can allow bypassing authentication, extracting data the user should not have access to, or in the most severe cases, modifying or deleting data in the database.

Why manual escaping is not enough

A common reaction to this risk is attempting to manually clean or escape special characters before inserting them into the query, for instance escaping apostrophes. This approach remains fragile: it depends on the developer remembering and being rigorous at every entry point of the site, and a single oversight is enough to reopen the flaw. It can also prove incomplete depending on how the data is used within the query, which makes it an uncertain protection rather than a robust solution.

Prepared statements, a strict separation between code and data

Prepared statements, natively available in PHP through the PDO or MySQLi extensions, solve this problem at its root by completely separating the SQL query's structure from the values inserted into it. The query is first sent to the database in a fixed form, with placeholders reserved for the variable values, then those values are transmitted separately and can never be interpreted as SQL code, regardless of their content. An attacker can enter any string, including complete SQL statements, and those characters will still be treated as plain text data with no effect on the query's structure.

A concrete example of the difference

A query built through direct concatenation typically looks like a string assembled with the user variable inserted in the middle of the SQL text. A prepared statement with PDO separates this logic: the query contains a placeholder, usually represented by a question mark or a name preceded by a colon, and the actual value is supplied separately at execution time, through a dedicated method of the connection object. This structural difference, once understood, does not require more lines of code than manual concatenation, contrary to a sometimes widespread belief.

What prepared statements do not cover

Prepared statements specifically protect against SQL injection, but do not exempt developers from other application security best practices, such as validating a data's expected format before even sending it to the database, or properly escaping data displayed on an HTML page to guard against another type of flaw called script injection. A form accepting an email should, for instance, verify the data resembles an email before processing, independently of the protection provided by the prepared statement itself.

The case of ORMs and modern frameworks

Common PHP frameworks and ORM libraries use prepared statements internally by default, which automatically protects the developer as long as they use the methods provided by these tools rather than manually building a raw query within those same frameworks. On an entirely custom build without a framework, the responsibility for systematically using prepared statements rests entirely on the coding habits adopted from the start of the project, which makes it all the more important to treat it as a non negotiable rule rather than an occasional option.

Points to verify before publishing: confirm that the description of how PDO and MySQLi work remains accurate for PHP versions currently under active support, and check that the distinction between SQL injection and script injection correctly reflects distinct protection mechanisms, without conflating them in the wording.