《原神攻略》楓原萬葉全方位攻略
SQL注入詳解
一:什麼是sql注入
SQL注入是比較常見的網路攻擊方式之一,它不是利用作業系統的BUG來實現攻擊,而是針對程式設計師編寫時的疏忽,通過SQL語句,實現無賬號登入,甚至篡改資料庫。
二:SQL注入攻擊的總體思路
1:尋找到SQL注入的位置
2:判斷伺服器型別和後臺資料庫型別
3:針對不同的伺服器和資料庫特點進行SQL注入攻擊
三:SQL注入攻擊例項
String sql = "select * from user_table where username= ' "+userName+" ' and password=' "+password+" '"; --當輸入了上面的使用者名稱和密碼,上面的SQL語句變成: SELECT * FROM user_table WHERE username= '’or 1 = 1 -- and password='’ """ --分析SQL語句: --條件後面username=”or 1=1 使用者名稱等於 ” 或1=1 那麼這個條件一定會成功; --然後後面加兩個-,這意味著註釋,它將後面的語句註釋,讓他們不起作用,這樣語句永遠都--能正確執行,使用者輕易騙過系統,獲取合法身份。 --這還是比較溫柔的,如果是執行 SELECT * FROM user_table WHERE username='' ;DROP DATABASE (DB Name) --' and password='' --其後果可想而知… """
四:如何防禦SQL注入
注意:但凡有SQL注入漏洞的程式,都是因為程式要接受來自客戶端使用者輸入的變數或URL傳遞的引數,並且這個變數或引數是組成SQL語句的一部分,對於使用者輸入的內容或傳遞的引數,我們應該要時刻保持警惕,這是安全領域裡的「外部資料不可信任」的原則,縱觀Web安全領域的各種攻擊方式,大多數都是因為開發者違反了這個原則而導致的,所以自然能想到的,就是從變數的檢測、過濾、驗證下手,確保變數是開發者所預想的。
1、檢查變數資料型別和格式
如果你的SQL語句是類似where id={$id}這種形式,資料庫裡所有的id都是數字,那麼就應該在SQL被執行前,檢查確保變數id是int型別;如果是接受郵箱,那就應該檢查並嚴格確保變數一定是郵箱的格式,其他的型別比如日期、時間等也是一個道理。總結起來:只要是有固定格式的變數,在SQL語句執行前,應該嚴格按照固定格式去檢查,確保變數是我們預想的格式,這樣很大程度上可以避免SQL注入攻擊。
比如,我們前面接受username引數例子中,我們的產品設計應該是在使用者註冊的一開始,就有一個使用者名稱的規則,比如5-20個字元,只能由大小寫字母、數字以及一些安全的符號組成,不包含特殊字元。此時我們應該有一個check_username的函式來進行統一的檢查。不過,仍然有很多例外情況並不能應用到這一準則,比如文章釋出系統,評論系統等必須要允許使用者提交任意字串的場景,這就需要採用過濾等其他方案了。
2、過濾特殊符號
對於無法確定固定格式的變數,一定要進行特殊符號過濾或轉義處理。
3、繫結變數,使用預編譯語句
MySQL的mysqli驅動提供了預編譯語句的支援,不同的程式語言,都分別有使用預編譯語句的方法
實際上,繫結變數使用預編譯語句是預防SQL注入的最佳方式,使用預編譯的SQL語句語義不會發生改變,在SQL語句中,變數用問號?表示,黑客即使本事再大,也無法改變SQL語句的結構
五:什麼是sql預編譯
1.1:預編譯語句是什麼
通常我們的一條sql在db接收到最終執行完畢返回可以分為下面三個過程:
- 詞法和語義解析
- 優化sql語句,制定執行計劃
- 執行並返回結果
我們把這種普通語句稱作Immediate Statements。
但是很多情況,我們的一條sql語句可能會反覆執行,或者每次執行的時候只有個別的值不同(比如query的where子句值不同,update的set子句值不同,insert的values值不同)。
如果每次都需要經過上面的詞法語義解析、語句優化、制定執行計劃等,則效率就明顯不行了。
所謂預編譯語句就是將這類語句中的值用佔位符替代,可以視為將sql語句模板化或者說引數化,一般稱這類語句叫Prepared Statements或者Parameterized Statements
預編譯語句的優勢在於歸納為:一次編譯、多次執行,省去了解析優化等過程;此外預編譯語句能防止sql注入。
當然就優化來說,很多時候最優的執行計劃不是光靠知道sql語句的模板就能決定了,往往就是需要通過具體值來預估出成本代價。
1.2:MySQL的預編譯功能
注意MySQL的老版本(4.1之前)是不支援服務端預編譯的,但基於目前業界生產環境普遍情況,基本可以認為MySQL支援服務端預編譯。
下面我們來看一下MySQL中預編譯語句的使用。
(1)建表 首先我們有一張測試表t,結構如下所示:
mysql> show create table t\G *************************** 1. row *************************** Table: t Create Table: CREATE TABLE `t` ( `a` int(11) DEFAULT NULL, `b` varchar(20) DEFAULT NULL, UNIQUE KEY `ab` (`a`,`b`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
(2)編譯
我們接下來通過PREPARE stmt_name FROM preparable_stm
的語法來預編譯一條sql語句
mysql> prepare ins from 'insert into t select ?,?'; Query OK, 0 rows affected (0.00 sec) Statement prepared
(3)執行
我們通過EXECUTE stmt_name [USING @var_name [, @var_name] ...]
的語法來執行預編譯語句
mysql> set @a=999,@b='hello'; Query OK, 0 rows affected (0.00 sec) mysql> execute ins using @a,@b; Query OK, 1 row affected (0.01 sec) Records: 1 Duplicates: 0 Warnings: 0 mysql> select * from t; +------+-------+ | a | b | +------+-------+ | 999 | hello | +------+-------+ 1 row in set (0.00 sec)
可以看到,資料已經被成功插入表中。
MySQL中的預編譯語句作用域是session級,但我們可以通過max_prepared_stmt_count變數來控制全域性最大的儲存的預編譯語句。
mysql> set @@global.max_prepared_stmt_count=1; Query OK, 0 rows affected (0.00 sec) mysql> prepare sel from 'select * from t'; ERROR 1461 (42000): Can't create more than max_prepared_stmt_count statements (current value: 1)
當預編譯條數已經達到閾值時可以看到MySQL會報如上所示的錯誤。
(4)釋放
如果我們想要釋放一條預編譯語句,則可以使用{DEALLOCATE | DROP} PREPARE stmt_name
的語法進行操作:
mysql> deallocate prepare ins; Query OK, 0 rows affected (0.00 sec)
六:為什麼PrepareStatement可以防止sql注入
原理是採用了預編譯的方法,先將SQL語句中可被客戶端控制的引數集進行編譯,生成對應的臨時變數集,再使用對應的設定方法,為臨時變數集裡面的元素進行賦值,賦值函式setString(),會對傳入的引數進行強制型別檢查和安全檢查,所以就避免了SQL注入的產生。下面具體分析
(1):為什麼Statement會被sql注入
因為Statement之所以會被sql注入是因為SQL語句結構發生了變化。比如:
"select*from tablename where username='"+uesrname+ "'and password='"+password+"'"
在使用者輸入'or true or'之後sql語句結構改變。
select*from tablename where username=''or true or'' and password=''
這樣本來是判斷使用者名稱和密碼都匹配時才會計數,但是經過改變後變成了或的邏輯關係,不管使用者名稱和密碼是否匹配該式的返回值永遠為true;
(2)為什麼Preparement可以防止SQL注入。
因為Preparement樣式為
select*from tablename where username=? and password=?
該SQL語句會在得到使用者的輸入之前先用資料庫進行預編譯,這樣的話不管使用者輸入什麼使用者名稱和密碼的判斷始終都是並的邏輯關係,防止了SQL注入
簡單總結,引數化能防注入的原因在於,語句是語句,引數是引數,引數的值並不是語句的一部分,資料庫只按語句的語義跑,至於跑的時候是帶一個普通揹包還是一個怪物,不會影響行進路線,無非跑的快點與慢點的區別。
七:mybatis是如何防止SQL注入的
1、首先看一下下面兩個sql語句的區別:
<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap"> select id, username, password, role from user where username = #{username,jdbcType=VARCHAR} and password = #{password,jdbcType=VARCHAR} </select>
<select id="selectByNameAndPassword" parameterType="java.util.Map" resultMap="BaseResultMap"> select id, username, password, role from user where username = ${username,jdbcType=VARCHAR} and password = ${password,jdbcType=VARCHAR} </select>
mybatis中的#和$的區別:
1、#將傳入的資料都當成一個字串,會對自動傳入的資料加一個雙引號。
如:where username=#{username},如果傳入的值是111,那麼解析成sql時的值為where username="111", 如果傳入的值是id,則解析成的sql為where username="id".
2、$將傳入的資料直接顯示生成在sql中。
如:where username=${username},如果傳入的值是111,那麼解析成sql時的值為where username=111;
如果傳入的值是;drop table user;,則解析成的sql為:select id, username, password, role from user where username=;drop table user;
3、#方式能夠很大程度防止sql注入,$方式無法防止Sql注入。
4、$方式一般用於傳入資料庫物件,例如傳入表名.
5、一般能用#的就別用$,若不得不使用“${xxx}”這樣的引數,要手工地做好過濾工作,來防止sql注入攻擊。
6、在MyBatis中,“${xxx}”這樣格式的引數會直接參與SQL編譯,從而不能避免注入攻擊。但涉及到動態表名和列名時,只能使用“${xxx}”這樣的引數格式。所以,這樣的引數需要我們在程式碼中手工進行處理來防止注入。
【結論】在編寫MyBatis的對映語句時,儘量採用“#{xxx}”這樣的格式。若不得不使用“${xxx}”這樣的引數,要手工地做好過濾工作,來防止SQL注入攻擊。
mybatis是如何做到防止sql注入的
MyBatis框架作為一款半自動化的持久層框架,其SQL語句都要我們自己手動編寫,這個時候當然需要防止SQL注入。其實,MyBatis的SQL是一個具有“輸入+輸出”的功能,類似於函式的結構,參考上面的兩個例子。其中,parameterType表示了輸入的引數型別,resultType表示了輸出的引數型別。迴應上文,如果我們想防止SQL注入,理所當然地要在輸入引數上下功夫。上面程式碼中使用#的即輸入引數在SQL中拼接的部分,傳入引數後,打印出執行的SQL語句,會看到SQL是這樣的:
select id, username, password, role from user where username=? and password=?
不管輸入什麼引數,打印出的SQL都是這樣的。這是因為MyBatis啟用了預編譯功能,在SQL執行前,會先將上面的SQL傳送給資料庫進行編譯;執行時,直接使用編譯好的SQL,替換佔位符“?”就可以了。因為SQL注入只能對編譯過程起作用,所以這樣的方式就很好地避免了SQL注入的問題。
【底層實現原理】MyBatis是如何做到SQL預編譯的呢?其實在框架底層,是JDBC中的PreparedStatement類在起作用,PreparedStatement是我們很熟悉的Statement的子類,它的物件包含了編譯好的SQL語句。這種“準備好”的方式不僅能提高安全性,而且在多次執行同一個SQL時,能夠提高效率。原因是SQL已編譯好,再次執行時無需再編譯
資料:https://www.cnblogs.com/shenbuer/p/7875419.html
http://www.cnblogs.com/mmzs/p/8398405.html