2011-11-09 62 views
10

我一直在做的mysql_connect的简单连接,mysql_pconnect更换mysql_ *与PDO和准备语句的功能

$db = mysql_pconnect('*host*', '*user*', '*pass*'); 

if (!$db) { 
    echo("<strong>Error:</strong> Could not connect to the database!"); 
    exit; 
} 

mysql_select_db('*database*'); 

在使用这个,我一直用简单的方法来逃避作出之前的任何数据查询,不管是INSERTSELECTUPDATEDELETE使用mysql_real_escape_string

$name = $_POST['name']; 

$name = mysql_real_escape_string($name); 

$sql = mysql_query("SELECT * FROM `users` WHERE (`name` = '$name')") or die(mysql_error()); 

现在我明白了,这是安全的,在一定程度上!

它逃脱危险字符;然而,它仍然容易受到其他可能包含安全字符的攻击,但可能会对显示数据或在某些情况下恶意修改或删除数据有害。

因此,我搜索了一下,发现了PDO,MySQLi和准备好的语句。是的,我可能会迟到,但我已经阅读了许多教程(tizag,W3C,博客,谷歌搜索),并没有一个提到过这些。这似乎很奇怪,为什么只是逃避用户输入实际上是不安全的,并不是最好的说法。是的,我知道你可以使用正则表达式来解决这个问题,但是我相信这还不够?

据我的理解,当用户输入变量时,使用PDO /准备语句是一种更安全的方式来存储和检索数据库中的数据。唯一的问题是,切换(特别是在被困在我以前的编码方式/习惯之后)有点困难。

现在我明白,使用PDO连接到我的数据库,我会用

$hostname = '*host*'; 
$username = '*user*'; 
$password = '*pass*'; 
$database = '*database*' 

$dbh = new PDO("mysql:host=$hostname;dbname=$database", $username, $password); 

if ($dbh) { 
    echo 'Connected to database'; 
} else { 
    echo 'Could not connect to database'; 
} 

现在,函数名是不同的,因此不再将我mysql_querymysql_fetch_arraymysql_num_rows等工作。所以我不得不阅读/记住一大堆新的,但这是我感到困惑的地方。

如果我想从注册/注册表单中插入数据,我该如何去做这件事,但主要是我会如何安全地进行操作?我认为这是准备好的声明进来的地方,但通过使用它们,这消除了使用诸如mysql_real_escape_string之类的东西的需要吗?我知道mysql_real_escape_string要求你通过mysql_connect/mysql_pconnect连接到数据库,所以现在我们不使用这个函数会不会产生错误?

我也见过不同的方法来处理PDO方法,例如,我已经看到:variable?作为我认为被称为占位符的对象(抱歉,如果这是错误的)。

但我认为这是大致的应该做什么从数据库中提取用户

$user_id = $_GET['id']; // For example from a URL query string 

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id"); 

$stmt->bindParam(':user_id', $user_id, PDO::PARAM_INT); 

的想法,但然后我卡上有两件事情,如果变量是不是数字并且是一串文本,如果我没有弄错,你必须在PDO:PARAM_STR之后给出长度。但是,如果您不确定用户输入的数据给出的价值,您如何给定一定的长度,每次都会有所不同?无论哪种方式,据我所知,然后显示您的数据

$stmt->execute(); 

$result = $stmt->fetchAll(); 

// Either 

foreach($result as $row) { 
    echo $row['user_id'].'<br />'; 
    echo $row['user_name'].'<br />'; 
    echo $row['user_email']; 
} 

// Or 

foreach($result as $row) { 
    $user_id = $row['user_id']; 
    $user_name = $row['user_name']; 
    $user_email = $row['user_email']; 
} 

echo("".$user_id."<br />".$user_name."<br />".$user_email.""); 

现在,这一切都安全吗?

如果我是正确的,将插入数据是,例如相同的:

$username = $_POST['username']; 
$email = $_POST['email']; 

$stmt = $dbh->prepare("INSERT INTO `users` (username, email) 
         VALUES (:username, :email)"); 

$stmt->bindParam(':username, $username, PDO::PARAM_STR, ?_LENGTH_?); 
$stmt->bindParam(':email, $email, PDO::PARAM_STR, ?_LENGTH_?); 

$stmt->execute(); 
请问

的工作,并且是安全的吗?如果这是正确的,我会为?_LENGTH_?输入什么值?我完全错了吗?

UPDATE

我已经到目前为止一直非常有帮助的答复,不知道怎么感谢你们够了!每个人都有一个+1来打开我的眼睛有点不同。选择最好的答案很困难,但我认为Col Shrapnel值得拥有,因为所有内容都被覆盖了,甚至进入其他具有我不知道的自定义库的数组!

但由于大家:)

+0

也许“用户名”和“电子邮件”字段的长度?例如。如果用户名是varchar(32),那么长度参数应该是32. – deejayy

回答

12

感谢您的有趣问题。在这里你去:

它会把危险人物,

你的观念是完全错误的
其实“危险人物”是一个神话,没有。 和mysql_real_escape_string转义,但仅仅是字符串分隔符。从这个定义可以得出结论 - 它只适用于字符串

但是,它仍然容易受到其他可能包含安全字符但可能会损害显示数据或在某些情况下恶意修改或删除数据的攻击。

你在这里混合一切。
说到数据库,

  • 的字符串是不容易。只要你的字符串被引用和转义,他们不能“恶意修改或删除数据”。 *
  • 其他数据类型数据 - 是的,它的没用。但不是因为它有点“不安全”,而仅仅是因为使用不当。

对于显示的数据,我想这是在PDO相关问题offtopic,为PDO无关或者与显示数据。

逃逸用户输入

^^^要注意的另一个妄想!

  • 用户输入有绝对无关,与逃逸。正如你可以从前一个定义中学到的,你必须避免字符串,而不是“用户输入”。因此,再次:

    • 你有逃生的字符串,没有它们的来源此事
    • 是没用的,逃避其他类型的数据,没有来源的问题。

明白了吗?
现在,我希望你明白逃跑的局限性以及“危险人物”的误解。

这是我的理解是使用PDO /准备语句是一个更安全的

不是真的。
事实上,有不同的查询部分,我们可以动态地添加到它:

  • 字符串
  • 若干
  • 标识符
  • 语法关键字。

所以,你可以看到转义只涵盖了一个问题。(但是,当适用,你可以,当然,如果你把数字作为字符串(将它们放在引号),他们的安全以及)

而准备的语句覆盖 - 唉 - 整个2组的所有主题!大不了;-)

对于其他2个问题看我前面的回答,In PHP when submitting strings to the database should I take care of illegal characters using htmlspecialchars() or use a regular expression?

现在,函数名是不同的,因此不再将我的mysql_query,mysql_fetch_array,mysql_num_rows等工作。

这是另一个,PHP的严重妄想用户自然灾害,灾难:

即使使用旧版本的MySQL驱动程序时,一个永远不应该在他们的代码中使用裸API函数 !人们必须把它们放在一些图书馆的日常使用功能中! (不是一些魔法仪式,只是为了缩短代码,减少重复,防错,更一致和可读)。

对于PDO也是一样!

现在再次提出您的问题。

但通过使用它们,这是否消除了使用类似mysql_real_escape_string的需求?

是的。

但我认为这是大致的应该做什么从数据库中提取用户

不去取的想法,但到任何数据添加到查询

你必须给PDO后的长度:PARAM_STR如果我没有记错

可以,但你不必。

现在,这一切安全吗?

就数据库安全性而言,此代码中没有任何弱点。没有什么可以保护的。

用于显示安全性 - 只需在此网站上搜索XSS关键字即可。

希望我对此有所了解。

BTW,为长刀片,你可以做一些利用我哪天写的功能,Insert/update helper function using PDO

不过,我没有使用准备好的语句的时刻,因为我更喜欢我自己酿制的占位符超过他们,利用图书馆我上面提到过。所以,对付发表如下里哈的代码,这将是尽可能短,这两条线:

$sql = 'SELECT * FROM `users` WHERE `name`=?s AND `type`=?s AND `active`=?i'; 
$data = $db->getRow($sql,$_GET['name'],'admin',1); 

但是,当然,你可以使用预处理语句,以及相同的代码。


* (yes I am aware of the Schiflett's scaring tales)

+0

我该如何标记答案作为最爱? – TehShrike

+0

谢谢:)现在,我想我通常会标记问题本身,如果我想为它添加书签 –

+0

为什么鼓励OP在使用OP时使用自编码库实际上问如何正确使用PDO?第一步首先出现,你知道吗?一个菜鸟(只知道有超过mysql_ *)应该在学习一个定制的数据库封装库之前学习基本的PDO用法。你的是PITA来阅读/理解/调试/修改。短代码并不总是最好的代码。 – riha

8

我从未与bindParam()或PARAM类型或长度打扰。

我只传递参数值的阵列来执行(),是这样的:

$stmt = $dbh->prepare("SELECT * FROM `users` WHERE `id` = :user_id"); 
$stmt->execute(array(':user_id' => $user_id)); 

$stmt = $dbh->prepare("INSERT INTO `users` (username, email) 
         VALUES (:username, :email)"); 
$stmt->execute(array(':username'=>$username, ':email'=>$email)); 

这是同样有效,并且更容易代码。

您可能也有兴趣在我的演示文稿SQL Injection Myths and Fallacies或我的书SQL Antipatterns: Avoiding the Pitfalls of Database Programming

+0

这也可以用?数组中的占位符呢?说'SELECT * FROM users WHERE name =? AND email =?“);'用'execute(array($ name,$ email));'或者在这种情况下不行? – Joe

+0

是的,您可以使用位置参数而不是命名参数,就像您展示的一样。 –

2

要回答长度问题,指定它是可选的,除非您绑定的参数是存储过程中的OUT参数,所以在大多数情况下您可以放心地忽略它。

就安全性而言,当您绑定参数时,在幕后执行转义。这是可能的,因为您在创建对象时必须创建数据库连接。您还可以防止SQL注入攻击,因为通过准备语句,您可以在用户输入可以接近它之前告诉数据库该语句的格式。举个例子:

$id = '1; MALICIOUS second STATEMENT'; 

mysql_query("SELECT * FROM `users` WHERE `id` = $id"); /* selects user with id 1 
                  and the executes the 
                  malicious second statement */ 

$stmt = $pdo->prepare("SELECT * FROM `users` WHERE `id` = ?") /* Tells DB to expect a 
                   single statement with 
                   a single parameter */ 
$stmt->execute(array($id)); /* selects user with id '1; MALICIOUS second 
           STATEMENT' i.e. returns empty set. */ 

因此,就安全性而言,上面的例子看起来很好。

最后,我同意单独的绑定参数是单调乏味的,并且与传递给PDOStatement-> execute()的数组一样有效(请参阅http://www.php.net/manual/en/pdostatement.execute.php)。

5

是,:什么是PDO命名占位符?是一个匿名占位符。它们允许你一个一个地绑定值,或者一次全部绑定值。

所以,基本上,这使得四个选项提供您的查询值。

逐个bindValue()

这只要你把它结合一个具体的价值,你的占位符。如果需要,您甚至可以绑定硬编码字符串,如bindValue(':something', 'foo')

提供参数类型是可选的(但建议)。但是,由于缺省值为PDO::PARAM_STR,因此只需在不是字符串时指定它即可。此外,PDO将照顾这里的长度 - 没有长度参数。

$sql = ' 
    SELECT * 
    FROM `users` 
    WHERE 
    `name` LIKE :name 
    AND `type` = :type 
    AND `active` = :active 
'; 
$stm = $db->prepare($sql); 

$stm->bindValue(':name', $_GET['name']); // PDO::PARAM_STR is the default and can be omitted. 
$stm->bindValue(':type', 'admin'); // This is not possible with bindParam(). 
$stm->bindValue(':active', 1, PDO::PARAM_INT); 

$stm->execute(); 
... 

我通常更喜欢这种方法。我发现它是最干净和最灵活的。

逐个bindParam()

一个变量被绑定到你的占位符当查询executed,而不是当bindParam()被调用时,将被读取。这可能是也可能不是你想要的。当你想用不同的值重复执行你的查询时,它会派上用场。

$sql = 'SELECT * FROM `users` WHERE `id` = :id'; 
$stm = $db->prepare($sql); 
$id = 0; 
$stm->bindParam(':id', $id, PDO::PARAM_INT); 

$userids = array(2, 7, 8, 9, 10); 
foreach ($userids as $userid) { 
    $id = $userid; 
    $stm->execute(); 
    ... 
} 

您只准备和绑定一次安全柜CPU周期。 :)

一次全部用指定的占位符

你只是在一个阵列下降到​​。每个键都是查询中的指定占位符(请参阅Bill Karwins的答案)。数组的顺序并不重要。

注意:使用这种方法,您无法为PDO提供数据类型提示(PDO :: PARAM_INT等)。 AFAIK,PDO试图猜测。

一次全部用匿名占位符

也滴在数组的execute(),但它是数字索引(没有字符串键)。这些值将按照它们出现在查询/数组中的顺序逐个替换您的匿名占位符 - 第一个数组值将替换第一个占位符等等。看到erm410的答案。

与数组和命名占位符一样,您不能提供数据类型提示。

他们的共同点

  • 所有这些都需要你绑定/你有 占位符,提供尽可能多的价值是什么。如果你绑定太多/很少,PDO会吃掉你的孩子。
  • 您不必关心转义,PDO可以处理它。准备好的PDO语句是SQL注入安全的设计。但是,对于exec()query()来说这不是真的 - 通常应该只将这两个用于硬编码查询。

另请注意,PDO会抛出异常。这些可能会向用户透露潜在的敏感信息。您至少应该将您的初始PDO设置放入try/catch块

如果您不希望它稍后引发异常,则可以将错误模式设置为警告。

try { 
    $db = new PDO(...); 
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING) 
} catch (PDOException $e) { 
    echo 'Oops, something went wrong with the database connection.'; 
} 
+0

那么try/catch块与if/else语句相似吗?并感谢您指出不同的选项,这应该给我一些东西来玩! – Joe

+0

另一个快速问题,'$ e'用于什么,我假设你引用了它的错误? – Joe

+0

不完全。如果在try块的任何地方抛出异常,它会中止try块的执行并执行catch块。 $ e是抛出的异常。它包含错误消息/代码/文件/行等,可用于发送电子邮件到管理员或登录到文件,以便您可以调查出错的地方。总结:try/catch特别适合错误处理。什么是双关哈哈。 – riha