如何從根本上防止 SQL 注入?

SQL注入導致的安全問題數不勝數,為什麼這麼多年來同樣的問題一再發生?

如果是因為SQL腳本拼接的原因,為什麼不在新的實現中採用api調用的方式來杜絕漏洞?

可能是問題描述不夠清晰,再補充一些吧。

樓主本人是懂得如何防止,而且參數化查詢這樣的方法本身也是很容易掌握的。

自己的疑問是,面對一個很容易解決,又有巨大危害的安全漏洞,靠用戶(開發者也是用戶)的自覺去避免,這樣的機制是不是很不合理?

各大資料庫廠商在自己的實現上面有沒有在這方面的做出一些努力?

如果說參數化查詢就可以解決SQL注入,那麼新的SQL標準里為什麼不使用參數化查詢作為查詢語句默認實現?是因為這樣的實現會降低性能,還是說SQL語言本身的特性不容易去實現?或者是標準的制定者認為這個問題已經可以靠使用者自身的自我規範來避免,所以沒有必要去改進?


根本上杜絕的方案太多太簡單了,SQL注入這種攻擊手段說白了和直接把門踹開偷東西一樣Low,這麼多年一爆再爆我也很震精。不過想想其實都是一群小白拿著幾十年前的小白寫的東西改改用才會這樣的。

有人非說我沒回答問題,OK,根本的手段就是參數化查詢或者做詞法分析。


@pansz 的答案的確是說到了一個關鍵,也就是如果支持文本式的指令,那這事兒是沒有辦法杜絕的,只能在外面套上一個殼,也就是詞法分析。


但是現代資料庫卻不是只提供了SQL文本查詢,參數化查詢也是標準API之一,當然還有存儲過程,這些高級的API都可以有效的杜絕SQL注入這種問題。在MySQL都支持存儲過程的今天,還有這麼多注入漏洞實在是讓人費解。

不使用支持SQL的資料庫過於偏頗了,幾乎所有的關係型資料庫都支持SQL查詢,但也提供了安全的查詢方式。


SQL注入並不是一個在SQL內不可解決的問題,這種攻擊方式的存在也不能完全歸咎於SQL這種語言,因為注入的問題而放棄SQL這種方式也是因噎廢食。首先先說一個我在其他回答中也曾提到過的觀點:沒有(運行時)編譯,就沒有注入

SQL注入產生的原因,和棧溢出、XSS等很多其他的攻擊方法類似,就是未經檢查或者未經充分檢查用戶輸入數據意外變成了代碼被執行。針對於SQL注入,則是用戶提交的數據,被資料庫系統編譯而產生了開發者預期之外的動作。也就是,SQL注入是用戶輸入的數據,在拼接SQL語句的過程中,超越了數據本身,成為了SQL語句查詢邏輯的一部分,然後這樣被拼接出來的SQL語句被資料庫執行,產生了開發者預期之外的動作。

所以從根本上防止上述類型攻擊的手段,還是避免數據變成代碼被執行,時刻分清代碼和數據的界限。而具體到SQL注入來說,被執行的惡意代碼是通過資料庫的SQL解釋引擎編譯得到的,所以只要避免用戶輸入的數據被資料庫系統編譯就可以了。

現在的資料庫系統都提供SQL語句的預編譯(prepare)和查詢參數綁定功能,在SQL語句中放置佔位符"?",然後將帶有佔位符的SQL語句傳給資料庫編譯,執行的時候才將用戶輸入的數據作為執行的參數傳給用戶。這樣的操作不僅使得SQL語句在書寫的時候不再需要拼接,看起來也更直接,而且用戶輸入的數據也沒有機會被送到資料庫的SQL解釋器被編譯執行,也不會越權變成代碼。

至於為什麼這種參數化的查詢方式沒有作為默認的使用方式,我想除了兼容老系統以外,直接使用SQL確實方便並且也有確定的使用場合。

多說一點,從代碼的角度來看,拼接SQL語句的做法也是不恰當的。

DELETE FROM planet WHERE name = "mercury";
DELETE FROM planet WHERE name = "venus";
DELETE FROM planet WHERE name = "earth";
DELETE FROM planet WHERE name = "mars";

我修改一下那個很經典的笑話:程序員不應該執行刪除地球這樣的SQL語句,而是寫刪除一個行星,然後將地球當作參數傳入。

$stmt = $mysqli-&>prepare("DELETE FROM planet WHERE name = ?");
$stmt-&>bind_param("s", "earth");
$stmt-&>execute();

更多閱讀:
MySQL :: MySQL 5.1 Reference Manual :: 13.5 SQL Syntax for Prepared Statements
Prepared statement


簡單,sql injection的原因就是用戶提交的參數可以入庫並且當做sql結構化查詢語句執行
根本辦法:杜絕用戶提交的參數入庫並且執行
具體做法:將查詢參數化或者詞法分析(一樓已說)但是如果不能做到的話,建議站庫分離(web伺服器和資料庫伺服器做隔離分開)waf什麼的都是扯球淡,只要心情好有時間都能繞,做cdn防護的雲waf更是扯淡,秒過(具體不好吐槽,怕做雲waf的大牛們噴我)
以上都是我編的,現在我編不下去了~


我給你說,大不了base64()一下再拼接,我放著讓他注,能注進算我輸

為什麼不用?

大部分人說效率

然而如今性能過剩的年代真的用不起?


樓主並不是問如何防止SQL注入的。 而是問這麼頻繁而簡單的問題為什麼一再發生?資料庫廠商是不是應該做些什麼。

首先,這並不是一個複雜的問題。其次,在這一點上資料庫廠商能做的已經都做了。 @槐磊 已經說的很清楚了。出問題的是plain sql。plain sql意味著資料庫廠商給了程序員100%的權利。如果要從資料庫廠商方面100%杜絕。就只能是禁止了plain sql。但這不可能,因為plain sql的需求是確實存在的。就像linux不給你root許可權一樣。

所以真正的問題在程序員方面。以我的經驗,有兩種比較典型的情況,

第一種:程序員太菜,太懶,太忙。由於各種大意,或者壓根不知道這麼一回事而導致的。通常來說,這類情況多輸出現在初級程序員身上。但是也有資深的程序員犯這種低級錯誤。人都有具體的情況。每天的休息狀況,情緒,狀態都不同。只要是人就難免有出錯的時候。

第二種:程序架構複雜,人數多,設計不夠好就會出問題。比如數據從前端到資料庫經歷的代碼是3個人寫的。每個人都可以處理,但由於溝通,設計不利。每個人都認為別人會處理。就過導致誰都沒處理。

我能想到的經常發生的,就是這兩種了。希望大家再補充。

由此可見,這個問題實際上不是一個資料庫的問題了。而是一個軟體開發管理問題了。所以,從這一點上來說。是不可能100%杜絕的。就像你不能要求程序員的代碼100%沒bug一樣。那如何解決的。我能想到的最好的辦法就是:

1,給出清晰地架構設計,明確驗證數據的地方。
2. 測試,測試,測試!!!
3. 對於項目經理來說,如果發生注入的情況了。要檢查發生的原因,根據具體情況,如果真是因為程序員出現了不應給的疏忽,並且經常出現的話。就真的要換人了。


SQL注入本質上是數據和語句的混淆。

在數據被拼入SQL模版變成SQL片段的時候,正是防止SQL注入漏洞產生的最佳時機,這時候什麼是數據什麼是語句分得最清楚,只要使用強類型轉化就可以避免數據和語句的混淆,而無需特徵匹配或語法分析。一但失去這個時機,紅豆和綠豆混在一起,再想摘出來就難了,難免會被繞過。

參數化只是捕捉這個時機並有效處理的最佳手段而已,這裡「最佳」是包含了有效性、成本、可控等若干方面的考量。看到很多人粗暴的將其理解為「參數化能防SQL注入」,然後舉各種反例,也挺無奈的。

相關討論中,很多人混淆了「抵禦SQL注入攻擊」和「防止SQL注入漏洞的產生」兩個概念,沒有做過企業級安全支持的人——無論他是其他領域多NB的專家,還是對安全一知半解的工程師——也很難真正理解後者的真實含義和必要性,而將其看作資料庫系統本身的弱點加以防禦,從而陷入特徵匹配的攻防糾纏中。

還有很多人混淆了「我如何防止SQL注入漏洞的產生」和「我如何避免公司里的工程師產生SQL注入漏洞」。刺客和軍官本身就是兩個職業,在高深的武藝、精良的裝備之外,還有很多諸如組織形式、流程、士氣、主要矛盾主要方面這樣的問題。作為一個公司級別的解決方案,如果在資料庫層面沒法保證讓程序員撒花的寫代碼也能避免SQL注入攻擊的話,那你就不得不去好好面對「我如何避免公司里的工程師產生SQL注入漏洞」的問題。工程師不可能都是安全方面的專家,你也不可能讓工程師寫代碼的時候始終崩著弦去考慮如何避免OWASP TOP 10這類問題,在這個前提下怎麼解決問題,似乎安全框架+編碼規範+白盒檢查成了最好的選擇。

SQL注入的危害性使得企業不得不追求徹底防禦,不能接受潛在的繞過風險。而徹底防禦又只能在生成SQL片段的時機實現,控制這個時機的是開發工程師而不是資料庫,因此這是個系統問題和管理問題,而不是技術問題,至少不是純粹的技術問題,特別不是SQL注入檢測技術的問題。

回到問題本身,SQL注入問題既不能「靠用戶(開發者也是用戶)的自覺去避免」,也不能完全脫離用戶(開發者也是用戶)而指望資料庫層面去避免。對於那些不了解SQL注入漏洞細節或不關心SQL注入漏洞或沒精力去關心SQL注入漏洞的工程師,你要給他們一條儘可能簡單可行透明的方案來避免SQL注入漏洞,告訴他這樣寫就可以了,這就是安全框架;然後告訴他或者讓他的老大告訴他你必須這樣寫,這就是安全編碼規範;然後你有手段在他沒有這樣寫的時候能夠檢查出來(這比檢查出漏洞要容易)並推動他改正,這就是白盒檢查。

我們現在的互聯網產品SQL注入漏洞仍然層出不窮,並不是這套思路有問題,相反恰恰是這套思路沒有完善。

一方面是框架方案本身不完善,以SQL注入漏洞為例,參數化是防SQL注入框架級方案的重要部分,但僅靠參數化沒法很好滿足開發過程中一些常見需求,如逗號分割的id列表問題、排序標記的問題等等(其實這些問題真要用參數化的方案解決也可以),使得開發更願意在這些地方使用非參數化或偽參數化的方法(比如拼接SQL片段後再把整個片段當作參數扔進去exec)。這些問題在參數化的基礎上,再加以改進,仍然守著拼接SQL片段時進行強類型轉換的思路,仍然是能很好解決的,也就是繼續完善參數化方案的問題,而不是看上去那樣「參數化解決不了問題」。

另一方面,安全編碼規範的制定、培訓、流程建設和實施保證上也做得遠遠不到位,開發leader們更希望後面的資料庫或者前面的安全防禦上能有手段去解決SQL注入問題,對於安全工程師來說,設置並維護幾個特徵串、語法分析場景也遠比做那些安全框架、編碼規範、白盒掃描來得要輕鬆實在,彼此在心照不宣中度過今天,自然不能指望明天能徹底踏實。


1. 選擇不用SQL;
2. 使用ibatis等框架預設的寫法;


要想解決這個問題很簡單,但都不是在資料庫層面的,通過編程框架或者詞法分析的方法本質上是在 SQL 之上再增加了一個適配層,用戶的輸入先經過了某個適配層的處理然後才進入 SQL 語句。而參數化查詢看起來是繞開了 SQL,但不能阻止程序員非要使用 SQL 進行查詢。

而這些方法都違背了題主問題的本意。題主的本意不是問如何解決 SQL 注入問題,而是問能否從資料庫自身的層面解決 SQL 注入問題,但這個問題在 SQL 內不可能解決

SQL 注入的本質在於它是一個語言而不是一個特定格式的API。所以你無法單獨的給它傳輸參數什麼的,只能整體的執行一條語句。只要用戶輸入的東西可以直接進入 SQL,一個元素就可以變成一條語句,那麼 SQL 注入就無法防止。

更明確一些,這個問題並不是 SQL 語言特有的,而是只要 API 是一個語言,這個問題就必然存在。除非這個資料庫的 API 並不以語言的方式提供,而是以某種不靈活不方便(但更安全)的方式。

我認為,要想完全從資料庫本身解決這個問題,唯一的方法只能是不用 SQL ,不使用支持 SQL 的資料庫,資料庫的 API 以某種並非文本語言的方式提供。——純粹資料庫的角度,只要用 SQL 語言發命令的這種方式仍然存在,你就沒法擋住手殘的程序員,除非完全禁止掉 SQL。


多年前寫過一篇 「Web安全之SQL注入攻擊技巧與防範」,今天回頭再看依然有價值,就偷個懶,直接貼過來了。

Web安全簡史

在Web1.0時代,人們更多是關注伺服器端動態腳本語言的安全問題,比如將一個可執行腳本(俗稱Webshell)通過腳本語言的漏洞上傳到伺服器上,從而獲得伺服器許可權。在Web發展初期,隨著動態腳本語言的發展和普及,以及早期工程師對安全問題認知不足導致很多」安全血案」的發生,至今仍然遺留下許多歷史問題,比如PHP語言至今仍然無法從語言本身杜絕「文件包含漏洞」(參見這裡),只能依靠工程師良好的代碼規範和安全意識。


伴隨著Web2.0、社交網路、微博等一系列新型互聯網產品的興起,基於Web環境的互聯網應用越來越廣泛,Web攻擊的手段也越來越多樣,Web安全史上的一個重要里程碑是大約1999年發現的SQL注入攻擊,之後的XSS,CSRF等攻擊手段愈發強大,Web攻擊的思路也從服務端轉向了客戶端,轉向了瀏覽器和用戶。

在安全領域,一般用帽子的顏色來比喻黑客的善與惡,白帽子是指那些工作在反黑客領域的技術專家,這個群體是」善」的的象徵;而黑帽子則是指那些利用黑客技術造成破壞甚至謀取私利造成犯罪的群體,他們是」惡」的代表。


「白帽子」和」黑帽子」是兩個完全對立的群體。對於黑帽子而言,他們只要找到系統的一個切入點就可以達到入侵破壞的目的,而白帽子必須將自己系統所有可能被突破的地方都設防,以保證系統的安全運行。


這看起來好像是不公平的,但是安全世界裡的規則就是這樣,可能我們的網站1000處都布防的很好,考慮的很周到,但是只要有一個地方疏忽了,攻擊者就會利用這個點進行突破,讓我們另外的1000處努力白費。


常見攻擊方式

一般說來,在Web安全領域,常見的攻擊方式大概有以下幾種:
1、SQL注入攻擊
2、跨站腳本攻擊 - XSS
3、跨站偽造請求攻擊 - CSRF
4、文件上傳漏洞攻擊
5、分散式拒絕服務攻擊 - DDOS

限於篇幅,本篇只討論SQL注入攻擊的技巧與防範。

SQL注入常見攻擊技巧

SQL注入攻擊是Web安全史上的一個重要里程碑,它從1999年首次進入人們的視線,至今已經有十幾年的歷史了,雖然我們現在已經有了很全面的防範對策,但是它的威力仍然不容小覷,SQL注入攻擊至今仍然是Web安全領域中的一個重要組成部分。

以PHP+MySQL為例,讓我們以一個Web網站中最基本的用戶系統來做實例演示,看看SQL注入究竟是怎麼發生的。


1、創建一個名為demo的資料庫:

CREATE DATABASE `demo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

2、創建一個名為user的數據表,並插入1條演示數據:

CREATE TABLE `demo`.`user` (
`uid` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT "用戶uid",
`username` VARCHAR( 20 ) NOT NULL COMMENT "用戶名",
`password` VARCHAR( 32 ) NOT NULL COMMENT "用戶密碼"
) ENGINE = INNODB;
INSERT INTO `demo`.`user` (`uid`, `username`, `password`) VALUES ("1", "plhwin", MD5("123456"));

實例一

通過傳入username參數,在頁面列印出這個會員的詳細信息,編寫 userinfo.php 程序代碼:

&multi_query($sql);
if($query){
do {
$result = $mysqli-&>store_result();
while($row = $result-&>fetch_assoc()){
$userinfo[] = $row;
}
if(!$mysqli-&>more_results()){
break;
}
} while ($mysqli-&>next_result());
}
}
echo "&

",print_r($userinfo, 1),"&";

上面這個程序要實現的功能是根據瀏覽器傳入的用戶名參數,在頁面上列印出這個用戶的詳細信息,程序寫的這麼複雜是因為我採用了mysqli的驅動,以便能使用到 multi_query 方法來支持同時執行多條SQL語句,這樣能更好的說明SQL注入攻擊的危害性。

假設我們可以通過 http://localhost/test/userinfo.php?username=plhwin 這個URL來訪問到具體某個會員的詳情,正常情況下,如果瀏覽器里傳入的username是合法的,那麼SQL語句會執行:

SELECT uid,username FROM user WHERE username="plhwin"

但是,如果用戶在瀏覽器里把傳入的username參數變為 plhwin";SHOW TABLES-- hack,也就是當URL變為 http://localhost/test/userinfo.php?username=plhwin";SHOW TABLES-- hack 的時候,此時我們程序實際執行的SQL語句變成了:

SELECT uid,username FROM user WHERE username="plhwin";SHOW TABLES-- hack"

注意:在MySQL中,最後連續的兩個減號表示忽略此SQL減號後面的語句,我本機的MySQL版本號為5.6.12,目前幾乎所有SQL注入實例都是直接採用兩個減號結尾,但是實際測試,這個版本號的MySQL要求兩個減號後面必須要有空格才能正常注入,而瀏覽器是會自動刪除掉URL尾部空格的,所以我們的注入會在兩個減號後面統一添加任意一個字元或單詞,本篇文章的SQL注入實例統一以 -- hack 結尾。


經過上面的SQL注入後,原本想要執行查詢會員詳情的SQL語句,此時還額外執行了 SHOW TABLES; 語句,這顯然不是開發者的本意,此時可以在瀏覽器里看到頁面的輸出:

Array
(
[0] =&> Array
(
[uid] =&> 1
[username] =&> plhwin
)
[1] =&> Array
(
[Tables_in_demo] =&> user
)
)

你能清晰的看到,除了會員的信息,資料庫表的名字user也被列印在了頁面上,如果作惡的黑客此時將參數換成 plhwin";DROP TABLE user-- hack,那將產生災難性的嚴重結果,當你在瀏覽器中執行 http://localhost/test/userinfo.php?username=plhwin";DROP TABLE user-- hack 這個URL後,你會發現整個 user 數據表都消失不見了。


通過上面的例子,大家已經認識到SQL注入攻擊的危害性,但是仍然會有人心存疑問,MySQL默認驅動的mysql_query方法現在已經不支持多條語句同時執行了,大部分開發者怎麼可能像上面的演示程序那樣又麻煩又不安全。


是的,在PHP程序中,MySQL是不允許在一個mysql_query中使用分號執行多SQL語句的,這使得很多開發者都認為MySQL本身就不允許多語句執行了,但實際上MySQL早在4.1版本就允許多語句執行,通過PHP的源代碼,我們發現其實只是PHP語言自身限制了這種用法,具體情況大家可以看看這篇文章「PHP+MySQL多語句執行」。


實例二

如果系統不允許同時執行多條SQL語句,那麼SQL注入攻擊是不是就不再這麼可怕呢?答案是否定的,我們仍然以上面的user數據表,用Web網站中常用的會員登錄系統來做另外一個場景實例,編寫程序login.php,代碼如下:

&",print_r($userinfo, 1),"&";
} else {
echo "用戶名不存在或密碼錯誤!";
}
}
?&>
&
&
&
&
&Web登錄系統SQL注入實例&
&

&
&

登錄帳號: &&
&

登錄密碼: &&
&

&
& &
&

此時如果輸入正確的用戶名 plhwin 和密碼 123456,執行的SQL語句為:

SELECT uid,username FROM user WHERE username="plhwin" AND password="e10adc3949ba59abbe56e057f20f883e"

上面語句沒有任何問題,可以看到頁面列印出了登錄成功後的會員信息,但如果有搗蛋鬼輸入的用戶名為 plhwin" AND 1=1-- hack,密碼隨意輸入,比如 aaaaaa,那麼拼接之後的SQL查詢語句就變成了如下內容:

SELECT uid,username FROM user WHERE username="plhwin" AND 1=1-- hack" AND password="0b4e7a0e5fe84ad35fb5f95b9ceeac79"

執行上面的SQL語句,因為 1=1 是永遠成立的條件,這意味著黑客只需要知道別人的會員名,無需知道密碼就能順利登錄到系統。


如何確定SQL注入漏洞

通過以上的實例,我們仍然還會有疑問:黑客並不知道我們程序代碼的邏輯和SQL語句的寫法,他是如何確定一個網站是否存在SQL注入漏洞呢?一般說來有以下2種途徑:

1、錯誤提示

如果目標Web網站開啟了錯誤顯示,攻擊者就可以通過反覆調整發送的參數、查看頁面列印的錯誤信息,推測出Web網站使用的資料庫和開發語言等重要信息。

2、盲注

除非運維人員疏忽,否則大部分的Web運營網站應該都關閉了錯誤提示信息,此時攻擊者一般會採用盲注的技巧來進行反覆的嘗試判斷。 仍然以上面的數據表user為例,我們之前的查看會員詳情頁面的url地址為 userinfo.php?username=plhwin,此時黑客分別訪問 userinfo.php?username=plhwin" AND 1=1-- hack userinfo.php?username=plhwin" AND 1=2-- hack,如果前者訪問能返回正常的信息而後者不能,就基本可以判斷此網站存在SQL注入漏洞,因為後者的 1=2 這個表達式永遠不成立,所以即使username傳入了正確的參數也無法通過,由此可以推斷這個頁面存在SQL注入漏洞,並且可以通過username參數進行注入。

如何防禦SQL注入

對於伺服器配置層面的防範,應該保證生產環境的Webserver是關閉錯誤信息的,比如PHP在生產環境的配置文件php.ini中的display_errors應該設置為Off,這樣就關閉了錯誤提示,下面我們更多的從編碼的角度來看看如何防範SQL注入。


上面用兩個實例分析了SQL注入攻擊的技巧,可以看到,但凡有SQL注入漏洞的程序,都是因為程序要接受來自客戶端用戶輸入的變數或URL傳遞的參數,並且這個變數或參數是組成SQL語句的一部分,對於用戶輸入的內容或傳遞的參數,我們應該要時刻保持警惕,這是安全領域裡的「外部數據不可信任」的原則,縱觀Web安全領域的各種攻擊方式,大多數都是因為開發者違反了這個原則而導致的,所以自然能想到的,就是從變數的檢測、過濾、驗證下手,確保變數是開發者所預想的。


1、檢查變數數據類型和格式

如果你的SQL語句是類似where id={$id}這種形式,資料庫里所有的id都是數字,那麼就應該在SQL被執行前,檢查確保變數id是int類型;如果是接受郵箱,那就應該檢查並嚴格確保變數一定是郵箱的格式,其他的類型比如日期、時間等也是一個道理。總結起來:只要是有固定格式的變數,在SQL語句執行前,應該嚴格按照固定格式去檢查,確保變數是我們預想的格式,這樣很大程度上可以避免SQL注入攻擊。


比如,我們前面接受username參數例子中,我們的產品設計應該是在用戶註冊的一開始,就有一個用戶名的規則,比如5-20個字元,只能由大小寫字母、數字以及一些安全的符號組成,不包含特殊字元。此時我們應該有一個check_username的函數來進行統一的檢查。不過,仍然有很多例外情況並不能應用到這一準則,比如文章發布系統,評論系統等必須要允許用戶提交任意字元串的場景,這就需要採用過濾等其他方案了。


2、過濾特殊符號

對於無法確定固定格式的變數,一定要進行特殊符號過濾或轉義處理。以PHP為例,通常是採用addslashes函數,它會在指定的預定義字元前添加反斜杠轉義,這些預定義的字元是:單引號 (") 雙引號 (") 反斜杠 () NULL。


來看2條SQL語句:

$uid = isset($_GET["uid"]) ? $_GET["uid"] : 0;
$uid = addslashes(uid);
$sql = "SELECT uid,username FROM user WHERE uid="{$uid}"";

以及

$uid = isset($_GET["uid"]) ? $_GET["uid"] : 0;
$uid = addslashes(uid);
$sql = "SELECT uid,username FROM user WHERE uid={$uid}";

上面兩個查詢語句都經過了php的addslashes函數過濾轉義,但在安全性上卻大不相同,在MySQL中,對於int類型欄位的條件查詢,上面個語句的查詢效果完全一樣,由於第一句SQL的變數被單引號包含起來,SQL注入的時候,黑客面臨的首要問題是必須要先閉合前面的單引號,這樣才能使後面的語句作為SQL執行,並且還要注釋掉原SQL語句中的後面的單引號,這樣才可以成功注入,由於代碼里使用了addslashes函數,黑客的攻擊會無從下手,但第二句沒有用引號包含變數,那黑客也不用考慮去閉合、注釋,所以即便同樣採用addslashes轉義,也還是存在SQL攻擊漏洞。


對於PHP程序+MySQL構架的程序,在動態的SQL語句中,使用單引號把變數包含起來配合addslashes函數是應對SQL注入攻擊的有效手段,但這做的還不夠,像上面的2條SQL語句,根據「檢查數據類型」的原則,uid都應該經過intval函數格式為int型,這樣不僅能有效避免第二條語句的SQL注入漏洞,還能使得程序看起來更自然,尤其是在NoSQL(如MongoDB)中,變數類型一定要與欄位類型相匹配才可以。


從上面可以看出,第二個SQL語句是有漏洞的,不過由於使用了addslashes函數,你會發現黑客的攻擊語句也存在不能使用特殊符號的條件限制,類似where username="plhwin"這樣的攻擊語句是沒法執行的,但是黑客可以將字元串轉為16進位編碼數據或使用char函數進行轉化,同樣能達到相同的目的,如果對這部分內容感興趣,可以點擊這裡查看。而且由於SQL保留關鍵字,如「HAVING」、「ORDER BY」的存在,即使是基於黑白名單的過濾方法仍然會有或多或少問題,那麼是否還有其他方法來防禦SQL注入呢?


3、綁定變數,使用預編譯語句

MySQL的mysqli驅動提供了預編譯語句的支持,不同的程序語言,都分別有使用預編譯語句的方法,我們這裡仍然以PHP為例,編寫userinfo2.php代碼:

&prepare($sql);
//綁定變數
$stmt-&>bind_param("s", $username);
$stmt-&>execute();
$stmt-&>bind_result($uid, $username);
while ($stmt-&>fetch()) {
$row = array();
$row["uid"] = $uid;
$row["username"] = $username;
$userinfo[] = $row;
}
}
echo "&

",print_r($userinfo, 1),"&";

從上面的代碼可以看到,我們程序里並沒有使用addslashes函數,但是瀏覽器里運行 http://localhost/test/userinfo2.php?username=plhwin" AND 1=1-- hack 里得不到任何結果,說明SQL漏洞在這個程序里並不存在。


實際上,綁定變數使用預編譯語句是預防SQL注入的最佳方式,使用預編譯的SQL語句語義不會發生改變,在SQL語句中,變數用問號?表示,黑客即使本事再大,也無法改變SQL語句的結構,像上面例子中,username變數傳遞的 plhwin" AND 1=1-- hack 參數,也只會當作username字元串來解釋查詢,從根本上杜絕了SQL注入攻擊的發生。


資料庫信息加密安全

相信大家都還對2011年爆出的CSDN拖庫事件記憶猶新,這件事情導致CSDN處在風口浪尖被大家痛罵的原因就在於他們竟然明文存儲用戶的密碼,這引發了科技界對用戶信息安全尤其是密碼安全的強烈關注,我們在防範SQL注入的發生的同時,也應該未雨綢繆,說不定下一個被拖庫的就是你,誰知道呢。


在Web開發中,傳統的加解密大致可以分為三種:

1、對稱加密:

即加密方和解密方都使用相同的加密演算法和密鑰,這種方案的密鑰的保存非常關鍵,因為演算法是公開的,而密鑰是保密的,一旦密匙泄露,黑客仍然可以輕易解密。常見的對稱加密演算法有:AES、DES等。


2、非對稱加密:

即使用不同的密鑰來進行加解密,密鑰被分為公鑰和私鑰,用私鑰加密的數據必須使用公鑰來解密,同樣用公鑰加密的數據必須用對應的私鑰來解密,常見的非對稱加密演算法有:RSA等。


3、不可逆加密:

利用哈希演算法使數據加密之後無法解密回原數據,這樣的哈希演算法常用的有:md5、SHA-1等。


在我們上面登錄系統的示例代碼中,$md5password = md5($password); 從這句代碼可以看到採用了md5的不可逆加密演算法來存儲密碼,這也是多年來業界常用的密碼加密演算法,但是這仍然不安全。為什麼呢?


這是因為md5加密有一個特點:同樣的字元串經過md5哈希計算之後生成的加密字元串也是相同的,由於業界採用這種加密的方式由來已久,黑客們也準備了自己強大的md5彩虹表來逆向匹配加密前的字元串,這種用於逆向反推MD5加密的彩虹表在互聯網上隨處可見,在Google里使用md5 解密作為關鍵詞搜索,一下就能找到md5在線破解網站,把我們插入用戶數據時候的MD5加密字元串e10adc3949ba59abbe56e057f20f883e填入進去,瞬間就能得到加密前的密碼:123456。當然也並不是每一個都能成功,但可以肯定的是,這個彩虹表會越來越完善。


所以,我們有迫切的需求採用更好的方法對密碼數據進行不可逆加密,通常的做法是為每個用戶確定不同的密碼加鹽(salt)後,再混合用戶的真實密碼進行md5加密,如以下代碼:

&

小結

1、不要隨意開啟生產環境中Webserver的錯誤顯示。
2、永遠不要信任來自用戶端的變數輸入,有固定格式的變數一定要嚴格檢查對應的格式,沒有固定格式的變數需要對引號等特殊字元進行必要的過濾轉義。
3、使用預編譯綁定變數的SQL語句。
4、做好資料庫帳號許可權管理。
5、嚴格加密處理用戶的機密信息。


首先反對目前排名較高的 @pansz及 @vczh兩位對參數化查詢的理解
第一,參數化查詢是DB本身提供的功能
第二,各種語言的庫在實現參數化查詢時可以採用以下兩種策略,姑且稱之為

  • 真?參數化查詢:將帶參數的語句及參數分別發送給DB,這種情況能100%防注入,因為對於DB來說,參數的值絕對不會作為語義要素來解析,據我所知JDBC是這種實現方式
  • 偽?參數化查詢:將拼接以後的plain SQL發給DB,只不過在拼接過程中會進行校驗及轉義,據我所知PHP的PDO默認是採用這種實現,不過可以通過設置開關切換為真?參數化查詢,個人目前無法理解為什麼要放著DB本身提供的正確實現而兜個大圈子,而且這樣並不能完全避免風險,因為不能保證目前的校驗及轉義是否還有沒考慮到的情況

從兩位的回答來看貌似是認為DB沒有提供參數化查詢機制,語言層面的參數化查詢都是通過偽?參數化查詢來實現的
第三,回答題主的問題,答案是,不能,DB已經把能做的都做到了,一方面,DB已經提供了兩種查詢方式分別能正確地適用不同場景,但是DB不可能阻止程序層面錯誤地使用,首先,plain SQL有存在的必要,但是你無法識別程序送過來的plain SQL到底是寫死的還是通過拼接來的
結論,防止SQL注入的責任仍然是在程序這邊,應該正確地使用DB提供的機制

以下為新增,本來是打算回復 @吳永志的,不過後來覺得修改答案更合適

其實這就是我所想表達的重點,「資料庫提供的功能」這種說法可能會引入歧義,在此重新定義一下,所謂的「資料庫提供」指的是,資料庫服務端(嚴格來說是具體到SQL分析引擎)接收到的就是「帶參數的SQL語句+參數值」二者分離,與之相對的「非資料庫提供」指的是,資料庫服務端接收到的是一條單獨的SQL語句,其中的參數已經被處理成了具體的值,這個過程是由客戶端(可能是最終程序,也可能是某些框架,或者資料庫驅動)完成的,這二者是有本質區別的,在第一種實現中,SQL語法引擎明確地知道參數的位置是一個整體,不會再對其進行語法及語義處理,而第二種則不然,參數值仍然會被進行語法及語義處理,客戶端所做的就是通過驗證、轉義等方式盡量地避免參數被解析成語義動作,不同於第一種方式,沒有從根本上解決用戶的輸入可能被作為指令這一隱患

從題主的評論來看題主應該是清楚這個機制的,那現在我們來再次回答題主的問題,設想一下為了達到這個目標我所能想到的資料庫這邊可以做哪些嘗試(歡迎補充)
1. 廢棄plain SQL ,強制使用參數化查詢(並且是上述的真?參數化查詢)
這其實沒有解決任何問題,首先,使用plain SQL的需求是真實存在的,當然我們假設可以通過空的參數列表來變相進行plain SQL的查詢,那這和plain SQL又有何區別,其次,參數化查詢仍然是由「帶參數的SQL」和參數列表兩部分組成的,無法識別和避免程序員在第一部分採用拼接
2. 完全拋棄SQL,提供結構化的介面,例如類似select(tableName, fields, condition)這種機制
這會使得介面異常複雜,且不一定應付得了複雜查詢,想想LINQ吧

所以結論仍然是,資料庫本身在這個問題上已經做到足夠好了,前提是使用者正確地使用其提供的功能


這事從根本上說是人的問題,提供再好的api,二逼程序員都有辦法把它用爛。sql能注入,不用sql?成,那html注入又怎麼說?

根本辦法就是把人給換了。

對於任何一個合格的程序員來說,防止sql注入根本就不是一個事。


字元串拼sql的時候記得把所有參數都正確轉義就行了,如果是C#的話用SqlCommand裡面就自帶這個功能,絕對不會被注入。


完全沒有 @pansz 說的那麼複雜。


至於說,如果說參數化查詢就可以解決SQL注入,那麼新的SQL標準里為什麼不使用參數化查詢作為查詢語句默認實現?這跟SQL語言本身沒關係,因為靠譜的SQL API都提供了這樣的功能。雖然他沒辦法阻止你自己拼,但是至少你可以用它提供給你的拼接方法來正確組成SQL,譬如上面提到的SqlCommand。這就是所謂的參數化查詢了。


===========================================


話說回來,微軟官方推薦的用法都是,全部功能寫進stored procedure,然後只給server開放execute許可權來執行那些stored procedure,這樣你就什麼都不用擔心了。


補充一下 @徐明明。網上查詢了一下,不單單是Java的JDBC,通常關係型資料庫產品都會支持Prepared Statement這種特性(詳見wikipedia.org 的頁面)。也就是,含有佔位符的SQL語句先行一步傳遞到RDBMS上面進行預編譯,然後再傳遞實際的參數,最後執行SQL。這樣從根本上可以杜絕題主說的利用佔位符參數這一種SQL注入。

但是根據Are prepared statements 100% safe against SQL injection?上面回答,發現其實還有其他形式的可以被稱作為SQL注入的攻擊方式(如,緩存溢出、終止字元)是Prepared Statement無法解決的。


SQL注入根本就不是漏洞而是功能,這就像是操作系統的管理者許可權一樣

Windows 98 許可權系統脆弱
XP有許可權系統了,恩,結果有人用管理員賬戶裸奔
Vista 加了UAC,結果有人F*ck UAC
Windows 8 自帶防病毒,結果有人,下了來歷不明的軟體,被defender隔離然後關掉實時保護,被詢問UAC授權毫不猶豫就授權,結果這些人反過來說Windows太脆弱
想要安全去用ipad吧。。。

事實證明,開發者無論多麼關心安全性,總會有2B的用戶繞開各種限制把事情搞遭然後歸咎於開發者。

強制使用參數化查詢能解決問題?別逗了,我拼接完了然後傳個空參數進去不行?你要是說必須要有參數,那那些本來就不帶參數的語句怎麼辦?

====================================================================

你們以為廢掉SQL就行了?真是圖樣圖森破,自稱文藝的2B程序員會扔掉driver直接發byte-stream過去,這下無解了,你們感受一下


一句話,也是最終方案,參數綁定,prepare statement


1) 應用層面解決,對於根據用戶輸入拼接而成的sql,檢查用戶輸入,只保證合法sql通過(而不是過濾delete/drop等危險關鍵字)

2)在資料庫中限制用戶許可權,drop/create/truncate等許可權謹慎grant

3) 以prepared statement方式執行sql可以一定程度上防止sql注入

4)對於MySQL等開源資料庫,不允許不帶where條件(或者where條件恆為true)的delete和update,即safe update模式,通過定製patch的方式限制用戶修改的數據行數,在sql中增加hint傳遞期望修改數據的數目,如果真實更新數據與目標不符,回滾操作

5)備份很重要,即使發生這類悲劇,通過上一次備份重建資料庫和重放最近更新來挽救


nosql


不用SQL不就行了(光速逃


同意 @Cosmia Fu 的觀點。
「SQL注入根本就不是漏洞而是功能」。
SQL語言本身就是一串字元串,任何語言的資料庫連接API都必須提供直接運行SQL字元串的功能。所以,從根本上,這個功能是必須存在的,也就是說SQL注入根本無法防治。

所以,問題本身就是一個偽命題。
關鍵在於寫程序的人。
而要改變寫程序人的習慣,關鍵又在於語言學習的教學法。
想當初,無論ASP、PHP、JSP的教學文章,幾乎都是通過獲得URL中的參數,直接拼出SQL字元串,然後運行。
這就是錯誤的!在誤人子弟!

所以,要解決這個問題,只能一條路,就是告訴所有人,什麼才是正確的程序設計方法。
僅此而已。


SQL 注入其實是利用「歧義的攻擊手段:建造程序時候拼寫的 SQL 本意,和實際攻擊中生成的 SQL 不一樣。

PreparedStatement 僅僅是降低概率,並算不上根本上防止。如果 PreparedStatement 裡面寫出漏洞被利用呢?再比如 rails 的 ActiveRecord 這種 ORM/DAO 方案,還有一些敏感字元過濾方案,都是降低 SQL 注入的概率。

上面有人說廢掉 SQL 就如何如何,其實這種東西比如「對象資料庫」,「NOSQL」一直有人搞。問題是:你能保證新的 DB 交互方式中,也沒有「歧義」?有交互(哪怕不是基於文本)就有「歧義」,功能越強大複雜就越容易利用「歧義(即使把 ORM/DAO 方案搞的能全面封裝 SQL ,那他們出現漏洞的概率也一定增加)

簡而言之,利用「歧義的攻擊手段永遠都會存在。廢掉 SQL 也沒有用 - 他不再叫 SQL 注入了你的數據就安全了?

但是,今天從實際的應用看,清晰的編程規範,結對/自動化代碼 review ,加大量現成的解決方案(PreparedStatement,ActiveRecord,歧義字元過濾, 只可訪問存儲過程 balabala)已經讓 SQL 注入的風險變得非常低了。

今天如果被攻擊成功,恐怕更多的是意識,而非技術問題。


推薦閱讀:

為什麼知乎點擊登錄過後才填寫驗證碼?
怎樣防範被人肉搜索?
未來安全公司會消亡嗎?為什麼?
360在安全領域的真實實力是怎麼樣的?
我可以用新浪微博登錄第三方平台,是否意味著我的用戶名和密碼已經被新浪送給了第三方?

TAG:網路安全 | SQL注入 |