php防注入函数 如何在PHP中阻止SQL注入?




php防注入函数 (23)

使用预准备语句和参数化查询。 这些是由数据库服务器与任何参数分开发送和解析的SQL语句。 这样攻击者就无法注入恶意SQL。

你基本上有两个选项来实现这个目标:

  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); // '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查询,则应用程序容易受到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;--')

可以采取哪些措施来防止这种情况发生?


使用PDOMYSQLi是防止SQL注入的一种很好的做法,但是如果你真的想使用MySQL函数和查询,那么最好使用它

mysql_real_escape_string()

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

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

is_string

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

is_numeric

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

使用这些函数来检查输入数据要好得多mysql_real_escape_string


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

  1. 使用的mysql_real_escape_string(),这是一个预先定义的函数PHP,这代码添加反斜杠以下字符:\x00\n\r\'"\x1a。将输入值作为参数传递,以最大限度地减少SQL注入的可能性。
  2. 最先进的方法是使用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");

如果可能,请转换参数类型。但它只适用于简单类型,如int,bool和float。

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

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

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

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

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


一些在SQL语句中转义特殊字符的准则。

不要使用MySQL,不推荐使用此扩展,使用MySQLiPDO

库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_preparemysqli_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

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


可以通过在数据库本身中授予适当的权限来解决此问题的简单替代方法。例如:如果您使用的是mysql数据库,那么通过终端或提供的UI进入数据库,只需按照以下命令:

 GRANT SELECT, INSERT, DELETE ON database TO [email protected]'localhost' IDENTIFIED BY 'password';

这将限制用户仅受限于指定的查询。删除删除权限,因此永远不会从从php页面触发的查询中删除数据。第二件事是刷新权限,以便mysql刷新权限和更新。

FLUSH PRIVILEGES; 

有关flush更多信息。

要查看用户的当前权限,请触发以下查询。

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

了解有关GRANT更多信息。


无论你最终使用什么,请确保你检查你的输入还没有被magic_quotes或其他一些善意的垃圾所破坏,如果有必要,可以通过stripslashes或其他任何消毒来清理它。


从安全的角度来看,我赞成存储过程MySQL自5.0以来一直支持存储过程) - 优点是 -

  1. 大多数数据库(包括MySQL)都允许将用户访问限制为执行存储过程。细粒度的安全访问控制对于防止特权攻击升级非常有用。这可以防止受损应用程序直接对数据库运行SQL。
  2. 它们从应用程序中抽象出原始SQL查询,因此应用程序可以使用较少的数据库结构信息。这使人们更难理解数据库的底层结构并设计合适的攻击。
  3. 它们只接受参数,因此参数化查询的优点就在于此。当然 - IMO你仍然需要清理你的输入 - 特别是如果你在存储过程中使用动态SQL。

缺点是 -

  1. 它们(存储过程)很难维护,并且很快就会繁殖。这使得管理它们成为一个问题。
  2. 它们不太适合动态查询 - 如果它们构建为接受动态代码作为参数,则许多优点被否定。

一个好主意是使用像Idiorm这样的“对象 - 关系映射器”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;
}

它不仅可以避免SQL注入,还可以避免语法错误!还支持使用方法链接的模型集合,以便一次过滤或将操作应用于多个结果和多个连接。


对于那些不确定如何使用PDO(来自mysql_函数)的人,我制作了一个非常非常简单的PDO包装器,它是一个单独的文件。它的存在表明,完成应用程序所需的所有常见事务是多么容易。适用于PostgreSQL,MySQL和SQLite。

基本上,PDO,了解如何在现实生活中使用PDO功能,以便以想要的格式存储和检索值。

我想要一个专栏

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

我想要一个数组(key => value)结果(即用于制作一个选择框)

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

我想要一行结果

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

我想要一系列结果

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

使用这个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注入的最佳方法是使用Prepared Statements 而不是escaping ,正如接受的答案所示。

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

注入预防 - mysql_real_escape_string()

PHP有一个特殊的功能来防止这些攻击。 您需要做的就是使用一个函数mysql_real_escape_string

mysql_real_escape_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;

您可以在MySQL中找到更多详细信息- SQL注入预防


**警告:本回答中描述的方法仅适用于非常特定的场景,并且不安全,因为SQL注入攻击不仅依赖于能够注入X=Y。**

如果攻击者试图通过PHP的$_GET变量或URL的查询字符串来攻击表单,那么如果它们不安全,您将能够捕获它们。

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

因为1=12=21=22=11+1=2,等...都是攻击者的SQL数据库中的常见问题。也许许多黑客应用程序也使用它。

但是您必须小心,不得从您的站点重写安全查询。上面的代码给你一个提示,重写或重定向(这取决于你)特定黑客的动态查询字符串到一个页面,该页面将存储攻击者的IP地址,或者甚至是他们的COOKIES,历史,浏览器或任何其他敏感信息信息,以便您以后可以通过禁止他们的帐户或联系当局来处理它们。


我想如果有人想使用PHP和MySQL或其他一些dataBase服务器:

  1. 考虑学习PDO(PHP数据对象) - 它是一个数据库访问层,提供访问多个数据库的统一方法。
  2. 考虑学习MySQLi
  3. 使用本机PHP函数,例如:strip_tagsmysql_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%。

请测试您的数据库的每个查询 - 这是防止注入的更好方法。


使用PDO和准备好的查询。

$connPDO对象)

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

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

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如果替换包含这些令牌,则此版本不会导致问题。


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

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

准备好的陈述只涵盖其中两个。

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

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

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

$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

但是,还有另一种方法来保护标识符 - 转义。 只要你有一个引用的标识符,你可以通过加倍来逃避内部的反引号。

作为进一步的步骤,我们可以从准备好的语句中借用一些占位符(代表查询中的实际值的代理),并发明另一种类型的占位符 - 标识符占位符。

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

因此,一般性建议可能被表述为只要您使用占位符向查询添加动态部分(当然这些占位符已正确处理),您可以确保您的查询是安全的

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

更新

虽然对SQL注入保护的最佳实践达成了一致意见,但仍有许多不良做法。 其中一些根深蒂固的PHP用户心中。 例如,在这个页面上(虽然大多数访问者看不到) 超过80个已删除的答案 - 由于质量差或促进不良和过时的做法而被社区删除。 更糟糕的是,一些不好的答案不会被删除,而是会更加繁荣。

例如, there(1) are(2) still(3) many(4) answers(5) ,包括share建议你手动字符串转义 - 一种被证明是不安全的过时方法。

或者有一个稍微好一些的答案,它表明了另一种字符串格式化的方法,甚至将它作为最终的灵丹妙药。 当然,事实并非如此。 这种方法并不比常规字符串格式更好,但它保留了所有缺点:它仅适用于字符串,并且与任何其他手动格式一样,它基本上是可选的,非强制性的度量,容易出现任何类型的人为错误。

我认为所有这一切都是因为一个非常古老的迷信,由OWASPPHP手册等权威机构支持,它宣称“逃避”和防止SQL注入之间的平等。

无论PHP手册*_escape_string所说的是什么, *_escape_string决不会使数据安全,而且从来没有打算过。 除了对字符串以外的任何SQL部分无用之外,手动转义是错误的,因为它是手动的,与自动化相反。

并且OWASP使情况变得更糟,强调逃避用户输入这完全是胡说八道:在注入保护的背景下应该没有这样的词。 每个变量都有潜在危险 - 无论来源如何! 或者,换句话说 - 每个变量都必须正确格式化以便放入查询中 - 无论来源是什么。 这是重要的目的地。 在开发人员开始将绵羊与山羊分开的那一刻(考虑某个特定变量是否“安全”)他/她迈出了他/她迈向灾难的第一步。 更不用说即使是措辞也表明在入口点大量逃避,类似于非常神奇的引用功能 - 已经被鄙视,弃用和删除。

因此,与“逃避”无关,准备好的语句确实可以防止SQL注入(适用时)。

如果您仍然不相信,这里是我写的“Hitchhiker的SQL注入预防指南”的逐步解释,我详细解释了所有这些问题,甚至编写了一个完全致力于不良做法及其披露的部分。


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

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


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


安全警告 :此答案与安全最佳实践不符。 转义不足以防止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 . "')");

这不会解决所有问题,但它是一个非常好的垫脚石。 我省略了明显的项目,例如检查变量的存在,格式(数字,字母等)。







sql-injection