If user input is inserted into a SQL query without modification, the application becomes vulnerable to SQL injection attacks, as shown in the following example:
$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
This is because the user can enter something like value'); DROP TABLE table;--
and the query will become:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
How can I prevent this from happening?
To use parameterized queries, you need to use Mysqli or PDO. To rewrite your example using mysqli, we would need code similar to the following.
The key function you may want to read about is
mysqli::prepare
.Also, as others have suggested, you may find it more useful/easier to use a higher level abstraction layer likePDO.
Please note that the situation you mention is quite simple, more complex situations may require more sophisticated methods. in particular:
mysql_real_escape_string
. In this case, it's better to pass the user's input through a whitelist to ensure that only "safe" values are allowed through.No matter which database you use, thecorrectway to avoid SQL injection attacks is toseparate the data from the SQLso that the data remains in the form of data andcan never be used by SQL The parser interpretsas a command. It is possible to create SQL statements with properly formatted data parts, but if you don'tfullyunderstand the details, you should alwaysuse prepared statements and parameterized queries. These are SQL statements that are sent separately from any parameters and analyzed by the database server. This way, attackers cannot inject malicious SQL.
There are basically two ways to achieve this:
UsingPDO(works with any supported database driver):
UsingMySQLi(for MySQL):
Since PHP 8.2, we can use the
execute_query()
method to prepare, bind parameters and execute SQL statements:Before PHP8.1:
If you are connecting to a database other than MySQL, you can refer to the second driver-specific option (for example, for PostgreSQL, you can use
pg_prepare()
andpg_execute()
). PDO is a universal option.Set up the connection correctly
PDO
Please note that when usingPDOto access a MySQL database, realprepared statements are not used by default. To resolve this issue, you need to disable simulation of prepared statements. The following is an example of creating a connection usingPDO:
In the example above, error mode is not strictly required,but it is recommended to add it. This way PDO will notify you of all MySQL errors by throwing
PDOException
.However, whatmustis the first line
setAttribute()
, which tells PDO to disable simulated prepared statements and userealprepared statements. This ensures that statements and values are not parsed by PHP before being sent to the MySQL server (so potential attackers cannot inject malicious SQL).While you can set the
charset
in the constructor's options, it is important to note that "older" versions of PHP (prior to 5.3.6)silently ignore the charset argument in the DSN.Mysqli
For mysqli, we need to follow the same routine:
explain
The SQL statements you pass to
prepare
are parsed and compiled by the database server. By specifying a parameter (in the example above, this could be?
or a named parameter like:name
), you tell the database engine where you want to filter. Then, when you callexecute
, the prepared statement is combined with the specified parameter values.What is important here is the combination of parameter values and compiled statements, not the combination with SQL strings. SQL injection works by tricking a script into including a malicious string when creating the SQL to be sent to the database. Therefore, by sending the actual SQL separately from the parameters, you limit the risk of unexpected results.
Any parameters sent using prepared statements will be treated as strings (although the database engine may perform some optimizations on parameters, so parameters may end up being numbers). In the example above, if the
$name
variable contains'Sarah'; DELETE FROM employees
, the result will only be the search string"'Sarah'; DELETE FROM employees"
, you will not getan empty table.Another benefit of using prepared statements is that if the same statement is executed multiple times in the same session, it will only be parsed and compiled once, thus improving some speed.
Oh, since you asked how to operate on inserts, here is an example (using PDO):
Are prepared statements suitable for dynamic queries?
While you can still use prepared statements for query parameters, the structure of the dynamic query itself cannot be parameterized, and certain query functions cannot be parameterized.
For these specific scenarios, best practice is to use a whitelist filter to limit the possible values.