2011-11-09 60 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