如何在 php 中创建安全的 mysql 准备语句?

Posted

技术标签:

【中文标题】如何在 php 中创建安全的 mysql 准备语句?【英文标题】:How to create a secure mysql prepared statement in php? 【发布时间】:2010-11-20 10:19:40 【问题描述】:

我不熟悉在 mysqlphp 中使用准备好的语句。我需要一些帮助来创建准备好的语句来检索列。

我需要从不同的列中获取信息。目前对于测试文件,我使用完全不安全 SQL 语句:

$qry = "SELECT * FROM mytable where userid='$_GET['userid']' AND category='$_GET['category']'ORDER BY id DESC"
$result = mysql_query($qry) or die(mysql_error()); 

有人可以帮我创建一个 secure 使用来自准备好的 url 参数(如上)的输入的 mysql 语句吗?

奖励:准备好的语句也可以提高速度。如果我在一个页面上只使用三到四次准备好的语句,它会提高整体速度吗?

【问题讨论】:

Best way to stop SQL Injection in PHP 的可能重复项 【参考方案1】:

这是一个使用 mysqli 的示例(对象语法 - 如果您愿意,可以很容易地转换为函数语法):

$db = new mysqli("host","user","pw","database");
$stmt = $db->prepare("SELECT * FROM mytable where userid=? AND category=? ORDER BY id DESC");
$stmt->bind_param('ii', intval($_GET['userid']), intval($_GET['category']));
$stmt->execute();

$stmt->store_result();
$stmt->bind_result($column1, $column2, $column3);

while($stmt->fetch())

    echo "col1=$column1, col2=$column2, col3=$column3 \n";


$stmt->close();

此外,如果您想要一种简单的方法来获取关联数组(用于 SELECT *),而不必准确指定要绑定的变量,这里有一个方便的函数:

function stmt_bind_assoc (&$stmt, &$out) 
    $data = mysqli_stmt_result_metadata($stmt);
    $fields = array();
    $out = array();

    $fields[0] = $stmt;
    $count = 1;

    while($field = mysqli_fetch_field($data)) 
        $fields[$count] = &$out[$field->name];
        $count++;
    
    call_user_func_array(mysqli_stmt_bind_result, $fields);

要使用它,只需调用它而不是调用 bind_result:

$stmt->store_result();

$resultrow = array();
stmt_bind_assoc($stmt, $resultrow);

while($stmt->fetch())

    print_r($resultrow);

【讨论】:

谢谢。如何显示此语句的结果?一般我用mysql_fetch_row,这里有用吗? 更新添加了如何获取结果的示例。 另请注意,我的示例假设您的两个参数都是整数 - 如果它们是字符串,您需要相应地将参数更改为 bind_param。【参考方案2】:

你可以这样写:

$qry = "SELECT * FROM mytable where userid='";
$qry.= mysql_real_escape_string($_GET['userid'])."' AND category='";
$qry.= mysql_real_escape_string($_GET['category'])."' ORDER BY id DESC";

但要使用准备好的语句,您最好使用通用库,例如​​ PDO

<?php
/* Execute a prepared statement by passing an array of values */
$sth = $dbh->prepare('SELECT * FROM mytable where userid=? and category=? 
                      order by id DESC');
$sth->execute(array($_GET['userid'],$_GET['category']));
//Consider a while and $sth->fetch() to fetch rows one by one
$allRows = $sth->fetchAll(); 
?>

或者,使用mysqli

<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "world");

/* check connection */
if (mysqli_connect_errno()) 
    printf("Connect failed: %s\n", mysqli_connect_error());
    exit();


$category = $_GET['category'];
$userid = $_GET['userid'];

/* create a prepared statement */
if ($stmt = mysqli_prepare($link, 'SELECT col1, col2 FROM mytable where 
                      userid=? and category=? order by id DESC')) 
    /* bind parameters for markers */
    /* Assumes userid is integer and category is string */
    mysqli_stmt_bind_param($stmt, "is", $userid, $category);  
    /* execute query */
    mysqli_stmt_execute($stmt);
    /* bind result variables */
    mysqli_stmt_bind_result($stmt, $col1, $col2);
    /* fetch value */
    mysqli_stmt_fetch($stmt);
    /* Alternative, use a while:
    while (mysqli_stmt_fetch($stmt)) 
        // use $col1 and $col2 
    
    */
    /* use $col1 and $col2 */
    echo "COL1: $col1 COL2: $col2\n";
    /* close statement */
    mysqli_stmt_close($stmt);


/* close connection */
mysqli_close($link);
?>

【讨论】:

【参考方案3】:

我同意其他几个答案:

PHP 的 ext/mysql 不支持参数化 SQL 语句。 查询参数被认为在防止 SQL 注入问题方面更可靠。 mysql_real_escape_string()如果使用得当也能发挥作用,但代码比较冗长。 在某些版本中,国际字符集包含未正确转义字符的情况,从而留下细微的漏洞。使用查询参数可以避免这些情况。

您还应该注意,即使使用查询参数,您仍然必须谨慎对待 SQL 注入,因为参数仅在 SQL 查询中代替文字值。如果您动态构建 SQL 查询并使用 PHP 变量作为表名、列名或 SQL 语法的任何其他部分,则查询参数和 mysql_real_escape_string() 在这种情况下都没有帮助。例如:

$query = "SELECT * FROM $the_table ORDER BY $some_column"; 

关于性能:

当您使用不同的参数值多次执行准备好的查询时,性能优势就会出现。您避免了解析和准备查询的开销。但是,您需要多久在同一个 PHP 请求中多次执行同一个 SQL 查询? 即使您可以利用这种性能优势,与您可以采取的许多其他提高性能的措施(例如有效地使用操作码缓存或数据缓存)相比,它通常也只是轻微的改进。

在某些情况下,准备好的查询损害性能。例如在以下情况下,优化器不能假定它可以使用索引进行搜索,因为它必须假定参数值可能以通配符开头:

SELECT * FROM mytable WHERE textfield LIKE ?

【讨论】:

优秀的答案。你会建议我如何处理像“SELECT * FROM mytable WHERE textfield LIKE?”这样的语句。我应该使用准备好的语句还是其他东西? 在这种特定情况下,您必须将模式插入到查询字符串中:“SELECT * FROM mytable WHERE textfield LIKE 'word%'”。这样优化器就可以判断它可以使用索引(当然,如果您的模式以通配符开头,那么索引无论如何都没有好处)。【参考方案4】:

在 PHP(或任何其他语言)中使用 MySQL 的安全性是一个被广泛讨论的问题。您可以从以下几个地方获得一些很棒的提示:

http://webmaster-forums.code-head.com/showthread.php?t=939 http://www.sitepoint.com/article/php-security-blunders/ http://dev.mysql.com/tech-resources/articles/guide-to-php-security.html http://www.scribd.com/doc/17638718/Module-11-PHP-MySQL-Database-Security-16

我认为最主要的两个项目是:

SQL 注入:确保使用 PHP 的 mysql_real_escape_string() 函数(或类似函数)转义所有查询变量。 输入验证:永远不要相信用户的输入。请参阅 this,获取有关如何正确清理和验证输入的教程。

【讨论】:

mysql_real_escape_string 不是很好,只能在字符串上下文中使用。切勿使用它来尝试保护非字符串字段。请,请,请使用绑定参数,而不是这个黑名单字符串过滤创可贴。【参考方案5】:

如果您要使用 mysqli - 这对我来说似乎是最好的解决方案 - 我强烈建议您下载 codesense_mysqli 类的副本。

这是一个简洁的小类,它封装并隐藏了使用原始 mysqli 时积累的大部分杂物,这样使用准备好的语句只需要比旧的 mysql/php 接口多出一两行

【讨论】:

该类非常易于使用。我想知道为什么这样的东西不受欢迎。【参考方案6】:

很晚了,但这可能会对某人有所帮助:

/**
* Execute query method. Executes a user defined query
*
* @param string $query the query statement
* @param array(Indexed) $col_vars the column variables to replace parameters. The count value should equal the number of supplied parameters
*
* Note: Use parameters in the query then supply the respective replacement variables in the second method parameter. e.g. 'SELECT COUNT(*) as count FROM foo WHERE bar = ?'
*
* @return array
*/
function read_sql($query, $col_vars=null) 
    $conn = new mysqli('hostname', 'username', 'user_pass', 'dbname');
    $types = $variables = array();
    if (isset($col_vars)) 
        for ($x=0; $x<count($col_vars); $x++) 
            switch (gettype($col_vars[$x])) 
                case 'integer':
                    array_push($types, 'i');
                        break;
                case 'double':
                    array_push($types, 'd');
                    break;
                default:
                    array_push($types, 's');
            
            array_push($variables, $col_vars[$x]);
        
        $types = implode('', $types);
        $sql = $conn->prepare($query);
        $sql -> bind_param($types, ...$variables);
        $sql -> execute();
        $results = $sql -> get_result();
        $sql -> close();
    else 
        $results = $conn->query($query) or die('Error: '.$conn->error);
    
    if ($results -> num_rows > 0) 
        while ($row = $results->fetch_assoc()) 
            $result[] = $row;
        
        return $result;
    else 
        return null;
    

然后你可以像这样调用函数:

read_sql('SELECT * FROM mytable where userid = ? AND category = ? ORDER BY id DESC', array($_GET['userid'], $_GET['category']));

【讨论】:

以上是关于如何在 php 中创建安全的 mysql 准备语句?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用多个 for 循环在 php 中创建 sql 语句

使用 PDO 准备好的语句 MySQL 错误

在 PHP 中创建安全密码哈希但检查 Access VBA

如何在 Laravel Controller 中创建 SQL Join 语句

如何找到在 MySQL 中创建视图的 SQL 语句?

如何使用 PHP 在 MySQL 中创建表?