2009-08-17 67 views
25

我是新來使用MySQL中準備的語句與PHP。我需要一些幫助,創建一個準備好的語句來檢索列。如何在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的聲明(如上),該準備?

BONUS:準備好的陳述也被假設爲提高速度。如果我只在頁面上使用三次或四次準備好的語句,它會提高整體速度嗎?

+1

可能重複[在PHP中停止SQL注入的最佳方法](http://stackoverflow.com/questions/60174/best-way-to-stop-sql-injection-in-php) – outis 2012-03-26 02:05:41

回答

45

下面是使用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); 
} 
+0

謝謝。我如何顯示此聲明的結果? 一般我使用mysql_fetch_row,這是否在這裏工作? – chris 2009-08-17 23:20:27

+0

已更新,以添加如何獲得結果的示例。 – Amber 2009-08-17 23:26:05

+0

另請注意,我的示例假定您的兩個參數都是整數 - 如果它們是字符串,則需要相應地將參數更改爲bind_param。 – Amber 2009-08-17 23:32:26

2

在PHP(或任何其他語言)中使用PHP進行安全性是一個很大的討論問題。這裏有幾個地方爲您挑選了一些偉大的祕訣:

兩個在我看來,最重要的項目是:

  • SQL注入:請務必使用PHP的mysql_real_escape_string()函數(或類似的函數)轉義所有查詢變量。
  • 輸入驗證:永遠不要相信用戶的輸入。請參閱this瞭解如何正確清理和驗證輸入的教程。
+1

mysql_real_escape_string不是很好,只能用於字串上下文。切勿使用它來嘗試和保護非字符串字段。請,請使用綁定參數優先於此黑名單字符串過濾創可貼。 – Cheekysoft 2009-08-18 08:50:42

+1

@James:輸入驗證鏈接非常有用。謝謝! – chris 2009-08-18 17:32:29

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

準備好的陳述aren' t由普通的舊式Mysql/PHP接口支持。您需要PDOmysqli。但是,如果您只是想在php的mysql_query手冊頁上替換佔位符check this comment

1

如果您打算使用mysqli - 這對我來說似乎是最好的解決方案 - 我強烈建議下載codesense_mysqli類的副本。

這是一個整潔的小類,它包裝起來,隱藏了大部分使用原始的mysqli這樣使用預處理語句只需要一兩行額外的在舊版本的MySQL/PHP接口

+0

這個類非常容易使用。我想知道爲什麼這樣的事情不是更受歡迎。 – chris 2009-08-18 17:31:28

7

我同意時累積的cruft其他幾個答案:

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

優秀的答案。你會如何建議我處理像「SELECT * FROM mytable WHERE textfield LIKE?」這樣的語句。我應該使用準備好的語句還是其他的東西? – chris 2009-08-18 22:00:22

+0

在這種特定情況下,您必須將模式內插到查詢字符串中:「'SELECT * FROM mytable WHERE textfield LIKE'word%''」。這樣優化器就可以知道它可以使用索引(當然,如果你的模式以通配符開頭,索引無論如何都沒有好處)。 – 2009-08-18 22:15:07