我怎样才能防止在PHP中的SQL注入?

如果用户input未经修改而插入到SQL查询中,则该应用程序易受SQL注入的影响 ,如下例所示:

$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')"); 

这是因为用户可以input类似的value'); DROP TABLE table;-- value'); DROP TABLE table;-- ,查询变成:

 INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--') 

可以做些什么来防止这种情况发生?

使用预准备语句和参数化查询。 这些是由数据库服务器独立于任何参数发送并parsing的SQL语句。 这样攻击者不可能注入恶意的SQL。

你基本上有两个select来实现这一点:

  1. 使用PDO (用于任何支持的数据库驱动程序):

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute(array('name' => $name)); foreach ($stmt as $row) { // do something with $row } 
  2. 使用MySQLi (用于MySQL):

     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // do something with $row } 

如果连接到MySQL以外的数据库,那么可以引用一个驱动程序特定的第二个选项(例如PostgreSQL的pg_prepare()pg_execute() )。 PDO是普遍的select。

正确设置连接

请注意,使用PDO访问MySQL数据库时,默认情况下不使用 实际准备的语句。 要解决这个问题,你必须禁用已准备好的语句的模拟。 使用PDO创build连接的示例是:

 $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass'); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 

在上面的例子中,错误模式并不是绝对必要的, 但build议添加它 。 当出现Fatal Error时,脚本不会因Fatal Error而停止。 它使开发人员有机会catch throw n作为PDOException s的任何错误。

然而,第一个setAttribute()行是必须的 ,它告诉PDO禁用模拟的预处理语句并使用实际准备的语句。 这样可以确保语句和值在发送到MySQL服务器之前不会被PHPparsing(让攻击者没有机会注入恶意SQL)。

尽pipe可以在构造函数的选项中设置charset ,但是需要注意的是“较旧”版本的PHP(<5.3.6) 默默地忽略了DSN中的字符集参数 。

说明

会发生什么是您传递给prepare的SQL语句由数据库服务器parsing和编译。 通过指定参数(在上面的例子中是一个?或者一个名为参数的:name ),你可以告诉数据库引擎你要过滤的地方。 然后,当您调用execute ,准备的语句将与您指定的参数值组合在一起。

这里最重要的是参数值与编译语句结合在一起,而不是一个SQLstring。 SQL注入的工作原理是在创buildSQL发送到数据库时,欺骗脚本使其包含恶意string。 所以通过从参数中分别发送实际的SQL,可以限制结束于你不想要的事情的风险。 在使用预处理语句时,您发送的任何参数都将被视为string(尽pipe数据库引擎可能会进行一些优化,因此参数最终也会以数字结尾)。 在上面的例子中,如果$namevariables包含'Sarah'; DELETE FROM employees 'Sarah'; DELETE FROM employees的结果只是searchstring"'Sarah'; DELETE FROM employees" ,你不会结束了一个空的表 。

使用预处理语句的另一个好处是,如果在同一个会话中多次执行相同的语句,它只会被parsing和编译一次,从而提高了速度。

哦,既然你问过如何做一个插入,这里是一个例子(使用PDO):

 $preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)'); $preparedStatement->execute(array('column' => $unsafeValue)); 

可以将准备好的语句用于dynamic查询吗?

尽pipe仍然可以使用准备好的语句作为查询参数,但是dynamic查询本身的结构不能被参数化,并且某些查询特征不能被参数化。

对于这些特定场景,最好的办法是使用限制可能值的白名单filter。

 // Value whitelist // $dir can only be 'DESC' otherwise it will be 'ASC' if (empty($dir) || $dir !== 'DESC') { $dir = 'ASC'; } 

警告:该问题的示例代码使用PHP的mysql扩展,该扩展在PHP 5.5.0中被弃用,并在PHP 7.0.0中完全删除。

如果您使用的是最新版本的PHP,下面列出的mysql_real_escape_string选项将不再可用(尽pipemysqli::escape_string是一个现代的等价物)。 现在, mysql_real_escape_string选项只适用于老版本PHP上的旧代码。


您有两个选项 – 转义unsafe_variable的特殊字符或使用参数化查询。 两者都可以保护你免受SQL注入。 参数化查询被认为是更好的做法,但是在使用它之前,需要在PHP中更改为一个更新的mysql扩展。

我们将首先覆盖较低的撞击弦。

 //Connect $unsafe_variable = $_POST["user-input"]; $safe_variable = mysql_real_escape_string($unsafe_variable); mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')"); //Disconnect 

另请参阅mysql_real_escape_string函数的详细信息。

要使用参数化查询,您需要使用MySQLi而不是MySQL函数。 重写你的例子,我们需要类似下面的东西。

 <?php $mysqli = new mysqli("server", "username", "password", "database_name"); // TODO - Check that connection was successful. $unsafe_variable = $_POST["user-input"]; $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)"); // TODO check that $stmt creation succeeded // "s" means the database expects a string $stmt->bind_param("s", $unsafe_variable); $stmt->execute(); $stmt->close(); $mysqli->close(); ?> 

你需要在这里读取的关键函数是mysqli::prepare

另外,正如其他人所build议的那样,您可能会发现使用类似PDO的抽象层可能会更容易/更容易。

请注意,您询问的情况相当简单,而且更复杂的情况可能需要更复杂的方法。 尤其是:

  • 如果你想根据用户input来改变SQL的结构,那么参数化的查询就不会mysql_real_escape_string了, mysql_real_escape_string不会覆盖所需的转义。 在这种情况下,您最好将用户的input通过白名单,以确保只允许“安全”值。
  • 如果您在条件中使用用户input的整数并采用mysql_real_escape_string方法,那么在下面的注释中将会遇到Polynomial描述的问题。 这种情况是棘手的,因为整数不会被引号包围,所以你可以通过validation用户input只包含数字来处理。
  • 有可能我还没有意识到的其他情况。 您可能会发现这是您可以遇到的一些更微妙的问题的有用资源。

这里的每个答案都只涵盖了部分问题。
实际上,我们可以dynamic添加四个不同的查询部分:

  • 一个string
  • 一个号码
  • 一个标识符
  • 一个语法关键字。

准备的报表只包括其中的两个

但有时我们必须使查询更加dynamic,并添加运算符或标识符。
所以我们需要不同的保护技术。

一般来说,这种保护方式是基于白名单的 。 在这种情况下,每个dynamic参数都应该在脚本中进行硬编码,并从该组中进行select。
例如,做dynamicsorting:

 $orders = array("name","price","qty"); //field names $key = array_search($_GET['sort'],$orders)); // see if we have such a name $orderby = $orders[$key]; //if not, first one will be set automatically. smart enuf :) $query = "SELECT * FROM `table` ORDER BY $orderby"; //value is safe 

但是,还有另一种方法来保护标识符 – 转义。 只要你引用了一个标识符,你就可以通过加倍的方式逃脱。

作为一个进一步的步骤,我们可以借用一个真正的好主意,从准备好的语句中使用一些占位符(一个代理来表示查询中的实际值),并发明另一种types的占位符 – 一个标识符占位符。

因此,长话短说:这是一个占位符 ,没有准备好的声明可以被视为银弹。

所以,一般的build议可能会被表述为
只要您使用占位符向查询添加dynamic部分(并且正确处理这些占位符当然),则可以确保您的查询是安全的

尽pipe如此,SQL语法关键字(如ANDDESC等)仍然存在问题,但在这种情况下,白名单似乎是唯一的方法。

更新

尽pipe关于SQL注入保护的最佳实践已经达成了一致,但仍存在许多不好的做法。 其中一些根深蒂固在PHP用户的心中。 例如,在这个页面上(尽pipe大多数访问者不可见) 有80多个被删除的答案 – 由于质量差或者推广不好的和过时的做法而被社区删除。 更糟糕的是,一些不好的答案不会被删除,而是繁荣。

例如, 有(1) (2) 仍然(3) 很多(4) 答案(5) ,其中包括第二个最高的答案,build议你手动string转义 – 一个过时的方法,被certificate是不安全的。

或者有一个更好的答案,暗示了另一种string格式化的方法,甚至把它作为最终的灵丹妙药。 当然,事实并非如此。 这个方法并不比普通的string格式化好,但它保留了所有的缺点:它只适用于string,和其他任何手动格式一样,它本质上是可选的,非强制性的度量,容易出现任何种类的人为错误。

我认为所有这一切都是因为一个非常古老的迷信,在OWASP或PHP手册这样的权威机构的支持下,它宣称无论是“逃跑”还是防止SQL注入都是平等的。

不pipePHP手册说了些什么, *_escape_string绝不会使数据安全 ,也从来没有打算过。 除了string以外的任何SQL部分都是无用的,手动转义是错误的,因为它是手动的,与自动化相反。

而且OWASP更加糟糕,强调逃避用户input ,这完全是无稽之谈:在注入保护的背景下不应该有这样的话。 每个variables都有潜在的危险 – 不pipe来源如何! 或者换句话说,每一个variables都必须被正确地格式化,以便放入一个查询中,而不pipe源再次。 这是重要的目的地。 当开发人员开始将山羊与山羊分离时(考虑某个特定variables是否“安全”),他迈出了第一步,走向了灾难。 更不用说,甚至措辞表明在入口处散装逃脱,类似于非常神奇的报价function – 已被鄙视,弃用和删除。

因此,与“转义”不同,准备好的语句确实可以防止SQL注入的措施(适用时)。

如果你还不确定,下面是我写的一步一步的解释, 即“SQL注入防范黑客入门指南” ,我详细解释了所有这些事情,甚至编写了一个完全致力于不良做法和披露的部分。

我build议使用PDO (PHP数据对象)来运行参数化的SQL查询。

这不仅可以防止SQL注入,还可以加快查询速度。

通过使用PDO而不是mysql_mysqli_pgsql_函数,可以使您的应用程序从数据库中抽象出来,而且在极less数情况下必须切换数据库提供程序。

使用PDO并准备好查询。

$conn是一个PDO对象)

 $stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)"); $stmt->bindValue(':id', $id); $stmt->bindValue(':name', $name); $stmt->execute(); 

正如你所看到的,人们build议你最多使用准备好的语句。 这没有错,但是当你的查询在每个进程中执行一次时 ,就会有轻微的性能损失。

我正面临着这个问题,但我想我已经以非常复杂的方式解决了这个问题 – 黑客用来避免使用引号的方式。 我将这个与模拟的准备语句结合使用。 我用它来防止各种可能的SQL注入攻击。

我的方法是:

  • 如果你期望input是整数,确保它是真正的整数。 在像PHP这样的可变types语言中,这是非常重要的。 sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • 如果你期望从整数hex的其他东西。 如果你hex,你将完全逃避所有的input。 在C / C ++中有一个名为mysql_hex_string()的函数,在PHP中可以使用bin2hex()

    不要担心转义string的长度是其原始长度的((2*input_length)+1) ,因为即使使用mysql_real_escape_string ,PHP也必须分配相同的容量((2*input_length)+1) ,这是相同的。

  • 这种hex方法通常用于传输二进制数据,但我没有理由不使用它来防止SQL注入攻击。 请注意,您必须用0x预先UNHEX数据,或者使用MySQL函数UNHEX

所以,例如,查询:

 SELECT password FROM users WHERE name = 'root' 

会变成:

 SELECT password FROM users WHERE name = 0x726f6f74 

要么

 SELECT password FROM users WHERE name = UNHEX('726f6f74') 

hex是完美的逃生。 没办法注入。

UNHEX函数和0x前缀之间的区别

在评论中有一些讨论,所以我最后想说清楚。 这两种方法非常相似,但在某些方面有所不同:

0x前缀只能用于char,varchar,text,block,binary等数据列。
另外,如果你要插入一个空string,它的使用会有点复杂。 你必须完全用''replace它,否则你会得到一个错误。

UNHEX()适用于任何专栏; 你不必担心空string。


hex方法经常用作攻击

请注意,这个hex方法经常用作SQL注入攻击,其中整数就像string,只是用mysql_real_escape_string转义。 那么你可以避免使用引号。

例如,如果你只是做这样的事情:

 "SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"]) 

攻击可以很容易地注入你。 考虑从脚本返回的以下注入代码:

SELECT … WHERE id = -1 union all select table_name from information_schema.tables

现在只是提取表结构:

SELECT … WHERE id = -1 union all从information_schema.column中selectcolumn_name,其中table_name = 0x61727469636c65

然后只要select任何想要的数据。 不是很酷吗?

但是,如果一个注射网站的编码器将hex,没有注射是可能的,因为查询将如下所示: SELECT ... WHERE id = UNHEX('2d312075...3635')

重要

正如接受的答案所示,防止SQL注入的最好方法是使用Prepared Statements 而不是转义

Aura.Sql和EasyDB等库允许开发人员更容易地使用预处理语句。 要了解更多关于为什么准备语句更好地停止SQL注入 ,请参阅此mysql_real_escape_string()绕过和最近修复WordPress中的Unicode SQL注入漏洞 。

注入预防 – mysql_real_escape_string()

PHP有一个专门的function来防止这些攻击。 所有你需要做的就是使用函数mysql_real_escape_string的满口。

mysql_real_escape_string接受一个将在MySQL查询中使用的string,并返回相同的string,所有SQL注入尝试都能安全地转义。 基本上,它将取代那些用户可能用MySQL安全的替代品(一个逃脱的报价)input的麻烦的引号(')。

注意:您必须连接到数据库才能使用此function!

//连接到MySQL

 $name_bad = "' OR 1'"; $name_bad = mysql_real_escape_string($name_bad); $query_bad = "SELECT * FROM customers WHERE username = '$name_bad'"; echo "Escaped Bad Injection: <br />" . $query_bad . "<br />"; $name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; $name_evil = mysql_real_escape_string($name_evil); $query_evil = "SELECT * FROM customers WHERE username = '$name_evil'"; echo "Escaped Evil Injection: <br />" . $query_evil; 

你可以在MySQL – SQL注入预防中find更多的细节。

安全警告 :这个答案不符合安全最佳实践。 转义不足以防止SQL注入 ,而是使用预处理语句 。 使用下面列出的策略需要您自担风险。 (另外,在PHP 7中删除了mysql_real_escape_string() )。

你可以做这样的基本事情:

 $safe_variable = mysql_real_escape_string($_POST["user-input"]); mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')"); 

这并不能解决所有问题,但这是一个很好的垫脚石。 我遗漏了明显的项目,如检查variables的存在,格式(数字,字母等)。

无论你做什么最终使用,确保你检查你的input还没有被magic_quotes或其他一些善意的垃圾破坏,如果有必要的话,运行它通过stripslashes或任何消毒。

参数化查询和inputvalidation是要走的路。 即使已经使用了mysql_real_escape_string() ,在很多情况下也可能发生SQL注入。

这些示例容易受到SQL注入的影响:

 $offset = isset($_GET['o']) ? $_GET['o'] : 0; $offset = mysql_real_escape_string($offset); RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10"); 

要么

 $order = isset($_GET['o']) ? $_GET['o'] : 'userid'; $order = mysql_real_escape_string($order); RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`"); 

在这两种情况下,你都不能使用'来保护封装。

来源 : 意外的SQL注入(当转义不够)

在我看来,在PHP应用程序(或任何Web应用程序)中通常阻止SQL注入的最佳方法是考虑应用程序的体系结构。 如果防止SQL注入的唯一方法是记住使用一种特殊的方法或函数,在每次与数据库交谈时都做正确的事情,那么你就错了。 这样,直到忘记在代码中的某个时刻正确设置查询的格式,这只是一个时间问题。

采用MVC模式和像CakePHP或CodeIgniter这样的框架可能是正确的select:像创build安全数据库查询这样的常见任务已经在这样的框架中得到解决和集中实现。 它们可以帮助您以合理的方式组织Web应用程序,并使您更多地考虑加载和保存对象,而不是安全地构build单个SQL查询。

有许多方法可以防止SQL注入和其他SQL黑客入侵。 您可以在Internet上轻松find它(Googlesearch)。 当然PDO是最好的解决scheme之一。 但是我想build议你从SQL注入防止一些很好的链接。

什么是SQL注入以及如何防止

用于SQL注入的PHP手册

微软对SQL注入和PHP预防的解释

和其他一些像预防与MySQL和PHP的SQL注入

现在, 为什么你需要阻止来自SQL注入的查询呢?

我想让你知道:为什么我们尝试使用下面的简短示例来防止SQL注入:

查询loginvalidation匹配:

 $query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' "; 

现在,如果有人(黑客)提出

 $_POST['email']= admin@emali.com' OR '1=1 

和密码的东西….

该查询将在系统中parsing到只有:

 $query="select * from users where email='admin@emali.com' OR '1=1'; 

另一部分将被丢弃。 那么,会发生什么? 非授权用户(黑客)将能够以pipe理员身份login,而无需input密码。 现在,他可以做任何pipe理员/电子邮件人员可以做的事情。 看,如果不注意SQL注入是非常危险的。

I favor stored procedures ( MySQL has had stored procedures support since 5.0 ) from a security point of view – the advantages are –

  1. Most databases (including MySQL ) enable user access to be restricted to executing stored procedures. The fine grained security access control is useful to prevent escalation of privileges attacks. This prevents compromised applications from being able to run SQL directly against the database.
  2. They abstract the raw SQL query from the application so less information of the database structure is available to the application. This makes it harder for people to understand the underlying structure of the database and design suitable attacks.
  3. They accept only parameters, so the advantages of parameterized queries are there. Of course – IMO you still need to sanitize your input – especially if you are using dynamic SQL inside the stored procedure.

The disadvantages are –

  1. They (stored procedures) are tough to maintain and tend to multiply very quickly. This makes managing them an issue.
  2. They are not very suitable for dynamic queries – if they are built to accept dynamic code as parameters then a lot of the advantages are negated.

I think if someone wants to use PHP and MySQL or some other dataBase server:

  1. Think about learning PDO (PHP Data Objects) – it is a database access layer providing a uniform method of access to multiple databases.
  2. Think about learning MySQLi
  3. Use native PHP functions like: strip_tags , mysql_real_escape_string or if variable numeric, just (int)$foo . Read more about type of variables in PHP here . If you're using libraries such as PDO or MySQLi, always use PDO::quote() and mysqli_real_escape_string() .

Libraries examples:

—- PDO

—– No placeholders – ripe for SQL injection! It's bad

 $request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)"); 

—– Unnamed placeholders

 $request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?); 

—– Named placeholders

 $request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)"); 

MySQLi

 $request = $mysqliConnection->prepare(' SELECT * FROM trainers WHERE name = ? AND email = ? AND last_login > ?'); $query->bind_param('first_param', 'second_param', $mail, time() - 3600); $query->execute(); 

PS :

PDO wins this battle with ease. With support for twelve different database drivers and named parameters, we can ignore the small performance loss, and get used to its API. From a security standpoint, both of them are safe as long as the developer uses them the way they are supposed to be used

But while both PDO and MySQLi are quite fast, MySQLi performs insignificantly faster in benchmarks – ~2.5% for non-prepared statements, and ~6.5% for prepared ones.

And please test every query to your database – it's a better way to prevent injection.

Type cast if possible your parameters. But it's only working on simple types like int, bool and float.

 $unsafe_variable = $_POST['user_id']; $safe_variable = (int)$unsafe_variable ; mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')"); 

If you want to take advantage of cache engines, like Redis or Memcached , maybe DALMP could be a choice. It uses pure MySQLi . Check this: DALMP Database Abstraction Layer for MySQL using PHP.

Also you can 'prepare' your arguments before preparing your query so that you can build dynamic queries and at the end have a full prepared statements query. DALMP Database Abstraction Layer for MySQL using PHP.

Using this PHP function mysql_escape_string() you can get a good prevention in a fast way.

例如:

 SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' 

mysql_escape_string — Escapes a string for use in a mysql_query

For more prevention you can add at the end …

 wHERE 1=1 or LIMIT 1 

Finally you get:

 SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1 

For those unsure of how to use PDO (coming from the mysql_ functions), I made a very, very simple PDO wrapper that is a single file. It exists to show how easy it is to do all the common things applications need done. Works with PostgreSQL, MySQL, and SQLite.

Basically, read it while you read the manual to see how to put the PDO functions to use in real life to make it simple to store and retrieve values in the format you want.

I want a single column

 $count = DB::column('SELECT COUNT(*) FROM `user`); 

I want an array(key => value) results (ie for making a selectbox)

 $pairs = DB::pairs('SELECT `id`, `username` FROM `user`); 

I want a single row result

 $user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id)); 

I want an array of results

 $banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE)); 

A few guidelines for escaping special characters in SQL statements.

Don't use MySQL , this extension is deprecated, use MySQLi or PDO .

MySQLi

For manually escaping special characters in a string you can use the mysqli_real_escape_string function. The function will not work properly unless the correct character set is set with mysqli_set_charset .

例:

 $mysqli = new mysqli( 'host', 'user', 'password', 'database' ); $mysqli->set_charset( 'charset'); $string = $mysqli->real_escape_string( $string ); $mysqli->query( "INSERT INTO table (column) VALUES ('$string')" ); 

For automatic escaping of values with prepared statements, use mysqli_prepare , and mysqli_stmt_bind_param where types for the corresponding bind variables must be provided for an appropriate conversion:

例:

 $stmt = $mysqli->prepare( "INSERT INTO table ( column1, column2 ) VALUES (?,?)" ); $stmt->bind_param( "is", $integer, $string ); $stmt->execute(); 

No matter if you use prepared statements or mysqli_real_escape_string, you always have to know the type of input data you're working with.

So if you use a prepared statement, you must specify the types of the variables for mysqli_stmt_bind_param function.

And use of mysqli_real_escape_string is for, as the name says, escaping special characters in a string, so it will not make integers safe. The purpose of this function is to prevent breaking the strings in SQL statements, and the damage to the database that it could cause. mysqli_real_escape_string is a useful function when used properly, especially when combined with sprintf.

例:

 $string = "x' OR name LIKE '%John%"; $integer = '5 OR id != 0'; $query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer ); echo $query; // SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5 $integer = '99999999999999999999'; $query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string( $string ), $integer ); echo $query; // SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647 

I use three different ways to prevent my web application from being vulnerable to SQL injection.

  1. Use of mysql_real_escape_string() , which is a pre-defined function in PHP , and this code add backslashes to the following characters: \x00 , \n , \r , \ , ' , " and \x1a . Pass the input values as parameters to minimize the chance of SQL injection.
  2. The most advanced way is to use PDOs.

我希望这会帮助你。

Consider the following query:

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string() will not protect here. If you use single quotes (' ') around your variables inside your query is what protects you against this. Here is an solution below for this:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

This question has some good answers about this.

I suggest, using PDO is the best option.

编辑:

mysql_real_escape_string() is deprecated as of PHP 5.5.0. Use either mysqli or PDO.

An alternative to mysql_real_escape_string() is

 string mysqli_real_escape_string ( mysqli $link , string $escapestr ) 

例:

 $iId = $mysqli->real_escape_string("1 OR 1=1"); $mysqli->query("SELECT * FROM table WHERE id = $iId"); 

The simple alternative to this problem could be solved by granting appropriate permissions in the database itself. For example: if you are using mysql database. then enter into the database through terminal or the ui provided and just follow this command:

  GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password'; 

This will restrict the user to only get confined with the specified query's only. Remove the delete permission and so the data would never get deleted from the query fired from the php page. The second thing to do is to flush the privileges so that the mysql refreshes the permissions and updates.

 FLUSH PRIVILEGES; 

more information about flush .

To see the current privileges for the user fire the following query.

 select * from mysql.user where User='username'; 

Learn more about GRANT .

Regarding many useful answers, I hope to add some values to this thread. SQL injection is an attack that can be done through user inputs (Inputs that filled by user and then used inside queries), The SQL injection patterns are correct query syntax while we can call it: bad queries for bad reasons, we assume that there might be bad person that try to get secret information (bypassing access control) that affect the three principles of security (Confidentiality, Integrity, Availability).

Now, our point is to prevent security threats such as SQL injection attacks, the question asking (How to prevent SQL injection attack using PHP), be more realistic, data filtering or clearing input data is the case when using user-input data inside such query, using PHP or any other programming language is not the case, or as recommended by more people to use modern technology such as prepared statement or any other tools that currently supporting SQL injection prevention, consider that these tools not available anymore? How you secure your application?

My approach against SQL injection is: clearing user-input data before sending it to the database (before using it inside any query).

Data filtering for (Converting unsafe data to safe data) Consider that PDO and MySQLi not available, how can you secure your application? Do you force me to use them? What about other languages other than PHP? I prefer to provide general ideas as it can be used for wider border not just for specific language.

  1. SQL user (limiting user privilege): most common SQL operations are (SELECT, UPDATE, INSERT), then, why giving UPDATE privilege to a user that not require it? For example login, and search pages are only using SELECT, then, why using DB users in these pages with high privileges? RULE: do not create one database user for all privileges, for all SQL operations, you can create your scheme like (deluser, selectuser, updateuser) as usernames for easy usage.

see Principle of least privilege

  1. Data filtering: before building any query user input should be validated and filtered, for programmers, it's important to define some properties for each user-input variables: data type, data pattern, and data length . a field that is a number between (x and y) must be exactly validated using exact rule, for a field that is a string (text): pattern is the case, for example, username must contain only some characters lets say [a-zA-Z0-9_-.] the length varies between (x and n) where x and n (integers, x <=n ). Rule: creating exact filters and validation rules are best practice for me.

  2. Use other tools: Here, I will also agree with you that prepared statement (parametrized query) and Stored procedures, the disadvantages here is these ways requires advanced skills which do not exist for most users, the basic idea here is to distinguish between SQL query and the data that being used inside, both approaches can be used even with unsafe data, because the user-input data here not add anything to the original query such as (any or x=x). For more information, please read OWASP SQL Injection Prevention Cheat Sheet .

Now, if you are an advanced user, start using this defense as you like, but, for beginners, if they can't quickly implement stored procedure and prepared the statement, it's better to filter input data as much they can.

Finally, let's consider that user sends this text below instead of entering his username:

 [1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root' 

This input can be checked early without any prepared statement and stored procedures, but to be on safe side, using them starts after user-data filtering and validation.

The last point is detecting unexpected behavior which requires more effort and complexity; it's not recommended for normal web applications. Unexpected behavior in above user input is SELECT, UNION, IF, SUBSTRING, BENCHMARK, SHA, root once these words detected, you can avoid the input.

UPDATE1:

A user commented that this post is useless, OK! Here is what OWASP.ORG provided:

Primary defenses:

Option #1: Use of Prepared Statements (Parameterized Queries)
Option #2: Use of Stored Procedures
Option #3: Escaping all User Supplied Input

Additional defenses:

Also Enforce: Least Privilege
Also Perform: White List Input Validation

As you may know, claiming on an article should be supported by valid argument, at least one reference! Otherwise, it's considered as an attack and bad claim!

UPDATE2:

From the PHP manual, PHP: Prepared Statements – Manual :

Escaping and SQL injection

Bound variables will be escaped automatically by the server. The server inserts their escaped values at the appropriate places into the statement template before execution. A hint must be provided to the server for the type of bound variable, to create an appropriate conversion. See the mysqli_stmt_bind_param() function for more information.

The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements if input values are escaped correctly.

Update3:

I created test cases for knowing how PDO and MySQLi send the query to MySQL server when using prepared statement:

PDO:

 $user = "''1''"; //Malicious keyword $sql = 'SELECT * FROM awa_user WHERE userame =:username'; $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); $sth->execute(array(':username' => $user)); 

Query Log:

  189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\'' 189 Quit 

MySQLi:

 $stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) { $stmt->bind_param("s", $user); $user = "''1''"; $stmt->execute(); 

Query Log:

  188 Prepare SELECT * FROM awa_user WHERE username =? 188 Execute SELECT * FROM awa_user WHERE username ='\'\'1\'\'' 188 Quit 

It's clear that a prepared statement is also escaping the data, nothing else.

As also mentioned in above statement The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly , therefore, this proves that data validation such as intval() is a good idea for integer values before sending any query, in addition, preventing malicious user data before sending the query is correct and valid approach .

Please see this question for more detail: PDO sends raw query to MySQL while Mysqli sends prepared query, both produce the same result

参考文献:

  1. SQL Injection Cheat Sheet
  2. SQL Injection
  3. 信息安全
  4. 安全原则
  5. Data validation

A simple way would be to use a PHP framework like CodeIgniter or Laravel which have in-built features like filtering and active-record, so that you don't have to worry about these nuances.

There are so many answers for PHP and MySQL , but here is code for PHP and Oracle for preventing SQL injection as well as regular use of oci8 drivers:

 $conn = oci_connect($username, $password, $connection_string); $stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123'); oci_bind_by_name($stmt, ':xx', $fieldval); oci_execute($stmt); 

Warning : the approach described in this answer only applies for very specific scenarios and isn't secure since SQL injection attacks do not only rely on being able to inject X=Y .

If the attackers are trying to hack with the form via PHP's $_GET variable or with the URL's query string, you would be able to catch them if they're not secure.

 RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+) RewriteRule ^(.*) ^/track.php 

Because 1=1 , 2=2 , 1=2 , 2=1 , 1+1=2 , etc… are the common questions to an SQL database of an attacker. Maybe also it's used by many hacking applications.

But you must be careful, that you must not rewrite a safe query from your site. The code above is giving you a tip, to rewrite or redirect (it depends on you) that hacking-specific dynamic query string into a page that will store the attacker's IP address , or EVEN THEIR COOKIES, history, browser, or any other sensitive information, so you can deal with them later by banning their account or contacting authorities.

Using PDO and MYSQLi is a good practice to prevent SQL injections, but if you really want to work with MySQL functions and queries, it would be better to use

mysql_real_escape_string

 $unsafe_variable = mysql_real_escape_string($_POST['user_input']); 

There are more ability to prevent this: like identify – if the input is a string, number, char or array, there are so many inbuilt functions to detect this. Also it would be better to use these functions to check input data.

is_string

 $unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : ''); 

is_numeric

 $unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : ''); 

And it is so much better to use those functions to check input data with mysql_real_escape_string .

A good idea is to use an 'object-relational mapper' like Idiorm :

 $user = ORM::for_table('user') ->where_equal('username', 'j4mie') ->find_one(); $user->first_name = 'Jamie'; $user->save(); $tweets = ORM::for_table('tweet') ->select('tweet.*') ->join('user', array( 'user.id', '=', 'tweet.user_id' )) ->where_equal('user.username', 'j4mie') ->find_many(); foreach ($tweets as $tweet) { echo $tweet->text; } 

It not only saves you from SQL injections, but from syntax errors too! Also Supports collections of models with method chaining to filter or apply actions to multiple results at once and mutliple connection.

I've written this little function several years ago:

 function sqlvprintf($query, $args) { global $DB_LINK; $ctr = 0; ensureConnection(); // Connect to database if not connected already. $values = array(); foreach ($args as $value) { if (is_string($value)) { $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'"; } else if (is_null($value)) { $value = 'NULL'; } else if (!is_int($value) && !is_float($value)) { die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.'); } $values[] = $value; $ctr++; } $query = preg_replace_callback( '/{(\\d+)}/', function($match) use ($values) { if (isset($values[$match[1]])) { return $values[$match[1]]; } else { return $match[0]; } }, $query ); return $query; } function runEscapedQuery($preparedQuery /*, ...*/) { $params = array_slice(func_get_args(), 1); $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results. return $results; } 

This allows running statements in an one-liner C#-ish String.Format like:

 runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2); 

It escapes considering the variable type. If you try to parameterize table, column names, it would fail as it puts every string in quotes which is invalid syntax.

SECURITY UPDATE: The previous str_replace version allowed injections by adding {#} tokens into user data. This preg_replace_callback version doesn't cause problems if the replacement contains these tokens.