首頁 > MySQL, PHP > 我很納悶…怎麼一堆 PHP 程式設計師不知道要怎麼防 SQL Injection

我很納悶…怎麼一堆 PHP 程式設計師不知道要怎麼防 SQL Injection

2010年6月22日 發表評論 閱讀評論

從 5 月份到 6 月份這段期間面試了好多個 PHP 程式設計師 , 包括也曾開職缺面試資訊主管

我都會問一個問題 , SQL Injection 怎麼防 ?

我說真的 , 大概面試了 10 來位 , 沒有一個人能夠脫口而出 , 不知道 SQL Injection 的程式設計師不會就算了 , 知道的人居然沒有一個人知道 PHP 原本就有功能可以過濾掉特殊字元 , 甚至還會回答用 str_replace 方法來過濾 , 所以我很想給看到這篇的人知道

如果你是用 mysql function , 那就是用 mysql_real_escape_string 去過濾

如果你是用 PDO , 就是用 bind value 作法

例如

1
2
3
4
5
$sql = "SELECT * FROM users WHERE user_id=?";
 
$sth = $pdo->prepare($sql);
 
$sth->execute( array($user_id) );

其實我是已經受夠了 !! 不知道是台灣的書籍太爛 , 還是學校裡面老師教的太爛 , 這種最簡單的防止方式居然都沒人知道 ?

舉個 PHP 原文上的例子

1
2
3
4
5
6
7
8
9
10
11
12
<?php
// Query database to check if there are any matching users
$query = "SELECT * FROM users WHERE user='{$_POST['username']}' AND password='{$_POST['password']}'";
mysql_query($query);
 
// We didn't check $_POST['password'], it could be anything the user wanted! For example:
$_POST['username'] = 'aidan';
$_POST['password'] = "' OR ''='";
 
// This means the query sent to MySQL would be:
echo $query;
?>

上面的程式 , 會輸出

1
SELECT * FROM users WHERE user='aidan' AND password='' OR ''=''

那麼根本不用密碼就可以登入了 , 非常簡單的攻擊方式但卻又不得不重視 , 其實這種文字填空遊戲就是 SQL Injection ,

如果狠一點 , 還可以 DROP TABLE !!

Categories: MySQL, PHP Tags:
  1. shiang
    2010年9月17日11:24 | #1

    瞭解了,個人理解:最終的目地就是要把SQL指令中變數所帶入的內容完全變成字串(包含特殊符號)
    或者強制禁止只用任何特殊字元,至於方法就看個人經驗和功力了(要假設相當多的可能),
    保險起見可以多做幾道,甚至不排除過濾掉高風險的指令比如DROP(看情況拉)

  2. 2010年9月17日15:15 | #2

    @shiang
    我怎麼覺得你說的很複雜

    不論任何來源的字串只要有被 sql driver 專用的指令轉換過的字串就不會被組合另一種 SQL 命令
    mysql_real_escape_string
    sql_escape_string
    pg_escape_string
    PDO::quote
    …..

  3. Rie
    2010年10月15日19:33 | #3

    其實就算有bindValue()
    有時候還是有些東西不好搞
    比方這2個傢伙 %,_
    關於Like的運算符是無法處理的~"~
    所以在某些應用中為了閉開這個問題..不得加個自定函數
    public function escape_like_string($str)
    {
    while(strpos($str,’%')!==false or strpos($str,’_')!==false)
    {
    $str=str_replace(array(‘%’,'_’),",$str);
    }
    return $str;
    }
    雖然目前這樣處理,可是我總覺得事情不是這麼一回事..可能有些概念上沒轉過去吧?

  4. 2010年10月19日10:56 | #4

    @Rie

    確實 % 和 _ 在 bindValue 無法處理

    如果有一段 sql 是
    SELECT * FROM `users` WHERE `login` LIKE :keyword
    $keyword = ‘%’ . $_GET['keyword'] . ‘%’;
    如果不加以處理 , 萬一 $_GET['keyword'] 只輸入一個 %
    那麼整段 SQL 將會變成
    SELECT * FROM `users` WHERE `login` LIKE ‘%%%’
    那會變成所有資料都 SELECT 出來了 , 而非去搜尋字串中有 % 的資料
    所以這個確實要另外處理 , 不過通常會用到 like 主要是要去搜尋 ,
    這問題只會影響搜尋結果 , 一般人也不太注意這個就是

    但若資料很多的時候 , 被人家這麼搞法會把 CPU 操爆了 , 所以還是要去注意這個問題
    除非有要 DELETE FROM tablexxx WHERE columnxxx LIKE …. 或 Update
    真的有人會寫這樣的程式放在 Web 嗎 ??

  5. Rie
    2010年10月27日02:55 | #5

    @pigo
    前後都萬用符其實比較少見,因為大家知道前面放%的話mysql會無視索引遍歷資料表
    通常是用在select上沒錯
    比較忌諱的結果是這種型態
    $keyword = $_GET['keyword'] . ‘%’;
    SELECT * FROM `users` WHERE `login` LIKE :keyword
    同樣輸入%,結果雖然一樣,但這問題的真正意義在於,
    這樣設計的原本目的是希望能夠在使用keyword時,還能利用索引
    不得已還是得防阿 ~"~

  6. 2010年10月27日16:33 | #6

    @Rie
    資料量小用 LIKE 還 ok
    資料量大也沒有人用 LIKE 了 , 會不會用到索引都很慢 , 可以去試試 mobile01 的會員專用搜尋喔 , 設多個字串 會操掛
    有一些對 MySQL 的全文索引 plugin 應該是比較好的解決之道
    例如
    bigram : http://www.pigo.idv.tw/archives/353 , http://www.pigo.idv.tw/archives/340

    Sphinx 現在也有即時索引了 , Sphinx 我還沒空去好好把玩 , 很多深入的玩家都推薦這個的

  7. CHENG
    2010年10月29日17:34 | #7

    $sql = "SELECT * FROM users WHERE user_id=?";
    $sth = $pdo->prepare($sql);
    $sth->execute( array($user_id) );

    大大你好 我是在查詢PDO sql injection 防範時看到您的文章
    我已經爬過文大概了解PDO連線方式
    可是 我還是不知道為什麼 使用prepare()進行資料庫串列產生
    在使用execute進行資料綁定可以預防 sql injection
    因為我已經看過很多次 跟GOOGLE很多次
    還是沒有很了解 所以想說留言詢問
    是在做prepare()
    還是execute( )
    哪一個方法 可以預防sql injection
    我本身也很重視資訊安全 最近要開發一個專案所以努力在尋求這答案~
    感謝大大看完我留言~

  8. 2010年10月30日02:00 | #8

    @CHENG
    PDO 有兩個物件
    PDO 及 PDOStatment
    PDO 本身就可以執行 sql 了 , 就是 PDO::query()
    那就和原本的 mysql_query 一樣
    因此先回想 mysql_query() 怎麼使用
    mysql_query( "select * from users WHERE user=’" . $_POST['user'] . "‘");
    如果單純這樣寫 , 被攻擊是一定會的
    但如果
    mysql_query("select * from users WHERE user=’" .mysql_real_escape_string($_POST['user']) . "‘");
    就可以防止
    同理 , PDO::query() 可以搭配 PDO::quote() 等同上述效果

    至於為何有另一種 PDOStatment 物件的做法
    我個人已經不曉得到底這種做法是從那一種語言開始看到 , java jdbc 很早前就有用這種做法 , 早期的 PHP ADODB 套件就有導入這種做法
    好處就是你可以不用寫那麼多程式碼 , 至少可以不用下太多次 PDO::quote , 可以用陣列帶值進去取代就和 PDO::quote() 的效果可預防 sql injection 了

    另外 PDOStatment 也讓程式有比較方便或彈性的設計 , 你可能要仔細摸才能品味到

  9. CHENG
    2010年10月30日10:14 | #9

    經過看了6次您的回文 我稍微寫一下我的想法
    像您說的 已經被學校或者外面書本教育到使用傳統連線已經習慣~
    而且效率相當低 所以特別來找尋PDO相關的連線方式~

    所以我稍微的去GOOGLE 找斷一段語法
    // 組合 SQL 語法,取得符合 id = 2、name = ‘John’ 的資料
    $sth = $dbh->prepare(‘SELECT * FROM table WHERE id = :id AND name = :name’);
    $where = array(‘:id’ => 2, ‘:name’ => ‘John’);

    // 使用 execute(),會自動 quote $where 的參數
    $sth->execute($where);

    所以簡單講~假設使用 prepare() 是做為宣告此資料庫連線,
    然後使用 execute()做綁定動作時 execute()會自動quote傳入的變數達到簡短程式碼增加效率,

    因為小弟目前是剛畢業的PHP 程式設計師,
    每天用最多的 就是資料庫連線, 所以最近把PDO物件導向化,
    希望把資料庫連線簡短且有效率,而不是寫得又臭又長又難維護,
    查到 SQL Injection才來詢問您,還希望大大有機會可以認識認識,
    因為對於台灣的資訊安全教育,我也真的很擔心所以目前在瘋狂的自我補強。

    且你上面提到的 PDOStatment 也讓程式有比較方便或彈性的設計
    其實我看到那段語法就想說 假設資料庫連線的宣告都先做成方法
    例如 要使用 User Table時就Query(In User,$sql);
    $sql=array(‘:id’ => 2, ‘:name’ => ‘John’);
    把$sql帶入In User Table 這樣去就可以在下次使用時隨意更換資料庫
    我這想法是不是就是你說的PDOStatment方便或彈性的設計嗎?

  10. 2010年10月30日11:15 | #10

    @CHENG
    是啊 , 有很多種用法 , 看人怎麼設計

    最簡單的做法 , 一個基礎的 db class , 裡面有個 $this->db , 然後其他的資料操作邏輯都可以寫成物件去繼承 db class , 就很好用了

  11. CHENG
    2010年10月30日13:05 | #11

    恩 我目前使用的方式就是您說的
    物件化 資料庫連線物件

    之後再使用那問件去執行其他方法 只是最近在想該怎樣做可以做到防sql injection
    且效能高 又不會很難維護的方法~ 目前已經有個大方向了~

  12. CHENG
    2010年11月9日13:41 | #12

    HI~我又來了 大大我遇到一個我無解的問題 想說可以詢問你看看~
    function NewSqlGo($SqlLink=NULL,$Parameters=NULL){
    try{
    $sth = $this->dbh->prepare($SqlLink);
    $sth->execute($Parameters);

    //print_r($sth->errorInfo()); //這裡可以輸出錯誤

    } catch (PDOException $e) {
    /*連線例外方法
    ===========================*/
    echo "例外";
    print_r( $e->errorInfo());
    }
    //$this->dbh =NULL;//關閉連線
    }

    這是我寫的一個連線方法 在最上面已經有產生資料庫連線
    然而我故意傳入一個錯誤的資料庫 然而只有在TRY 那邊使用 $sth->errorInfo()
    才會顯現錯誤語法~

    然後在例外處理部分使用$e去接 並把它導向errorInfo()要列印出錯誤
    結果
    不管是
    echo "例外"
    或者 $e->errorInfo() 都沒有顯現資料

    想說問大大能不能為我解惑~

  13. 2010年11月10日16:29 | #13

    @CHENG
    看看這篇就知道了
    http://www.php.net/manual/en/pdo.error-handling.php

    基本上 execute() 也有返回 bool 可以讓你判斷有沒有錯 , 但若妳要完全用 Exeception 就要如上述網址去改看看

  14. CHENG
    2010年11月11日02:56 | #14

    感謝大大提供全英文資訊~ 哈哈
    我對於英文部分真的還要繼續加強~~

    不管怎樣 感謝您~~~

  15. 2011年8月26日14:41 | #15

    哈阿哈哈
    以前學校沒教
    我是到恆逸上課才學的

    我去面試也有被問到這題
    考試也有考

    借轉喔!!!!

  16. 曼菲士
    2011年12月15日18:33 | #16

    我認為 把SQL injection 丟給PDO prepare處理
    並不算懂怎麼過濾
    真正的懂 是能用正規式 自己replace

  17. 2011年12月15日19:55 | #17

    to 曼菲士

    確實 PDO prepare 不是徹底防止 sql injection , 但確可以讓程式碼少寫 , 且效能也比較好 , 某些情形下再加以不同的正規式 replace 過濾甚至還要搭配過濾掉資料含有 HTML SCRIPT 的元件來防止寫入資料庫後造成 xss 攻擊 , 這些則是網站開發者隨年齡與經驗必須慢慢去體會的

評論分頁
1 2 820