2010-03-29 50 views
5

我正在構建一個數據庫類,並認爲將SQL注入預防(duh!)合併爲一個好主意。下面是運行一個數據庫查詢方法:防止SQL注入數據庫類

class DB 
{ 
    var $db_host = 'localhost'; 
    var $db_user = 'root'; 
    var $db_passwd = ''; 
    var $db_name = 'whatever'; 

    function query($sql) 
    { 
     $this->result = mysql_query($sql, $this->link); 
     if(!$this->result) 
     { 
      $this->error(mysql_error()); 
     } else { 
      return $this->result; 
     } 
    } 
} 

還有更多的類比,但我砍倒它只是這一點。我面臨的問題是,如果我只使用mysql_real_escape_string($sql, $this->link);,那麼它會轉義整個查詢並導致SQL語法錯誤。我如何動態查找需要轉義的變量?我想避免在我的主代碼塊中使用mysql_real_escape_string(),我寧願在函數中使用它。

謝謝。

+4

我知道我在你之前的帖子上說過,但讓我重複一遍:使用PDO。如果您使用PDO的準備好的語句,您甚至不必擔心逃脫它們。 – ryeguy 2010-03-29 19:44:23

+0

使用參數化的quires,使用adodb或pdo。 – rook 2010-03-29 21:28:43

回答

0

防止SQL注入攻擊的整個想法是阻止用戶運行自己的SQL。在這裏,似乎你想允許用戶運行他們自己的SQL,那麼爲什麼要限制它們呢?

或者如果您不允許用戶將SQL傳遞到query()方法。這太低而不能實現轉義參數。正如你所意識到的,你只想逃避參數,而不是整個SQL語句。

如果你參數化了SQL,那麼你可以轉義參數。在這種情況下,我假設用戶可以影響參數的值,而不是SQL。

看看PDO的bindParam()或Zend_DB的query()方法,以瞭解它是如何在其他數據庫接口中實現的。

+0

如果這將用於登錄表單等(它會),我不想相信用戶輸入非惡意的SQL。 – Joe 2010-03-29 19:31:52

+0

這顯然是他計劃在整個代碼中重用的一個類... – 2010-03-29 19:34:27

+0

「將用於登錄表單」?當心Bobby Tables ... http://xkcd.com/327/ – Simon 2010-03-29 19:46:24

0

要做到這一點正確的方法,您必須在構建查詢時清理這些內容,或者將其放入類中以傳入獨立於核心查詢的參數。

1

問題是,當您構建SQL查詢時,通過查找變量能夠阻止注入已爲時過晚 - 否則它已經內置到PHP中。

在構建查詢時,轉義需要早得多。您可以使用查詢構建類。

但是我建議一個不同的方法 - 其是具有提供一個數據庫表作爲一個對象的層,這裏是一個例子user object其從提供使用active record pattern到數據庫的完整的接口的base db entity class導出的,並且iterator pattern

我會用一些例子來說明這一點;這裏整潔的東西是迭代器,因爲你可以抽象出更多的東西,並且有一些相當通用的類可以進一步提取數據。

$user = new DbUser(); 
$user->create(); 
$user->set_email('[email protected]'); 
$user->write(); 

要讀取用戶記錄:

$user = new DbUser(); 
$user->set_email('[email protected]'); 
if ($user->load_from_fields()) 
{ 
} 

要通過記錄遍歷:

$user_iterator = DbUser::begin(); 
if ($user_iterator->begin()) 
{ 
    do 
    { 
     $user = $user_iterator->current(); 
     echo $user->get_email(); 
    } while ($user_iterator->next()); 
} 
+1

這不是一個壞主意。這裏的關鍵詞是「主動記錄模式」。 Zend Framework,Ruby on Rails和其他一些框架提供了這種類型的功能。 – 2010-03-29 19:48:54

+0

然後查看一些關於stackoverflow的orm/active記錄討論,例如http://stackoverflow.com/questions/494816/using-an-orm-or-plain-sql – VolkerK 2010-03-29 20:14:19

1

有兩種方法

要使用上述方法創建一個用戶記錄防止SQL注入攻擊:基於黑名單的方法和基於白名單的方法。

黑名單方法意味着您需要檢查整個查詢字符串並確定不需要的代碼並將其刪除。這非常困難。 而是使用參數化SQL使用白名單方法。通過這種方式,您將確保將執行的唯一查詢將是您有意使用代碼構建的查詢,並且任何注入嘗試都將失敗,因爲所有注入查詢都將成爲參數的一部分,因此不會由數據庫。 您正試圖找到一種方法來防止注入後查詢已建立,這間接意味着基於黑名單的方法。

嘗試在您的代碼中使用參數化SQL,這是其全球適應性安全編碼原則之一。

1

無論是參數化或嘗試建立DB類,以通爲WHERE(以及任何可以使用值)使用像所有的值:

$db->where(x,y); 

即。

$db->where('userid','22'); 

而且在類語料庫使用類似

function where(var x, var y) // method 
{ 
    $this->where .= x . ' = '.mysql_real_escape_string(y); 
} 

當然,這需要清洗,以支持多種輸入的地方。

0

我,我通過向查詢函數添加參數解決了這個問題。 我發現codeigniter做得非常好,所以我適應了自己的口味。

實施例:

$result = Database::query('INSERT INTO table (column1,column2,column3) VALUES(?,?,?)',array($value1,$value2,$value3)); 



public static $bind_marker = '?'; 
public static function query($query, $binds = FALSE) 
    { 
     if($binds !== FALSE) 
     { 
      $query = self::compile_binds($query,$binds); 
     } 
     // $query now should be safe to execute 
} 

private static function compile_binds($query, $binds) 
    { 
     if(strpos($query, self::$bind_marker) === FALSE) 
     { 
      return $query; 
     } 

     if(!is_array($binds)) 
     { 
      $binds = array($binds); 
     } 

     $segments = explode(self::$bind_marker, $query); 

     if(count($binds) >= count($segments)) 
     { 
      $binds = array_slice($binds, 0, count($segments)-1); 
     } 

     $result = $segments[0]; 
     $i = 0; 
     foreach($binds as $bind) 
     { 
      if(is_array($bind)) 
      { 
       $bind = self::sanitize($bind); 
       $result .= implode(',',$bind); 
      } 
      else 
      { 
       $result .= self::sanitize($bind); 
      } 

      $result .= $segments[++$i]; 
     } 

     return $result; 
    } 

public static function sanitize($variable) 
{ 
    if(is_array($variable)) 
    { 
     foreach($variable as &$value) 
     { 
      $value = self::sanitize($value); 
     } 
    } 
    elseif(is_string($variable)) 
    { 
     mysql_real_escape_string($variable); 
    } 
    return $variable; 
} 

主要此外,我從笨的版本添加的我可以使用的陣列作爲一個參數,其是用於使用「IN」有用:

$parameters = array 
    (
     'admin', 
     array(1,2,3,4,5) 
    ); 

$result = Database::query("SELECT * FROM table WHERE account_type = ? AND account_id IN (?)",$parameters); 
1

整點的代碼注入的預防是你想區分用戶注入的sql和sql。在這個最低級別你不能再這樣做了。讓我們舉一個例子:

select * from users where username='test' and password='itisme' or '4'='4' 

這似乎是完美有效的SQL,但它也可能是一個SQL注入的版本:

"select * from users where username='test' and password='" . "itisme' or '4'='4". "'" 

所以,你必須進一步做它在你的代碼,或使用包裝如其他人所建議的那樣。

0

爲什麼要重新發明輪子? 只需擴展PDO並使用參數化查詢。如果您不知道它是什麼,請閱讀包含大量示例的文檔以開始使用。