php防注入函数 - 如何在PHP中阻止SQL注入?
php防止sql注入 (19)
如果插入用户输入而不修改SQL查询,则应用程序容易受到SQL注入的攻击,如下例所示:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
那是因为用户可以输入类似value'); DROP TABLE table;--
value'); DROP TABLE table;--
,查询变为:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
可以采取哪些措施来防止这种情况发生?
安全警告 :此答案与安全最佳实践不符。 转义不足以防止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 . "')");
这不会解决所有问题,但它是一个非常好的垫脚石。 我省略了明显的项目,例如检查变量的存在,格式(数字,字母等)。
警告:这个答案的示例代码(如问题的示例代码)使用PHP的
mysql
扩展,在PHP 5.5.0中已弃用并在PHP 7.0.0中完全删除。
如果您使用的是最新版本的PHP,则下面列出的mysql_real_escape_string
选项将不再可用(尽管mysqli::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
。
此外,正如其他人所建议的那样,您可能会发现使用PDO类的步骤来提升抽象层是有用的/更容易的。
请注意,您询问的案例非常简单,更复杂的案例可能需要更复杂的方法。 特别是:
- 如果要根据用户输入更改SQL的结构,参数化查询将无济于事,并且
mysql_real_escape_string
不会涵盖所需的转义。 在这种情况下,您最好通过白名单传递用户的输入,以确保只允许“安全”值。 - 如果在条件中使用来自用户输入的整数并采用
mysql_real_escape_string
方法,则您将在下面的注释中遇到Polynomial描述的问题。 这种情况比较棘手,因为整数不会被引号括起来,所以你可以通过验证用户输入只包含数字来处理。 - 可能还有其他我不知道的情况。 您可能会发现this是一个有用的资源,可以解决您可能遇到的一些更微妙的问题。
使用预准备语句和参数化查询。 这些是由数据库服务器与任何参数分开发送和解析的SQL语句。 这样攻击者就无法注入恶意SQL。
你基本上有两个选项来实现这个目标:
使用PDO (适用于任何支持的数据库驱动程序):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute(array('name' => $name)); foreach ($stmt as $row) { // do something with $row }
使用MySQLi (用于MySQL):
$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string' $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_assoc()) { // do something with $row }
如果你要连接MySQL以外的数据库,你可以参考一个特定于驱动程序的第二个选项(例如pg_prepare()
和pg_execute()
用于PostgreSQL)。 PDO是通用选项。
正确设置连接
请注意,使用PDO
访问MySQL数据库时,默认情况下不使用 实际准备好的语句。 要解决此问题,您必须禁用预准备语句的模拟。 使用PDO创建连接的示例是:
$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);
在上面的例子中,错误模式并不是绝对必要的, 但建议添加它 。 这样,当出现问题时,脚本不会因Fatal Error
而停止。 它使开发人员有机会catch
任何作为PDOException
throw
错误。
但是, 强制性的是第一个setAttribute()
行,它告诉PDO禁用模拟的预准备语句并使用真实的预准备语句。 这样可以确保PHP在将语句和值发送到MySQL服务器之前不会对其进行解析(使攻击者可能无法注入恶意SQL)。
虽然您可以在构造函数的选项中设置charset
,但重要的是要注意PHP的旧版本(<5.3.6) 默认忽略 DSN中的charset参数 。
说明
会发生什么是您传递给prepare
的SQL语句由数据库服务器解析和编译。 通过指定参数( ?
或命名参数,如上例中的:name
),您可以告诉数据库引擎要过滤的位置。 然后,当您调用execute
,预准备语句将与您指定的参数值组合。
这里重要的是参数值与编译语句结合,而不是SQL字符串。 SQL注入通过在创建SQL以发送到数据库时欺骗脚本来包含恶意字符串。 因此,通过将参数中的实际SQL分开发送,可以限制结束您不想要的内容的风险。 使用预准备语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数最终也可能作为数字)。 在上面的例子中,如果$name
变量包含'Sarah'; DELETE FROM employees
'Sarah'; DELETE FROM employees
结果只是搜索字符串"'Sarah'; DELETE FROM employees"
,你不会得到一个空表 。
使用预准备语句的另一个好处是,如果在同一个会话中多次执行相同的语句,它只会被解析和编译一次,从而为您带来一些速度提升。
哦,既然你问过如何为插入操作,这是一个例子(使用PDO):
$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');
$preparedStatement->execute(array('column' => $unsafeValue));
准备好的语句可以用于动态查询吗?
虽然您仍然可以为查询参数使用预准备语句,但动态查询本身的结构无法进行参数化,并且某些查询功能无法进行参数化。
对于这些特定方案,最好的办法是使用限制可能值的白名单过滤器。
// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
$dir = 'ASC';
}
一些在SQL语句中转义特殊字符的准则。
不要使用MySQL,不推荐使用此扩展,使用MySQLi或PDO。
库MySQLi
要手动转义字符串中的特殊字符,可以使用mysqli_real_escape_string函数。除非使用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')" );
要使用mysqli_stmt_bind_param准备语句自动转义值,请使用mysqli_prepare和mysqli_stmt_bind_param,其中必须提供相应绑定变量的类型以进行适当的转换:
例:
$stmt = $mysqli->prepare( "INSERT INTO table ( column1, column2 ) VALUES (?,?)" );
$stmt->bind_param( "is", $integer, $string );
$stmt->execute();
无论您是使用预处理语句还是mysqli_real_escape_string,您始终必须知道您正在使用的输入数据的类型。
因此,如果使用预准备语句,则必须为mysqli_stmt_bind_param函数指定变量的类型。
正如名称所说,使用mysqli_real_escape_string是为了逃避字符串中的特殊字符,因此它不会使整数安全。此函数的目的是防止破坏SQL语句中的字符串,以及它可能导致的数据库损坏。如果使用得当,mysqli_real_escape_string是一个很有用的函数,特别是与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
几年前我写过这个小函数:
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;
}
这允许在单行C#-String.Format中运行语句,如:
runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);
考虑到变量类型,它逃脱了。如果您尝试参数化表,列名称,它将失败,因为它将每个字符串放在引号中,这是一个无效的语法。
安全更新:先前str_replace
版本允许通过将{#}令牌添加到用户数据中进行注入。preg_replace_callback
如果替换包含这些令牌,则此版本不会导致问题。
参数化查询和输入验证是要走的路。 即使使用了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`");
在这两种情况下,您都不能使用'
来保护封装。
Source : 意外的SQL注入(当转义不够时)
如您所见,人们建议您最多使用预准备语句。 这没有错,但是当每个进程只执行一次查询时,会有轻微的性能损失。
我正面临这个问题,但我认为我以非常复杂的方式解决了这个问题 - 黑客用来避免使用引号的方式。 我将它与模拟的预处理语句结合使用。 我用它来防止各种可能的SQL注入攻击。
我的方法:
如果您希望输入为整数,请确保它是真正的整数。 在像PHP这样的可变类型语言中,这非常重要。 您可以使用这个非常简单但功能强大的解决方案:
sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);
如果你期望整数十六进制的其他东西呢 。 如果你将它变成十六进制,你将完全逃脱所有输入。 在C / C ++中有一个名为
mysql_hex_string()
的函数,在PHP中你可以使用bin2hex()
。不要担心转义字符串的原始长度是其原始长度的
((2*input_length)+1)
,因为即使您使用mysql_real_escape_string
,PHP也必须分配相同的容量((2*input_length)+1)
,这是相同的。传输二进制数据时经常使用这种十六进制方法,但我认为没有理由不在所有数据上使用它来防止SQL注入攻击。 请注意,您必须使用
0x
前置数据或使用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等数据列。
此外,如果您要插入空字符串,它的使用有点复杂。 你必须用''
完全替换它,否则你会收到错误。
UNHEX()适用于任何列; 你不必担心空字符串。
Hex方法通常用作攻击
请注意,此十六进制方法通常用作SQL注入攻击,其中整数就像字符串一样,只使用mysql_real_escape_string
转义。 然后你可以避免使用引号。
例如,如果您只是这样做:
"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])
攻击可以很容易地注入你。 考虑从脚本返回的以下注入代码:
SELECT ... WHERE id = -1 union来自information_schema.tables的所有select table_name
现在只需提取表结构:
SELECT ... WHERE id = -1 union all from information_schema.column select column_name其中table_name = 0x61727469636c65
然后只需选择想要的任何数据。 不是很酷吗?
但是如果可注射部位的编码器会使其十六进制,则无法进行注射,因为查询看起来像这样: SELECT ... WHERE id = UNHEX('2d312075...3635')
我使用三种不同的方法来防止我的Web应用程序容易受到SQL注入的攻击。
- 使用的
mysql_real_escape_string()
,这是一个预先定义的函数PHP,这代码添加反斜杠以下字符:\x00
,\n
,\r
,\
,'
,"
和\x1a
。将输入值作为参数传递,以最大限度地减少SQL注入的可能性。 - 最先进的方法是使用PDO。
我希望这能帮到您。
请考虑以下查询:
$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";
mysql_real_escape_string()不会在这里保护。如果你在查询中的变量周围使用单引号('')可以保护你不受此影响。以下是针对此的解决方案:
$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";
这个question有一些很好的答案。
我建议,使用PDO是最好的选择。
编辑:
mysql_real_escape_string()
自PHP 5.5.0起不推荐使用。使用mysqli或PDO。
mysql_real_escape_string()的替代方法是
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");
我想如果有人想使用PHP和MySQL或其他一些dataBase服务器:
- 考虑学习PDO(PHP数据对象) - 它是一个数据库访问层,提供访问多个数据库的统一方法。
- 考虑学习MySQLi
- 使用本机PHP函数,例如:strip_tags,mysql_real_escape_string()或者如果是变量数字,只需
(int)$foo
。在here阅读有关PHP中变量类型的更多信息。如果您使用的是PDO或MySQLi等库,请始终使用PDO::quote()和mysqli_real_escape_string()。
图书馆示例:
---- PDO
-----没有占位符 - 适合SQL注入!这不好
$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");
-----未命名的占位符
$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);
-----命名占位符
$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轻松赢得这场战斗。通过支持12种不同的数据库驱动程序和命名参数,我们可以忽略较小的性能损失,并习惯其API。从安全角度来看,只要开发人员以他们应该使用的方式使用它们,它们都是安全的
但是,虽然PDO和MySQLi都非常快,但MySQLi在基准测试中的表现速度要快得多 - 非准备语句约为2.5%,准备语句约为6.5%。
请测试您的数据库的每个查询 - 这是防止注入的更好方法。
无论你最终使用什么,请确保你检查你的输入还没有被magic_quotes
或其他一些善意的垃圾所破坏,如果有必要,可以通过stripslashes
或其他任何消毒来清理它。
**警告:本回答中描述的方法仅适用于非常特定的场景,并且不安全,因为SQL注入攻击不仅依赖于能够注入X=Y
。**
如果攻击者试图通过PHP的$_GET
变量或URL的查询字符串来攻击表单,那么如果它们不安全,您将能够捕获它们。
RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php
因为1=1
,2=2
,1=2
,2=1
,1+1=2
,等...都是攻击者的SQL数据库中的常见问题。也许许多黑客应用程序也使用它。
但是您必须小心,不得从您的站点重写安全查询。上面的代码给你一个提示,重写或重定向(这取决于你)特定于黑客的动态查询字符串到一个页面,该页面将存储攻击者的IP地址,或者甚至是他们的COOKIES,历史,浏览器或任何其他敏感信息信息,以便您以后可以通过禁止他们的帐户或联系当局来处理它们。
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);
一种简单的方法是使用像CodeIgniter或Laravel这样的PHP框架,它具有内置功能,如过滤和活动记录,这样您就不必担心这些细微差别。
从安全的角度来看,我赞成存储过程(MySQL自5.0以来一直支持存储过程) - 优点是 -
- 大多数数据库(包括MySQL)都允许将用户访问限制为执行存储过程。细粒度的安全访问控制对于防止特权攻击升级非常有用。这可以防止受损应用程序直接对数据库运行SQL。
- 它们从应用程序中抽象出原始SQL查询,因此应用程序可以使用较少的数据库结构信息。这使人们更难理解数据库的底层结构并设计合适的攻击。
- 它们只接受参数,因此参数化查询的优点就在于此。当然 - IMO你仍然需要清理你的输入 - 特别是如果你在存储过程中使用动态SQL。
缺点是 -
- 它们(存储过程)很难维护,并且很快就会繁殖。这使得管理它们成为一个问题。
- 它们不太适合动态查询 - 如果它们构建为接受动态代码作为参数,则许多优点被否定。
使用这个PHP函数,mysql_escape_string()
您可以快速获得良好的预防。
例如:
SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'
mysql_escape_string
- 转义一个字符串以在mysql_query中使用
为了更多预防,您可以在最后添加...
wHERE 1=1 or LIMIT 1
最后你得到:
SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1
关于许多有用的答案,我希望在这个帖子中添加一些值。SQL注入是一种攻击,可以通过用户输入(由用户填充然后在查询中使用的输入)完成,SQL注入模式是正确的查询语法,而我们可以调用它:错误的查询由于不好的原因,我们假设有可能试图获取影响安全三原则(机密性,完整性,可用性)的秘密信息(绕过访问控制)的坏人。
现在,我们的观点是防止SQL注入攻击等安全威胁,问题(如何使用PHP防止SQL注入攻击),更真实,数据过滤或清除输入数据是在使用用户输入数据时的情况查询,使用PHP或任何其他编程语言不是这种情况,或者更多的人建议使用现代技术,如准备好的语句或当前支持SQL注入预防的任何其他工具,请考虑这些工具不再可用?您如何保护您的申请?
我对SQL注入的方法是:在将用户输入数据发送到数据库之前清除它(在任何查询中使用它之前)。
数据过滤(将不安全数据转换为安全数据)考虑到PDO和MySQLi不可用,您如何保护您的应用程序?你强迫我使用它们吗?除了PHP之外的其他语言呢?我更愿意提供一般性的想法,因为它可以用于更广泛的边界,而不仅仅是特定的语言。
- SQL用户(限制用户权限):最常见的SQL操作是(SELECT,UPDATE,INSERT),那么,为什么要给不需要它的用户提供UPDATE权限?例如登录,搜索页面只使用SELECT,那么,为什么在这些具有高权限的页面中使用数据库用户呢?规则:不要为所有权限创建一个数据库用户,对于所有SQL操作,您可以创建方案(deluser,selectuser,updateuser)作为用户名以便于使用。
数据过滤:在构建任何查询之前,应验证和过滤用户输入,对于程序员来说,为每个用户输入变量定义一些属性非常重要:数据类型,数据模式和数据长度。对于作为字符串(文本)的字段,必须使用精确规则精确验证(x和y)之间的数字字段:模式就是这种情况,例如,用户名必须只包含一些字符,我们可以说[a- zA-Z0-9_-。]长度在(x和n)之间变化,其中x和n(整数,x <= n)。规则:为我创建精确的过滤器和验证规则是最佳实践。
使用其他工具:在这里,我也同意你那个准备好的声明(参数化查询)和存储过程,这里的缺点是这些方法需要大多数用户不存在的高级技能,这里的基本思路是区分SQL查询以及在内部使用的数据,即使对于不安全的数据,这两种方法也可以使用,因为这里的用户输入数据不会向原始查询添加任何内容,例如(any或x = x)。有关更多信息,请阅读OWASP SQL注入预防备忘单。
现在,如果您是高级用户,请根据需要开始使用此防御,但是,对于初学者,如果他们无法快速实现存储过程并准备语句,则最好尽可能多地过滤输入数据。
最后,让我们考虑用户在下面发送此文本而不是输入他的用户名:
[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'
可以在没有任何预处理语句和存储过程的情况下提前检查此输入,但为了安全起见,使用它们在用户数据过滤和验证之后启动。
最后一点是检测需要更多努力和复杂性的意外行为; 它不推荐用于普通的Web应用程序。上述用户输入中的意外行为是SELECT,UNION,IF,SUBSTRING,BENCHMARK,SHA,root一旦检测到这些单词,就可以避免输入。
UPDATE1:
一位用户评论说这篇文章没用,好吧!以下是OWASP.ORG提供的内容:
主要防御:
选项#1:准备语句的使用(参数化查询)
选项#2:使用存储过程
选项#3:转义所有用户提供的输入
附加防御:
同时强制执行:最小权限
也执行:白名单输入验证
您可能知道,声称文章应该得到有效论证的支持,至少有一个参考!否则,它被视为攻击和不良声明!
UPDATE2:
从PHP手册,PHP:准备语句 - 手册:
转义和SQL注入
绑定变量将由服务器自动转义。服务器在执行之前将其转义值插入到语句模板中的适当位置。必须为服务器提供绑定变量类型的提示,以创建适当的转换。有关更多信息,请参阅mysqli_stmt_bind_param()函数。
服务器内的值的自动转义有时被认为是防止SQL注入的安全功能。如果正确转义输入值,则可以使用非预准备语句实现相同程度的安全性。
UPDATE3:
我创建了一些测试用例,用于了解PDO和MySQLi在使用预准备语句时如何将查询发送到MySQL服务器:
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));
查询日志:
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();
查询日志:
188 Prepare SELECT * FROM awa_user WHERE username =? 188 Execute SELECT * FROM awa_user WHERE username ='\'\'1\'\'' 188 Quit
很明显,准备好的声明也在逃避数据,没有别的。
正如上面的声明中所提到的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
,因此,这证明了数据验证,例如intval()
在发送任何查询之前对整数值是一个好主意,此外,在发送查询之前防止恶意用户数据是正确和有效的方法。
请查看此问题以获取更多详细信息:PDO向MySQL发送原始查询,而Mysqli发送准备好的查询,两者都产生相同的结果
参考文献:
如果可能,请转换参数类型。但它只适用于简单类型,如int,bool和float。
$unsafe_variable = $_POST['user_id'];
$safe_variable = (int)$unsafe_variable ;
mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
如果您想利用Redis或Memcached等缓存引擎,DALMP可能是一种选择。它使用纯MySQLi。检查一下:使用PHP的MySQL的DALMP数据库抽象层。
此外,您可以在准备查询之前“准备”您的参数,以便您可以构建动态查询,并在最后具有完全准备好的语句查询。使用PHP的MySQL的DALMP数据库抽象层。
有许多方法可以防止SQL注入和其他SQL攻击。您可以在互联网上轻松找到它(谷歌搜索)。当然PDO是一个很好的解决方案。但我想建议你从SQL注入一些良好的链接预防。
以及其他一些像使用MySQL和PHP预防SQL注入
现在,为什么你需要阻止你的SQL注入查询?
我想告诉您:为什么我们尝试使用以下简短示例来阻止SQL注入:
查询登录验证匹配:
$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";
现在,如果有人(黑客)放弃
$_POST['email']= [email protected]' OR '1=1
和密码什么....
查询将被解析到系统中,最多只能:
$query="select * from users where email='[email protected]' OR '1=1';
另一部分将被丢弃。那么,会发生什么?未经授权的用户(黑客)将能够以管理员身份登录而无需密码。现在,他可以做管理/电子邮件人员可以做的任何事情。请注意,如果不阻止SQL注入,则非常危险。