如何在php中创建一个安全的mysql预处理语句?

时间:2009-08-17 23:06:43

标签: php mysql security

我是新手在php中使用mysql中的预处理语句。我需要一些帮助来创建一个准备好的语句来检索列。

我需要从不同的列中获取信息。目前,对于测试文件,我使用完全不安全的 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()); 

有人可以帮助我使用已准备好的url参数(如上所述)的输入创建安全 mysql语句吗?

奖金:准备好的陈述也可以提高速度。如果我只在页面上使用三到四次准备好的声明,它会提高整体速度吗?

7 个答案:

答案 0 :(得分:46)

这是一个使用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);
}

答案 1 :(得分:11)

你可以这样写:

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

答案 2 :(得分:8)

我同意其他几个答案:

  • 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 ?
    

答案 3 :(得分:2)

PHP中的MySQL(或其他任何语言)的安全性是一个被广泛讨论的问题。以下是一些可以获得一些好建议的地方:

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

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

答案 4 :(得分:1)

普通的Mysql / PHP界面不支持预编译语句。您需要PDOmysqli。但是如果你只想在php的mysql_query手册页上替换占位符check this comment

答案 5 :(得分:1)

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

这是一个整洁的小类,它包含并隐藏了使用原始mysqli时累积的大部分内容,因此使用预准备语句只需要在旧的mysql / php界面上多花一两行

答案 6 :(得分:0)

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

/**
* 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']));