如何防止PHP中的SQL注入?

如何防止PHP中的SQL注入?,php,mysql,sql,security,sql-injection,Php,Mysql,Sql,Security,Sql Injection,如果未经修改就将用户输入插入到SQL查询中,则应用程序易受攻击,如以下示例所示: $safe_variable=$_POST['user_input']; mysql_queryINSERT到'table``列'VALUES'$safe_variable'; 这是因为用户可以输入类似“值”的内容;放下桌子;-,查询变成: INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--') 如何防止这种情况发生?我建议使用P

如果未经修改就将用户输入插入到SQL查询中,则应用程序易受攻击,如以下示例所示:

$safe_variable=$_POST['user_input']; mysql_queryINSERT到'table``列'VALUES'$safe_variable'; 这是因为用户可以输入类似“值”的内容;放下桌子;-,查询变成:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
如何防止这种情况发生?

我建议使用PHP数据对象来运行参数化SQL查询

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


通过使用PDO而不是mysql、mysqli和pgsql函数,您可以使您的应用程序从数据库中更抽象一些,而很少需要切换数据库提供程序。

您可以执行以下基本操作:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])
SELECT ... WHERE id = UNHEX('2d312075...3635');
这并不能解决所有问题,但它是一个很好的垫脚石。我遗漏了一些明显的项目,比如检查变量的存在、格式数字、字母等

不推荐的警告: 这个答案的示例代码与问题的示例代码一样,使用PHP的MySQL扩展,该扩展在PHP5.5.0中被弃用,在PHP7.0.0中被完全删除

安全警告:此答案不符合安全最佳做法,使用事先准备好的语句。使用下面概述的策略,风险自负。此外,在PHP7中删除了mysql_real_escape_字符串

如果您使用的是最新版本的PHP,下面列出的mysql\u real\u escape\u string选项将不再可用,尽管mysqli::escape\u string是一个现代的等效选项。如今,mysql\u real\u escape\u string选项只适用于旧版本PHP上的遗留代码

您有两个选择-转义不安全的_变量中的特殊字符,或使用参数化查询。两者都可以防止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
另请参见函数的详细信息

要使用参数化查询,您需要使用而不是函数。为了重写您的示例,我们需要如下内容

<?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();
?>
您需要了解的关键功能是

此外,正如其他人所建议的,您可能会发现使用以下内容来提升抽象层很有用/更容易

请注意,您询问的案例相当简单,更复杂的案例可能需要更复杂的方法。特别是:

如果您想根据用户输入改变SQL的结构,参数化查询将不会有帮助,而且mysql\u real\u escape\u字符串不包含所需的转义。在这种情况下,最好通过白名单传递用户的输入,以确保只允许“安全”值通过。 如果您在条件中使用来自用户输入的整数,并采用mysql\u real\u escape\u string方法,您将遇到下面注释中描述的问题。这种情况更为棘手,因为整数不会被引号包围,所以您可以通过验证用户输入只包含数字来处理。 可能还有其他我不知道的案例。您可能会发现,在您可能遇到的一些更微妙的问题上,这是一个有用的资源。 使用准备好的语句和参数化查询。这些SQL语句与任何参数分开发送到数据库服务器并由数据库服务器解析。这样,攻击者就不可能注入恶意SQL

您基本上有两个选项来实现这一点:

用于任何受支持的数据库驱动程序:

$stmt=$pdo->prepare'SELECT*FROM employees,其中name=:name'; $stmt->execute['name'=>$name]; foreach$stmt作为$row{ //用$row做点什么 } 使用MySQL:

$stmt=$dbConnection->prepare'SELECT*FROM employees WHERE name=?'; $stmt->bind_参数',$name;//'s'指定变量类型=>'string' $stmt->execute; $result=$stmt->get\u result; 而$row=$result->fetch\u assoc{ //用$row做点什么 } 如果您连接的是MySQL以外的数据库,则可以参考特定于驱动程序的第二个选项,例如,pg_prepare和pg_execute for PostgreSQL。PDO是通用选项

正确设置连接 请注意,当使用PDO访问MySQL数据库时,默认情况下不使用real prepared语句。要解决此问题,必须禁用对已准备语句的模拟。使用PDO创建连接的示例如下:

$dbConnection=newpdo'mysql:dbname=dbtest;主机=127.0.0.1;字符集=utf8','用户','密码'; $dbConnection->setAttributePDO::ATTR\u EMULATE\u PREPARES,false; $dbConnection->setAttributePDO::ATTR_ERRMODE,PDO::ERRMODE_异常; 在上面的示例中,错误模式不是st 这是非常必要的,但建议添加它。这样,当出现错误时,脚本不会因致命错误而停止。并且它使开发人员有机会捕获作为异常抛出的任何错误

然而,必须的是第一行setAttribute,它告诉PDO禁用模拟的准备语句,并使用真正的准备语句。这可以确保在将语句和值发送到MySQL服务器之前,PHP不会解析语句和值,从而使可能的攻击者没有机会注入恶意SQL

尽管您可以在构造函数的选项中设置字符集,但需要注意的是,DSN中5.3.6之前的PHP的“旧”版本

解释 传递给准备的SQL语句由数据库服务器解析和编译。通过指定参数a?或者上面示例中的命名参数:name告诉数据库引擎要在哪里进行筛选。然后,在调用execute时,准备好的语句将与指定的参数值组合在一起

这里重要的一点是,参数值与已编译语句相结合,而不是与SQL字符串相结合。SQL注入的工作原理是,在脚本创建要发送到数据库的SQL时,诱使脚本包含恶意字符串。因此,通过将实际SQL与参数分开发送,您可以限制以意外结果结束的风险

使用预处理语句时发送的任何参数都将被视为字符串,尽管数据库引擎可能会进行一些优化,因此参数也可能最终成为数字。在上面的示例中,如果$name变量包含“Sarah”;从employees中删除结果只是搜索字符串“Sarah”;从员工中删除,您将不会得到

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

哦,既然您询问了如何进行插入,下面是一个使用PDO的示例:

$preparedStatement=$db->prepare'INSERT INTO table column VALUES:column'; $preparedStatement->execute['column'=>$unsafeValue]; 准备好的语句可以用于动态查询吗? 尽管您仍然可以对查询参数使用预处理语句,但动态查询本身的结构无法参数化,某些查询功能也无法参数化

对于这些特定场景,最好使用白名单过滤器来限制可能的值

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}
使用PDO和准备好的查询

$conn是一个PDO对象


无论您最终使用什么,请确保您的输入没有被神奇的引用或其他善意的垃圾所破坏,如果必要,请通过StripSlash或其他任何方式对其进行清理。

从安全角度来看,我赞成您的做法-好处是-

大多数数据库(包括)允许用户访问仅限于执行存储过程。细粒度安全访问控制有助于防止权限攻击升级。这将防止受损应用程序能够直接针对数据库运行SQL。 它们从应用程序中提取原始SQL查询,因此应用程序可以获得的数据库结构信息较少。这使得人们更难理解数据库的底层结构并设计合适的攻击。 它们只接受参数,因此参数化查询的优点是存在的。当然-在我看来,您仍然需要清理输入-尤其是在存储过程中使用动态SQL时。 缺点是-

它们的存储过程很难维护,而且往往繁殖得很快。这使得管理它们成为一个问题。 它们不太适合于动态查询——若它们是为了接受动态代码作为参数而构建的,那个么许多优势就被否定了。 不推荐的警告: 这个答案的示例代码与问题的示例代码一样,使用PHP的MySQL扩展,该扩展在PHP5.5.0中被弃用,在PHP7.0.0中被完全删除

安全警告:此答案不符合安全最佳做法,使用事先准备好的语句。使用下面概述的策略,风险自负。此外,在PHP7中删除了mysql_real_escape_字符串

重要的

防止SQL注入的最佳方法是使用准备好的语句,而不是转义,如所示

还有像和这样的库,允许开发人员更容易地使用准备好的语句。要了解有关为什么准备好的陈述更擅长的更多信息,请参阅和

注射预防-

PHP有一个特制的函数来防止这些攻击。你所需要做的就是使用一个函数,mysql\u real\u escape\u string

mysql\u real\u escape\u string获取将在mysql中使用的字符串 返回相同的字符串,所有SQL注入尝试都安全转义。基本上,它将替换那些麻烦的引号“用户可能输入的MySQL安全替代品,转义引号\”

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

//连接到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;
您可以在中找到更多详细信息

不推荐的警告: 这个答案的示例代码与问题的示例代码一样,使用PHP的MySQL扩展,该扩展在PHP5.5.0中被弃用,在PHP7.0.0中被完全删除

安全警告:此答案不符合安全最佳做法,使用事先准备好的语句。使用下面概述的策略,风险自负。此外,在PHP7中删除了mysql_real_escape_字符串

参数化查询和输入验证是一条可行之路。尽管使用了mysql\u real\u escape\u字符串,但在许多情况下可能会发生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");

在这两种情况下,都不能使用“保护封装”


:转义时意外的SQL注入是不够的

这里的每个答案只涉及问题的一部分。 事实上,有四个不同的查询部分可以动态添加到SQL中:-

一串 数 标识符 语法关键字 而准备好的声明只涵盖其中两个

但有时我们必须使查询更加动态,同时添加运算符或标识符。 因此,我们需要不同的保护技术

通常,这种保护方法基于白名单

在这种情况下,每个动态参数都应该在脚本中硬编码并从该集中选择。 例如,要执行动态排序,请执行以下操作:

$orders  = array("name", "price", "qty"); // Field names
$key = array_search($_GET['sort'], $orders)); // if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. 
$query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe
为了简化这一过程,我写了一个在一行中完成所有工作的示例:

$orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
$query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe
还有另一种保护标识符的方法——转义,但我坚持认为白名单是一种更健壮、更明确的方法。但是,只要您引用了一个标识符,就可以转义引号字符以确保其安全。例如,默认情况下,对于mysql,您必须。对于其他DBMS,转义规则会有所不同

尽管如此,诸如AND、DESC等SQL语法关键字仍然存在问题,但在这种情况下,白名单似乎是唯一的方法

因此,一般性建议的措辞可以是

任何表示SQL数据文字的变量,或者简单地说,表示SQL字符串或数字的变量都必须通过准备好的语句添加。没有例外。 任何其他查询部分,如SQL关键字、表或字段名或运算符,都必须通过白名单进行筛选。 使现代化 尽管关于SQL注入保护的最佳实践已经达成了一致,但仍然存在许多不好的实践。其中一些问题在PHP用户的头脑中根深蒂固。例如,在这个页面上,虽然大多数访问者看不见80多个被删除的答案,但由于质量不好或推广了不好的过时做法,所有答案都被社区删除了。更糟糕的是,一些糟糕的答案并没有被删除,而是得到了成功

例如,包括建议您手动字符串转义,这是一种过时的方法,被证明是不安全的

或者有一个稍微好一点的答案,它暗示了正义,甚至吹嘘它是终极灵丹妙药。当然,事实并非如此。这种方法并不比常规的字符串格式好多少,但它保留了所有的缺点:它只适用于字符串,而且与任何其他手动格式一样,它本质上是可选的、非强制性的,容易出现任何类型的人为错误

我认为这一切都是因为一个非常古老的迷信,得到了像or这样的权威机构的支持,它宣称任何逃避和保护SQL注入的行为都是平等的

不管PHP手册多年来怎么说,*_escape_string绝不会使数据安全,也从来没有打算这样做。除了对字符串以外的任何SQL部分都没有用处之外,手动转义也是错误的,因为它是手动的,而不是自动的

而OWASP则让情况更糟,强调逃避用户输入,这完全是胡说八道:在注入保护的上下文中不应该有这样的词语。每一个变量都有潜在的危险-无论其来源如何!或者,换句话说,无论源代码是什么,每个变量都必须正确格式化才能放入查询。重要的是目的地。当开发人员开始将绵羊和山羊区分开来,思考某个特定变量是否安全时,他/她向灾难迈出了第一步。更不用说,即使是措辞也暗示在入口点进行批量转义,类似于非常神奇的引号功能——已经被轻视、弃用和删除


因此,与任何转义不同,prepared语句在适用时确实可以防止SQL注入。

如果可能,可以强制转换参数的类型 呃。但它只适用于int、bool和float等简单类型

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

在我看来,在PHP应用程序或任何web应用程序中防止SQL注入的最好方法是考虑应用程序的体系结构。如果防止SQL注入的唯一方法是记住在每次与数据库对话时使用一种特殊的方法或函数来做正确的事情,那么这就是错误的。这样,在代码中的某个时刻忘记正确格式化查询只是时间问题


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

有很多方法可以防止SQL注入和其他SQL攻击。你可以很容易地在互联网上找到它谷歌搜索。当然,PDO是一个很好的解决方案。但我想向您推荐一些防止SQL注入的好链接

还有其他类似的

现在,您为什么需要阻止SQL注入查询

我想让您知道:为什么我们要用下面的一个简短示例来阻止SQL注入:

查询登录身份验证匹配:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";
现在,如果有人被黑客

$_POST['email']= admin@emali.com' OR '1=1
什么都可以

查询最多只能解析到系统中:

$query="select * from users where email='admin@emali.com' OR '1=1';
另一部分将被丢弃。那么,会发生什么?未经授权的用户黑客将能够以管理员身份登录,而无需密码。现在,他/她可以做管理员/电子邮件人员可以做的任何事情。请看,如果不阻止SQL注入,这是非常危险的。

使用此PHP函数mysql\u escape\u string,您可以快速获得良好的预防

例如:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'
mysql\u escape\u string-转义字符串以用于mysql\u查询

要获得更多预防,您可以在末尾添加

wHERE 1=1   or  LIMIT 1
最后你会得到:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1
安全警告:此答案不符合安全最佳做法,使用事先准备好的语句。使用下面概述的策略,风险自负。此外,在PHP7中删除了mysql_real_escape_字符串

已弃用警告:mysql扩展目前已弃用。我们建议使用PDO扩展

我使用三种不同的方法来防止我的web应用程序受到SQL注入的攻击

使用mysql\u real\u escape\u字符串,这是中的预定义函数,此代码将反斜杠添加到以下字符:\x00、\n、\r、\、'和\x1a。将输入值作为参数传递,以最小化SQL注入的机会。 最先进的方法是使用PDO。 我希望这对你有帮助

考虑以下查询:

$iId=mysql\u real\u escape\u string1或1=1; $sSql=从表中选择*,其中id=$iId

mysql\u real\u escape\u字符串在此不受保护。如果在查询中的变量周围使用单引号“”,则可以防止出现这种情况。以下是针对此问题的解决方案:

$iId=int mysql\u real\u escape\u string1或1=1; $sSql=从表中选择*,其中id=$iId

这有一些很好的答案

我建议,使用PDO是最好的选择

编辑:

mysql\u real\u escape\u字符串从PHP5.5.0开始就不推荐使用。使用mysqli或PDO

mysql\u real\u escape\u字符串的另一种替代方法是

string mysqli_real_escape_string ( mysqli $link , string $escapestr )
例如:

$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')");
$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();
$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

对于那些不确定如何使用来自mysql_uu函数的PDO的人,我制作了一个简单的文件。它的存在是为了说明实现应用程序需要完成的所有常见任务是多么容易。与PostgreSQL、MySQL和SQLite配合使用

基本上,阅读它可以了解如何在现实生活中使用PDO函数,从而简化以所需格式存储和检索值的过程

我想要一个专栏

我想要一个arraykey=>值结果,即用于制作选择框

我想要一个单行结果

我想要一系列的结果


正如你所见,人们建议你最多使用事先准备好的陈述。这并没有错,但当每个进程只执行一次查询时,会有轻微的性能损失

我曾经面对过这个问题,但我认为我用了一种非常复杂的方式解决了它——黑客用来避免引用的方式。我将其与模拟的准备语句结合使用。我使用它来防止各种可能的SQL注入攻击

我的做法: 如果您希望输入是整数,请确保它确实是整数。在像PHP这样的变量类型语言中,这一点非常重要。例如,您可以使用这个非常简单但功能强大的解决方案:sprintfSELECT 1,2,3,从表中4=%u$input

如果你对整型十六进制还有什么期待的话。如果你对它进行十六进制运算,你将完全摆脱所有的输入。在C中 这些工具不再可用了吗?您如何保护您的应用程序

我反对SQL注入的方法是:在任何查询中使用用户输入数据之前,先清除用户输入数据,然后再将其发送到数据库

用于将不安全数据转换为安全数据的数据筛选

考虑到这一点,我们无法提供。如何保护您的应用程序?你强迫我使用它们吗?除了PHP以外的其他语言呢?我更愿意提供一般性的想法,因为它可以用于更广泛的边界,而不仅仅是一种特定的语言

SQL用户限制用户权限:最常见的SQL操作是选择、更新、插入,那么,为什么要将更新权限授予不需要它的用户呢?例如,登录和搜索页面仅使用SELECT,那么,为什么在这些页面中使用具有高权限的DB用户呢? 规则:不要为所有权限创建一个数据库用户。对于所有SQL操作,您可以创建类似deluser、selectuser、updateuser等方案作为用户名,以便于使用

数据过滤:在生成任何查询用户输入之前,应该对其进行验证和过滤。对于程序员来说,为每个用户输入变量定义一些属性很重要: 数据类型、数据模式和数据长度。对于介于x和y之间的数字字段,必须使用精确的规则进行精确验证,对于字符串文本的字段:模式就是这样,例如,用户名必须只包含一些字符,比如[A-zA-Z0-9_-。]。长度在x和n之间变化,其中x和n个整数,x警告:此答案中描述的方法仅适用于非常特定的场景,并且不安全,因为SQL注入攻击不仅依赖于能够注入x=Y

如果攻击者试图通过PHP的$\u GET变量或URL的查询字符串侵入表单,如果它们不安全,您将能够捕获它们

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php
因为1=1,2=2,1=2,2=1,1+1=2等等。。。是攻击者SQL数据库的常见问题。也许它也被许多黑客应用程序所使用


但您必须小心,不能从您的站点重写安全查询。上面的代码为您提供了一个提示,重写或重定向取决于您是否将特定的动态查询字符串破解到一个页面,该页面将存储攻击者的,甚至他们的cookie、历史记录、浏览器或任何其他敏感信息,因此,您可以稍后通过禁止他们的帐户或联系权威机构来处理这些问题。

一些有关SQL语句中特殊字符转义的指导原则

不要使用。此扩展已弃用。使用或代替

MySQLi

要手动转义字符串中的特殊字符,可以使用函数。除非设置了正确的字符集,否则该函数将无法正常工作

例如:

$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')");
$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();
$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
要使用准备好的语句自动转义值,请使用,并且必须为相应的绑定变量提供类型,以便进行适当的转换:

例如:

$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')");
$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();
$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
无论您使用预处理语句还是mysqli\u real\u escape\u字符串,您都必须知道正在使用的输入数据的类型

因此,如果使用预处理语句,则必须指定mysqli_stmt_bind_param函数的变量类型

而mysqli_real_escape_string的使用,顾名思义,是为了转义字符串中的特殊字符,因此它不会使整数安全。此函数的目的是防止破坏SQL语句中的字符串以及它可能对数据库造成的损坏。当正确使用mysqli_real_escape_字符串时,它是一个非常有用的函数,尤其是与sprintf结合使用时

例如:

$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')");
$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();
$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

一个简单的方法是使用像or这样的PHP框架,它具有过滤和活动记录等内置功能,这样您就不必担心这些细微差别

不推荐的警告: 这个答案的示例代码与问题的示例代码一样,使用PHP的MySQL扩展,该扩展在PHP5.5.0中被弃用,在PHP7.0.0中被完全删除

安全警告:此答案不符合安全最佳做法,使用事先准备好的语句。使用下面概述的策略,风险自负。此外,在PHP7中删除了mysql_real_escape_字符串

使用and是防止SQL注入的良好实践,但是如果您真的想使用MySQL函数和查询,最好使用and

有更多的功能来防止这种情况:比如识别——如果输入是字符串、数字、字符或数组,那么有很多内置函数来检测这种情况。此外,最好使用这些函数检查输入数据


使用这些函数用mysql\u real\u escape\u字符串检查输入数据要好得多。

对于PHP和mysql有很多答案,但下面是PHP和Oracle防止SQL注入以及定期使用oci8驱动程序的代码:

$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);

几年前我写过这个小函数:

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;
}
这允许运行st

执行一个查询,除此之外执行任何其他查询;被忽略了。即使这已经被弃用,PHP5.5.0下仍有许多系统可能使用此函数。这是一个坏习惯,但也是一个问题后解决方案:不仅对于SQL注入,而且对于任何类型的注入,例如,如果您有一个现成的旧网站或应用程序存在注入缺陷,F3 framework v2中存在视图模板注入漏洞,一种解决方案是在引导时使用转义值重新分配supperglobal预定义变量(如$\u POST)的值。通过PDO,今天的框架仍然有可能逃逸:substr$PDO->quote$str、\PDO::PARAM_str,1,-1这个答案没有解释什么是准备好的语句-一件事-如果在请求期间使用大量准备好的语句,那么性能会受到影响,有时它会造成10倍的性能损失。更好的情况是在关闭参数绑定的情况下使用PDO,但关闭语句准备。使用PDO更好,如果使用直接查询,请确保使用mysqli::escape_string@Alix这在理论上听起来是个好主意,但有时价值观需要另一种逃避,例如,对于SQL和HTMLTH,这是为数不多的使用转义值而不是预处理语句的情况之一。整数类型转换非常有效。这个问题非常普遍。上面有一些很好的答案,但大多数都是建议事先准备好的陈述。MySQLi async不支持预先准备好的语句,因此sprintf看起来是这种情况下的一个很好的选择。这个答案基本上是错误的,因为它无助于防止注入预防,而只是试图缓和后果。徒劳。没错,这并不能提供解决方案,但这是您可以在开始之前避免的事情。@Apurv如果我的目标是从您的数据库中读取私人信息,那么没有删除权限意味着什么。@AlexHolsgrove:别紧张,我只是在建议减轻后果的好做法。@Apurv你不想减轻后果,你要尽一切可能防止它。公平地说,设置正确的用户访问权限很重要,但不是OP真正要求的。我认为你的第一段很重要。理解是关键。而且,并非每个人都为公司工作。对于大多数人来说,框架实际上与理解的理念背道而驰。在最后期限内工作时,熟悉基本面可能并不重要,但那些“自己动手”的人喜欢把手弄脏。框架开发人员并没有特权到其他人都必须低头并假定他们从不犯错误。决策权仍然很重要。谁能说我的框架在未来不会取代其他方案?@AnthonyRutledge你完全正确。了解正在发生的事情以及原因是非常重要的。然而,一个真正的、经过尝试的、积极使用和开发的框架遇到并解决了许多问题、修补了许多安全漏洞的可能性已经相当高了。查看源代码以了解代码质量是个好主意。如果这是一个未经测试的烂摊子,它可能不安全。在这里好的观点。然而,您是否同意,许多人可以学习采用MVC系统,但不是每个人都可以通过手动控制器和服务器复制MVC系统。在这一点上,人们可能会走得太远。在我加热我女朋友给我做的花生酱山核桃饼干之前,我需要了解我的微波炉吗-@我同意!我认为这个用例也有不同:我是在为我的个人主页建立一个照片库,还是在构建一个网上银行web应用程序?在后一种情况下,了解安全性的细节以及我使用的框架如何解决这些问题非常重要。啊,自己动手的推论的安全例外。看,我倾向于冒着一切风险去破产开玩笑。只要有足够的时间,人们就可以学会制作一个非常安全的应用程序。太多人在匆忙中。他们举手并认为框架更安全。毕竟,他们没有足够的时间来测试和解决问题。此外,安全是一个需要专门研究的领域。这不仅仅是程序员通过理解算法和设计模式而深入了解的东西。我认为问题的关键是在不使用这种框架的情况下完成这项工作。使用mysql\u real\u escape\u string就足够了,或者我也必须使用参数化?@peimanF。保持使用参数化查询的良好实践,即使在本地项目上也是如此。使用参数化查询可以保证不会出现SQL注入。但请记住,您应该清理数据,以避免虚假检索,例如XSS注入,例如将HTML代码放在带有HTML属性的文本中,以便example@peimanF. 参数化查询和绑定值的良好实践,但真正的转义字符串有利于现在我理解
为了完整起见,我加入了mysql\u real\u escape\u字符串,但我不喜欢首先列出最容易出错的方法。读者可能会很快抓住第一个例子。好在它现在已经被弃用了:@SteenSchütt-所有mysql_*函数都被弃用了。它们被类似的mysqli_*函数所取代,例如mysqli_real_escape_string。请解释oci_bind_by_name参数。1-1=0是怎么回事@Ráplipandrá是某种[0-9\-]+=[0-9]+。mysqli是不正确的。第一个参数表示数据类型。这个答案有误导性。PDO并不是一根魔杖,它仅仅通过存在来保护您的查询。您应该用占位符替换查询中的每个变量,以获得来自PDO的保护。老实说,我不同意您的建议。这可能会导致在任何ORM中产生错误的安全感。当然,其中大多数都负责准备语句和参数化查询。来到这篇文章的新手可能仍然会因为选择了任何ORM而感到安全——相信他们所有人。一般来说,ORM通过隐藏/抽象实现细节来简化工作。你真的想检查或盲目相信它是如何完成的。经验法则:它背后的开源社区支持越大,它就越不会被完全搞砸;老实说,这不是最坏的主意,pocketrocket。根据ORM的不同,ORM的作者很有可能比编码人员更了解SQL。这有点像旧的加密规则,除非你的名字出现在领域内的研究论文上,否则不要滚动你自己的名字,因为攻击者很可能在领域内的论文上有他的名字。也就是说,如果ORM要求您提供查询ie模型的全部或部分。filter'where foo=?',bar,那么您最好使用SQL
$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();
 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';
FLUSH PRIVILEGES; 
select * from mysql.user where User='username';
[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'
$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));
    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit
$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();
    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit
RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php
$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')");
$stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");

$stmt->bind_param("is", $integer, $string);

$stmt->execute();
$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
$unsafe_variable = mysql_real_escape_string($_POST['user_input']);
$unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');
$unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');
$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);
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;
}
runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);
$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;
}