為什麼參數化SQL查詢可以防止SQL注入?
原理是採用了預編譯的方法,先將SQL語句中可被客戶端控制的參數集進行編譯,生成對應的臨時變數集,再使用對應的設置方法,為臨時變數集裡面的元素進行賦值,賦值函數setString(),會對傳入的參數進行強制類型檢查和安全檢查,所以就避免了SQL注入的產生。
最近在深入學習Java,附上一段實現代碼,其他語言把賦值函數的處理封裝起來了,導致用戶不可見,不能了解其中的機理。
import java.sql.PreparedStatement;
String sql = "select * from user where username=? and passwd=?";
ps = conn.PreparedStatement(sql);
ps.setString(1, "admin");
ps.setString(2, "123456");
resultSet = ps.executeQuery();
以前 WOW 有一個梗,法師在生產了一個魔法泉水的時候,隊員會有一個提示 「XXXX製造了魔法泉水」。
有一個貨起名叫 「處女一抬腿」...結果隊友出現的提示內容就是 「處女一抬腿製造了魔法泉水」 是不是有一種很惡的感覺?
這就是 SQL 注入的基本原理。那怎麼解決呢?用變數啊,比如,提示信息改成 「ID:1233456 製造了魔法泉水」,不就好了,管你叫什麼名字呢。你的參數化查詢,其實就是創建了一個變數,並且單獨為這個變數賦值。其實 @Xi Yang 的回答最直接,我的回答太表象.... 可能不是題主所想要的.... 還是匿了吧....都沒有回答的點兒上,部分答者只見樹木不見森林,為什麼這麼說呢,引用一個上邊其他人的回答,
賦值函數setString(),會對傳入的參數進行強制類型檢查和安全檢查,所以就避免了SQL注入的產生。
@王音引用王音的回答,乍眼一看,貌似說的很有道理,但是仔細想一想,就會發現漏洞,我con.prepareStatement生成預編譯對象後,通過給參數複製set, 這裡沒錯,但是完全依靠 setString,setInt 就可以完全規避sql注入了嗎,如果我參數類型是一個名字name,參數值 完全可以 張三『 or "1=1 這個值來進行攻擊,所以 ,使用預編譯 是可以防止sql注入,但是這種說法不是正確的原因。
我來解釋一下,如果有失偏頗,還請各位讀者指正,預編譯之所以能防止sql注入(我在這裡只說sql注入這方面),還要從sql的執行方式有關。一般來說,sql 是怎麼執行的呢?簡單說,一個sql 是經過解析器編譯並執行,注意這裡是一個並字。舉一個栗子,校驗有沒有這個用戶的場景sql, select count(1) from students where name="張三"
上邊的資料庫執行時,是直接將這句話連帶 name="張三",一起給編譯了,然後執行。假設我的sql 注入語句是
select count(1) from students where name="張三" or "1=1"
即name 參數為張三『 or "1=1 ,這個參數也會被編譯器一同編譯。
而使用預編譯,資料庫是怎麼處理的呢?這個步驟是在con.prepareStatement(「」)語句執行的時候,伺服器就這個sql發送給了資料庫,然後資料庫將該sql編譯後放入到緩存池中。等到伺服器執行execute的時候,傳給資料庫的 張三『 or "1=1 編譯器並不進行編譯,而是這找到原來的sql模板,傳參,執行。所以, 張三『 or "1=1 只會被資料庫當做參數來處理。我這樣解釋不知道大家是否明白?
因為客戶端沒有機會給你代碼,給你的東西永遠被視為數據。
具體來講,在沒有任何防護的情況下,我在網頁表單里給你填了個"; drop all;
,你想想會發生什麼?
SQL 語句文本對於資料庫來說,是一種指令,與 Shell 中輸入的一條條命令行很類似。我們在 SQL 中混入的各種值就是操作的參數。
考慮一個 WHERE user_id = 10 的篩選,WHERE 的條件包含兩個部分:按用戶篩選,以及用戶 id 的值,後者即為篩選操作的參數。
當用戶 id 直接混在 SQL 中,表示 id 值的文本作為 SQL 正文的一部分,就很容易被動手腳,攻擊者只要偽造一個令你最終也能符合語法的 偽裝值就行。
比如 WHERE user_id = 10;delete from xxxx
這樣,工程師直接拼到裡面之後,在執行引擎里,它分析我們的語句,發現是兩個子句。就會執行後面的注入的 SQL。
上述過程的問題在於,執行引擎理解到的 SQL 語義與我們要表達的不一致。我們隱含地意思是,在 WHERE user_id = 之後的值都被當作 user_id 的篩選條件,而不應該有其他語句出現。但是,基於 SQL 分析來執行數據交互,這種誤解是無法解除的,比如,上面的例子,偽裝之後,它也是個符合語法的 SQL。
參數化為我們提供了消除這種誤解的能力。在遇到參數時,不管輸入任何值,都將整個值作為參數的值,而不是原始 SQL 文本的一部分。
在上面的例子里,如果使用參數化 SQL,則 10;delete from xxxx 整體會作為用於比對的 user_id 值,顯然找不到。而且資料庫不會被注入,因為這時 delete from xxxx 是參與運算的值的一部分,而不是 SQL 操作指令的一部分。
其他答案里有提到,這是一種抽象。如果做過表達式解析的練習,你會發現,操作數與操作運算元是兩種不同性質的東西。
SQL 注入的原理就是,如果簡單地通過拼接字元串來獲得 SQL 文本,就會使本應屬於操作數的內容被當作操作運算元來執行了。
感覺有些答案要麼錯的離譜,要麼沒答到點上。為什麼說的這麼不肯定,是因為我自己也還沒徹底弄清楚這個問題,主要是資料庫內部處理這部分——如何解析SQL語句並如何生成執行計劃?執行計劃本質上到底是個什麼鬼?所以以下關於資料庫處理的部分僅是我的主觀理解,但我相信它是接近真相的。另我只熟悉sql server,故只拿它說事。
參數查詢是資料庫原生提供的能力,而不是http://ado.net等數據訪問類庫提供的,後者只是對前者的封裝。我們在程序語言中寫的sql語句和參數對象,送到資料庫時還是語句和參數,並不是有些答案認為的那樣把參數的值轉好義後拼接進語句,最後把語句提給資料庫。要說類庫做了什麼「預處理」,大概只是在開發者沒有顯式指定參數的類型和長度時,類庫會根據參數的值自動為其確定合適的類型和長度,僅此而已。這一點用資料庫語句跟蹤工具(如SQL Server Profiler)很容易證實。所以參數化查詢真不關程序語言/類庫多少事。
至於資料庫接到語句和參數後如何處理,我的理解/猜測是,資料庫負責解析查詢語句的子系統將語句轉換/編譯為某種底層的、資料庫執行子系統能executing的語言(好比C#編譯器把C#編譯為IL給CLR跑類似),就這一步,就將本批查詢語句的語義固化成了一套行為動作,這套行為動作正是所謂的「執行計劃」,執行計劃描述的東西大概是從什麼地方取數據、如何處理數據等等,這也是為什麼表名、欄位名不能參數化的原因,因為這些東西都不確定的話根本沒法生成執行計劃。至於參數的值有沒有影響執行計劃的生成,是有的,但它影響的是這個值能否命中某個索引、統計信息等性能相關的東西,能的話就生成更優的執行計劃(精確指引到某個頁取數據之類),否則走笨方法(如全表掃描),而不會對整套計劃的綱領造成影響,這個就是參數化能防注入的原因所在。
簡單總結,參數化能防注入的原因在於,語句是語句,參數是參數,參數的值並不是語句的一部分,資料庫只按語句的語義跑,至於跑的時候是帶一個普通背包還是一個怪物,不會影響行進路線,無非跑的快點與慢點的區別。
PS:謬誤之處希望得到大神的斧正。
其實博客園有不少講參數化查詢的好文章,原諒我懶得找來貼上,建議題主去看看。本身參數化查詢這一機制並不是為了防注入這個目的,而是為了儘可能命中已編譯的執行計劃從而提高查詢性能,它是整個DBMS很重要的一個特性,防注入只不過是一個副功能罷了。首先SQL注入不外乎改變原有的SQL達到注入目的。
在Oracle中,SQL語句會被預先生成執行計劃,之後根據參數據行之前生成的執行計劃。
而MySQL中,JDBC默認會轉義參數後與原SQL拼接組成需要執行的SQL。這兩種方式都會根源上杜絕SQL注入。謝邀,上面已經有專業的回答和抽象的回答了。
最近在開發自己公司官網系統,這個系統有兩層防注入,一層參數化,一層自己編寫的waf我現在把自己編寫的waf關掉。這是參數化後的,
我提交95zz" or 1=1 --如果沒有參數化會這樣"95zz" or 1=1 --"你會發現執行計劃裡面顯示的是"95zz" "or 1=1 --"前後兩個單引號是Sql Server自動補全的。 除此之外還多了一個單引號。or前面的單引號就是參數化而補全的。而這個因參數化而補全的單引號造成了正常閉合。
所以不會存在注入。如果還不了解,我發兩個圖。
【未報錯】模擬參數化執行 【報錯】非參數化執行。 對了,你聽說過寬位元組注入嗎?在mysql的場景下,參數化也......(逃另外回答 @一隻小小小小鳥
因為waf沒辦法參數化,這樣說吧,比如cdn【waf的一種】
你在請求的時候是先要到cdn裡面過一遍,進行規則匹配。才會到真正的主機上。而參數化是代碼底層的事兒。那麼我估計你的新問題又來了,waf為什麼不能在代碼層就實現參數化我暫時沒有想到一個很通俗的說話。請自行查閱相關資料吧,多看看基礎資料就能理解了。好了,要去恢復代碼了。預處理以後,注入的數據不會被當做sql語句執行,而只當做參數值來處理。
請參閱 @ahdung的答案。
經評論提醒,又去查了一下資料,可能我沒有理解參數化查詢的含義。如果參數化查詢是指資料庫的預處理機制的話,那麼防注入確實是由資料庫自身來處理的。因為第一次先接受帶參數佔位符的sql語句,此時資料庫已經知道查詢意圖了,第二次接受參數只需要對號入座,無法再對查詢意圖進行干預。
原答案主要是解釋一些框架和ORM提供的參數化查詢方式的工作原理,理解有些片面:
以下為原答案:
---------------------------------------------------------
本質上是對查詢參數做了轉義。
一個業務中SQL語句的由來:
1. 用戶提交參數A
2. 介面層取到參數A3. 預處理(業務邏輯)後給到數據層4. 數據層拼接SQL查詢DBSQL注入的原理是,如果參數A中包含一些特殊字元,在拼接SQL的時候,如果直接用字元串連接,會導致SQL語義被改變,本來預想用戶輸入的是參數,結果這個參數中的一部分變成了語句。
例如我們有語句select * from user where id="{A}",其中A是希望用戶查詢的ID。
如果A為 1" or "1"="1,我們在拼接的時候就會變成 select * from user where id="1" or "1"="1"。
SQL注入的防禦方法,則是對參數做轉義,轉義之後讓它在拼接時只能表示字元串,不能變成查詢語句。例如上例的A經過轉義變成1" or "1"="1,拼接之後為 select * from user where id="1" or "1"="1",此時就沒有問題了。
那參數化查詢跟我們自己做轉義的區別在哪裡呢?主要在於「強制」「可靠」。因為這個過程被放到了底層,即每個查詢的參數都必須強制進行這樣的轉義,並且這個轉義是經過驗證的安全的轉義方法來做的,所以認為在注入這件事情上採用參數化查詢的方式是安全的。
一句話總結:本質還是轉義,只是由數據層內置這個過程,強制可靠。如果你想深入理解 sql 編譯器的原理,只能去看源碼了。
因為參數化查詢把代碼和數據分開了。類似於防止緩衝區溢出,都是為了防止數據被當作代碼被執行
你搞java後端開發為什麼要簽名
是不是要搞事情?我要用好世界上最好的語言PHP
相比Statement的優勢有哪些
1. 執行效率高,高在哪裡?
a. 資料庫系統對sql進行了預編譯,生成相應的執行計劃,會將該執行計劃緩存起來,方便重用。
b.參數化查詢,比字元串拼接的方式效率更高。
2. 防止SQL注入,為什麼能實現呢?
a. 語句與參數隔離,語句預編譯(兩種方式:客戶端服務端),對於輸入的參數,創建臨時變數,執行時對應賦值。預編譯的SQL會被緩存起來。
b. 參數替換,setInt()/setLong()等,因為類型的強校驗不會產生注入風險,但是對於setString()是存在的。PreparedStatement採取的策略是特殊字元過濾;如果參數中不包含特殊字元,同樣會使用""將參數包含在內,進一步防止注入。
例如java.sql中PreparedStatement對象的setString方法,它將string值發送給資料庫時會轉換成SQL VARCHAR 或 LONGVARCHAR,這個轉換是自動的,會對單引號等進行轉義,所以可以防止注入
推薦閱讀:
※工控轉行,求建議?
※為什麼 MySQL 使用多線程,而 Oracle 和 PostgreSQL 使用多進程?
※為何mysql不用raft等協議實現同步複製?
※如何在資料庫中創建一個方法?
※MySQL已經可以干大部分事情了,還有必要使用商業資料庫或者PostgreSQL嗎?