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

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

这是因为用户可以输入类似值的内容);DROP TABLE表;--,并且查询变为:

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

可以采取什么措施防止这种情况发生?


当前回答

我认为如果有人想使用PHP和MySQL或其他数据库服务器:

考虑一下学习PDO(PHP数据对象)——它是一个数据库访问层,提供了访问多个数据库的统一方法。考虑学习MySQLi


库示例:

----个人数字助理

-----没有占位符-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();

P.S:

PDO轻松赢得了这场战斗。支持12个不同的数据库驱动程序和命名参数,我们可以习惯它的API。从安全的角度来看,只要开发人员按照应该使用的方式使用它们,它们都是安全的

其他回答

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

基本上,在阅读手册的同时阅读它,了解如何在实际生活中使用PDO函数,从而使以所需格式存储和检索值变得简单。

我想要一列$count=数据库::列('SELECT count(*)FROM `user`');我想要一个数组(key=>value)结果(即,用于生成一个选择框)$pairs=数据库::pairs('SELECT `id',`username`FROM`user`');我想要单行结果$user=DB::行('SELECT*FROM`user`WHERE `id`=?',数组($user_id));我想要一系列结果$banned_users=DB::fetch('SELECT*FROM `user`WHERE `banned`=?',数组('RUE'));

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

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

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

而且,通过使用PDO而不是mysql_、mysqli_和pgsql_函数,可以使应用程序从数据库中抽象出一点,这是很少需要切换数据库提供程序的情况。

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

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

我的方法:

如果您希望输入是整数,请确保它真的是整数。在像PHP这样的变量类型语言中,这一点非常重要。例如,您可以使用这个非常简单但功能强大的解决方案:sprintf(“SELECT 1,2,3 FROM table WHERE 4=%u”,$input);如果你对整数十六进制有任何期望,如果你对它十六进制,你将完全逃避所有输入。在C/C++中有一个名为mysql_hex_string()的函数,在PHP中可以使用bin2hex()。不用担心转义字符串的大小是其原始长度的2倍,因为即使使用mysql_real_ascape_string,PHP也必须分配相同的容量((2*input_length)+1),这是相同的。这种十六进制方法通常在传输二进制数据时使用,但我认为没有理由不对所有数据使用它来防止SQL注入攻击。请注意,您必须在数据前面加上0x,或者改用MySQL函数UNHEX。

例如,查询:

SELECT password FROM users WHERE name = 'root';

将变成:

SELECT password FROM users WHERE name = 0x726f6f74;

or

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

Hex是完美的逃脱。无法注射。

UNHEX函数与0x前缀之间的差异

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

0x前缀只能用于char、varchar、text、block、binary等数据列。此外,如果要插入空字符串,则其使用有点复杂。您必须将其完全替换为“”,否则会出现错误。

UNHEX()适用于任何列;你不必担心空字符串。


十六进制方法通常用作攻击

注意,这种十六进制方法通常被用作SQL注入攻击,其中整数就像字符串一样,并且只使用mysql_real_aescape_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 SELECT column_name FROM information_schema.column WHERE table_name = __0x61727469636c65__;

然后只需选择所需的数据。是不是很酷?

但如果可注入站点的编码器将其十六进制,则不可能进行注入,因为查询将如下所示:

SELECT ... WHERE id = UNHEX('2d312075...3635');

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

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#字符串中运行语句。格式如下:

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

考虑到变量类型,它会逃逸。若您试图参数化表、列名称,它将失败,因为它将每个字符串放在引号中,这是无效语法。

安全更新:以前的str_replace版本允许通过向用户数据中添加{#}标记进行注入。如果替换包含这些令牌,则preg_replace_callback版本不会导致问题。