因爲segmentfault在處理特殊字符時也並不是完美,因此下面文章中有些符號被轉換了,請到本人博客下載原文txt http://www.yunxi365.cn/blog/a... javascript
本文將從sql注入風險提及,而且比較addslashes、mysql_escape_string、mysql_real_escape_string、mysqli和pdo的預處理的區別。php
當一個變量從表單傳入到php,須要查詢mysql的話,須要進行處理。
舉例:
$unsafe_variable = $_POST['user_input'];
mysqli_query("INSERT INTO table (column) VALUES ('" . $unsafe_variable . "')");
用戶能夠輸入諸如 : value'); DROP TABLE table;-- ,SQL語句就變成這樣了:
INSERT INTO table (column) VALUES('value'); DROP TABLE table;--')
執行的結果就是table表被刪掉了。html
這是一種常見的sql注入方法,那麼在程序中,應該怎樣預防呢?java
1.魔術引用 (推薦指數3)
addslashes()與stripslashes()是功能相反的函數。
addslashes()用於對變量中的' " 和NULL添加斜槓,用於避免傳入sql語句的參數格式錯誤,同時若是有人注入子查詢,經過加能夠將參數解釋爲內容,而非執行語句,避免被mysql執行。mysql
不過,addslashes()添加的只在php中使用,並不會寫入mysql中。
那麼,tripslashes()的做用是將加了的php變量去掉,因爲不會寫入mysql中,因此從mysql查詢出來的內容不須要再tripslashes()。sql
在防注入方面,addslashes()能夠防止掉大多數的注入,可是此函數並不會檢查變量的編碼,當使用例如中文gbk的時候,因爲長度比較長 ,會將某些gbk編碼解釋成兩個ascii編碼,形成新的注入風險(俗稱寬字節注入)。見下面2。數據庫
若是從網頁表單、php、mysql都使用utf8編碼,則沒有這個問題。
基於此函數的風險,並不建議使用,推薦使用下面3中的方法。
https://segmentfault.com/q/10...segmentfault
2. mysql_real_escape_string() (推薦指數4)
因爲addslashes()不檢測字符集,因此有寬字節注入風險,因此php中添加了這個函數。
這個函數原本是mysql的擴展,可是因爲存在寬字節的問題,php基於mysql的擴展開發了此函數。跨域
gbk寬字符漏洞致使的sql注入
https://www.91ri.org/8611.html
http://www.cnblogs.com/suihui...數組
mysql_real_escape_chars()是mysql_escape_chars()的替代用法。
與addslashes()相比,不只會將' " NOL(ascii的0)轉義,還會把r n進行轉義。同時會檢測數據編碼。
按php官方的描述,此函數能夠安全的用於mysql。
此函數在使用時會使用於數據庫鏈接(由於要檢測字符集),並根據不一樣的字符集作不一樣的操做。若是當前鏈接不存在,剛會使用上一次的鏈接。
mysql_real_escape_string()防注入詳解
此方法在php5.5後不被建議使用,在php7中廢除。
參考:https://segmentfault.com/q/10...
3.預處理查詢 (Prepared Statements) (推薦指數5)
使用prepared statements(預處理語句)和參數化的查詢,能夠有效的防止sql注入。
爲何預處理和參數化查詢能夠防止sql注入呢?
在傳統的寫法中,sql查詢語句在程序中拼接,防注入(加斜槓)是在php中處理的,而後就發語句發送到mysql中,mysql其實沒有太好的辦法對傳進來的語句判斷哪些是正常的,哪些是惡意的,因此直接查詢的方法都有被注入的風險。
在mysql5.1後,提供了相似於jdbc的預處理-參數化查詢。它的查詢方法是:
a. 先預發送一個sql模板過去
b. 再向mysql發送須要查詢的參數
就好像填空題同樣,無論參數怎麼注入,mysql都能知道這是變量,不會作語義解析,起到防注入的效果,這是在mysql中完成的。
參考:
PHP中如何防止SQL注入
http://blog.csdn.net/sky_zhe/...
參數化查詢爲何可以防止SQL注入
http://www.cnblogs.com/LoveJe...
上面提供的資料比較多,下面根據本身的理解整理出來。
預處理分爲兩種:
A.使用mysqli:prepare()實現
看一個完整的用法:
$mysqli = new mysqli("example.com", "user", "password", "database");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = ?");
$stmt->bind_param(1, $city);
$stmt->execute();
$res = $stmt->get_result();
$row = $res->fetch_assoc();
a.寫sql語句,而後用?佔位符替代sql中的變量
b.替換變量
c.執行
d.獲得一個二進制結果集,從二進制結果中取出php結果集
e.遍歷結果集
使用預處理,一條查詢分兩步,因此很安全。也是php5.5及php7推薦方法。
參考:
http://www.cnblogs.com/liuzha...
B. 使用pdo實現
pdo是一個php官方推薦的數據庫抽象層,提供了不少實用的工具。
使用pdo的預處理-參數化查詢能夠有效防止sql注入。
使用方法跟上面差很少,區別在於pdo提供了更多樣的方法。
使用這個pdo->$stmt對象進行查詢後,會被結果集覆蓋,類型是一個二維數組。
咱們在上面預處理-參數化查詢是在mysql中進行防注入操做的,其實pdo也內置了一個預處理的模擬器,叫作ATTR_EMULATE_PREPARES。
默認狀況下,PDO會使用DSN中指定的字符集對輸入參數進行本地轉義(PHP手冊中稱爲native prepared statements),而後拼接成完整的SQL語句,發送給MySQL Server。這有些像咱們平時程序中拼接變量到SQL再執行查詢的形式。
這種狀況下,PDO驅動可否正確轉義輸入參數,是攔截SQL注入的關鍵。然而PHP 5.3.6及老版本,並不支持在DSN中定義charset屬性(會忽略之),這時若是使用PDO的本地轉義,仍然可能致使SQL注入,
若是ATTR_EMULATE_PREPARES=true(默認狀況),預處理-參數化查詢在pdo的模擬器中完成,模擬器根據字符集(dsn參數)進行處理,而後把語句發送給mysql。
若是ATTR_EMULATE_PREPARES=false,sql會分兩次把參數給送給mysql,mysql根據自身的字符集(set names <charset>)進行處理,完成查詢。
但因爲各版本差別,pdo在各版本中的實現程度也不同,有些版本還有bug,咱們以php5.3.6作爲分界線來進行說明:
php5.3.6如下版本
$pdo = new PDO("mysql:host=localhost;dbname=test;",'root','pwd');
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
$pdo->exec('set names utf8');
$id = '0 or 1 =1 order by id desc';
$sql = "select * from article where id = ?";
$statement = $pdo->prepare($sql);
$statement->bindParam(1, $id);
$statement->execute();
如上,咱們關閉了本地預處理模擬器,參數會直接分批發送給mysql,由mysql根據set name utf8字符集進行檢測,完成sql注入處理。以上代碼不會產生注入。
php5.3.6以上版本
$pdo = new PDO("mysql:host=localhost;dbname=test;charset=utf8",'root','pwd');
$pdo->exec('set names utf8');
$id = '0 or 1 =1 order by id desc';
$sql = "select * from article where id = ?";
$statement = $pdo->prepare($sql);
$statement->bindParam(1, $id);
$statement->execute();
在php5.3.6以上版本中,默認狀況下ATTR_EMULATE_PREPARES開啓,模擬器會根據new PDO()中的charset=utf8進行檢測,在模擬器上完成防注入操做。若是把模擬器關閉,也會像低版本同樣送交mysql進行防注入處理。
參考:
PDO防注入原理分析以及使用PDO的注意事項
http://zhangxugg-163-com.itey...
PHP 5.3.6及之前版本的PDO的bindParam,bindValue潛在的安全隱患
http://zhangxugg-163-com.itey...
再論php 5.3.6之前版本中的PDO SQL注入漏洞問題
http://my.oschina.net/zxu/blo...
segmentfault討論
https://segmentfault.com/q/10...
特殊字符輸出
好比' " < >有着特殊的意義,若是直接寫到html中輸出,會引發dom格式的錯亂,那麼就須要用到特殊的輸出方法。
htmlspecialchars()
用於將一些特殊符號轉義成只有瀏覽器識別的轉義符。
舉個例子:
$a = " ' ";
<input type="test" value='<?=$a;?>'>
<textarea><?=$a;?></textarea>
上面因爲$a的值就是一個' ,當它輸出在value=''之間時,會破壞html原有的dom格式,致使html解析錯誤。
下面那個'輸出在標籤對之間時沒有問題。
上面那個問題怎麼解決呢? 能夠這樣:
<input type="test" value='<?php echo htmlspecialchars($a);?>'>
php會向瀏覽器輸出:
<input type="test" value='&qouts;'>
這個符號只有流量器認識,源碼中看到是這樣,可是瀏覽器輸出的就是一個'號。
xss注入
xss也就是常說的跨域攻擊,這是一種在客戶端瀏覽器上面執行的攻擊。
好比在表單或者url參數中,人爲寫入javascript代碼,看起來是普通的文字,可是被瀏覽器解析後變成可執行的javascript動做,用來作廣告或者攻擊等等。
舉例:
有人在發貼的時候寫入了javascript代碼,一打開就彈窗口。
<script type="text/javascript">alert("你是個人小蘋果!");</scirpt>
對於這種惡意的東西,爲了力求安全,咱們便可以在發貼前對可用的html代碼進行過濾,也能夠用htmlspecialchars()進行轉義。
轉義後alert()內容變成:
alert("你是個人小蘋果!");
雖然看到的文字不變,可是因爲轉義了,這個alert()只會以文字顯示,而不會執行彈窗。
http://netsecurity.51cto.com/...
總結:
1.建議將升級到php 5.3.9+ php 5.4+,php 5.3.8存在致命的hash碰撞漏洞。
2.不要使用基礎的php拼接sql語句直接查詢,避免使用addslashes()和mysql_real_escape_string()防止注入。
3.推薦使用mysqli或pdo的預處理-參數化查詢。
4.pdo默認會使用自帶的ATTR_EMULATE_PREPARES模擬器處理sql語句,php5.3.6如下版本使用預處理-參數化時,設置dsn參數(setcahrs=utf8)沒用的(多是bug),因此防注入仍是有缺陷。應將模擬器關閉,pdo會把sql交由mysql進行防注入。
5.php5.3.6以上版本,應設置dsn參數(setchars=utf8),默認就能夠在模擬器防注入。也能夠手工關掉模擬器,效果跟上面第4步同樣。