php操作mysql防止sql注入(合集)

DanaSwan發表於2018-08-31

本文將從sql注入風險說起,並且比較addslashes、mysql_escape_string、mysql_real_escape_string、mysqli和pdo的預處理的區別。

當一個變數從表單傳入到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表被刪掉了。

這是一種常見的sql注入方法,那麼在程式中,應該怎樣預防呢?

1.魔術引用 (推薦指數3)
addslashes()與stripslashes()是功能相反的函式。
addslashes()用於對變數中的' " 和NULL新增斜槓,用於避免傳入sql語句的引數格式錯誤,同時如果有人注入子查詢,通過加可以將引數解釋為內容,而非執行語句,避免被mysql執行。

不過,addslashes()新增的只在php中使用,並不會寫入mysql中。
那麼,tripslashes()的作用是將加了的php變數去掉,由於不會寫入mysql中,所以從mysql查詢出來的內容不需要再tripslashes()。

在防注入方面,addslashes()可以防止掉大多數的注入,但是此函式並不會檢查變數的編碼,當使用例如中文gbk的時候,由於長度比較長 ,會將某些gbk編碼解釋成兩個ascii編碼,造成新的注入風險(俗稱寬位元組注入)。見下面2。

如果從網頁表單、php、mysql都使用utf8編碼,則沒有這個問題。
基於此函式的風險,並不建議使用,推薦使用下面3中的方法。
https://segmentfault.com/q/10...

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的預處理-引數化查詢。它的查詢方法是:

  1. 先預傳送一個sql模板過去
  2. 再向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...

  1. html輸出與防止xss注入
    特殊字元輸出

比如' " < >有著特殊的意義,如果直接寫到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步一樣。

來源:http://www.yunxi365.cn/index....

相關文章