SQL注入詳解及防范方法
一:什么是sql注入
SQL注入是比較常見的網(wǎng)絡(luò)攻擊方式之一,它不是利用操作系統(tǒng)的BUG來實(shí)現(xiàn)攻擊,而是針對程序員編寫時(shí)的疏忽,通過SQL語句,實(shí)現(xiàn)無賬號(hào)登錄,甚至篡改數(shù)據(jù)庫。
二:SQL注入攻擊的總體思路
- 1:尋找到SQL注入的位置
- 2:判斷服務(wù)器類型和后臺(tái)數(shù)據(jù)庫類型
- 3:針對不同的服務(wù)器和數(shù)據(jù)庫特點(diǎn)進(jìn)行SQL注入攻擊
三:SQL注入攻擊實(shí)例
String sql = "select * from user_table where username= ' "+userName+" ' and password=' "+password+" '"; --當(dāng)輸入了上面的用戶名和密碼,上面的SQL語句變成: SELECT * FROM user_table WHERE username= ''or 1 = 1 -- and password='' """ --分析SQL語句: --條件后面username=”or 1=1 用戶名等于 ” 或1=1 那么這個(gè)條件一定會(huì)成功; --然后后面加兩個(gè)-,這意味著注釋,它將后面的語句注釋,讓他們不起作用,這樣語句永遠(yuǎn)都--能正確執(zhí)行,用戶輕易騙過系統(tǒng),獲取合法身份。 --這還是比較溫柔的,如果是執(zhí)行 SELECT * FROM user_table WHERE username='' ;DROP DATABASE (DB Name) --' and password='' --其后果可想而知… """
四:如何防御SQL注入
注意:但凡有SQL注入漏洞的程序,都是因?yàn)槌绦蛞邮軄碜钥蛻舳擞脩糨斎氲淖兞炕騏RL傳遞的參數(shù),并且這個(gè)變量或參數(shù)是組成SQL語句的一部分,對于用戶輸入的內(nèi)容或傳遞的參數(shù),我們應(yīng)該要時(shí)刻保持警惕,這是安全領(lǐng)域里的「外部數(shù)據(jù)不可信任」的原則,縱觀Web安全領(lǐng)域的各種攻擊方式,大多數(shù)都是因?yàn)殚_發(fā)者違反了這個(gè)原則而導(dǎo)致的,所以自然能想到的,就是從變量的檢測、過濾、驗(yàn)證下手,確保變量是開發(fā)者所預(yù)想的。
1、檢查變量數(shù)據(jù)類型和格式
如果你的SQL語句是類似where id={$id}這種形式,數(shù)據(jù)庫里所有的id都是數(shù)字,那么就應(yīng)該在SQL被執(zhí)行前,檢查確保變量id是int類型;如果是接受郵箱,那就應(yīng)該檢查并嚴(yán)格確保變量一定是郵箱的格式,其他的類型比如日期、時(shí)間等也是一個(gè)道理??偨Y(jié)起來:只要是有固定格式的變量,在SQL語句執(zhí)行前,應(yīng)該嚴(yán)格按照固定格式去檢查,確保變量是我們預(yù)想的格式,這樣很大程度上可以避免SQL注入攻擊。
比如,我們前面接受username參數(shù)例子中,我們的產(chǎn)品設(shè)計(jì)應(yīng)該是在用戶注冊的一開始,就有一個(gè)用戶名的規(guī)則,比如5-20個(gè)字符,只能由大小寫字母、數(shù)字以及一些安全的符號(hào)組成,不包含特殊字符。此時(shí)我們應(yīng)該有一個(gè)check_username的函數(shù)來進(jìn)行統(tǒng)一的檢查。不過,仍然有很多例外情況并不能應(yīng)用到這一準(zhǔn)則,比如文章發(fā)布系統(tǒng),評(píng)論系統(tǒng)等必須要允許用戶提交任意字符串的場景,這就需要采用過濾等其他方案了。
2、過濾特殊符號(hào)
對于無法確定固定格式的變量,一定要進(jìn)行特殊符號(hào)過濾或轉(zhuǎn)義處理。
3、綁定變量,使用預(yù)編譯語句
MySQL的mysqli驅(qū)動(dòng)提供了預(yù)編譯語句的支持,不同的程序語言,都分別有使用預(yù)編譯語句的方法
實(shí)際上,綁定變量使用預(yù)編譯語句是預(yù)防SQL注入的最佳方式,使用預(yù)編譯的SQL語句語義不會(huì)發(fā)生改變,在SQL語句中,變量用問號(hào)?表示,黑客即使本事再大,也無法改變SQL語句的結(jié)構(gòu)
五:什么是sql預(yù)編譯
1.1:預(yù)編譯語句是什么
通常我們的一條sql在db接收到最終執(zhí)行完畢返回可以分為下面三個(gè)過程:
- 詞法和語義解析
- 優(yōu)化sql語句,制定執(zhí)行計(jì)劃
- 執(zhí)行并返回結(jié)果
我們把這種普通語句稱作Immediate Statements
?! ?/p>
但是很多情況,我們的一條sql語句可能會(huì)反復(fù)執(zhí)行,或者每次執(zhí)行的時(shí)候只有個(gè)別的值不同(比如query的where子句值不同,update的set子句值不同,insert的values值不同)。
如果每次都需要經(jīng)過上面的詞法語義解析、語句優(yōu)化、制定執(zhí)行計(jì)劃等,則效率就明顯不行了。
所謂預(yù)編譯語句就是將這類語句中的值用占位符替代,可以視為將sql語句模板化或者說參數(shù)化,一般稱這類語句叫Prepared Statements
或者Parameterized Statements
預(yù)編譯語句的優(yōu)勢在于歸納為:一次編譯、多次運(yùn)行,省去了解析優(yōu)化等過程;此外預(yù)編譯語句能防止sql注入。
當(dāng)然就優(yōu)化來說,很多時(shí)候最優(yōu)的執(zhí)行計(jì)劃不是光靠知道sql語句的模板就能決定了,往往就是需要通過具體值來預(yù)估出成本代價(jià)。
1.2:MySQL的預(yù)編譯功能
注意MySQL的老版本(4.1之前)是不支持服務(wù)端預(yù)編譯的,但基于目前業(yè)界生產(chǎn)環(huán)境普遍情況,基本可以認(rèn)為MySQL支持服務(wù)端預(yù)編譯。
下面我們來看一下MySQL中預(yù)編譯語句的使用。
(1)建表
首先我們有一張測試表t,結(jié)構(gòu)如下所示:
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
的語法來預(yù)編譯一條sql語句
mysql> prepare ins from 'insert into t select ?,?'; Query OK, 0 rows affected (0.00 sec) Statement prepared
(3)執(zhí)行
我們通過EXECUTE stmt_name [USING @var_name [, @var_name] ...]
的語法來執(zhí)行預(yù)編譯語句
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)
可以看到,數(shù)據(jù)已經(jīng)被成功插入表中。
MySQL中的預(yù)編譯語句作用域是session級(jí),但我們可以通過max_prepared_stmt_count
變量來控制全局最大的存儲(chǔ)的預(yù)編譯語句。
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)
當(dāng)預(yù)編譯條數(shù)已經(jīng)達(dá)到閾值時(shí)可以看到MySQL會(huì)報(bào)如上所示的錯(cuò)誤。
(4)釋放
如果我們想要釋放一條預(yù)編譯語句,則可以使用{DEALLOCATE | DROP} PREPARE stmt_name
的語法進(jìn)行操作:
mysql> deallocate prepare ins; Query OK, 0 rows affected (0.00 sec)
六:為什么PrepareStatement可以防止sql注入
原理是采用了預(yù)編譯的方法,先將SQL語句中可被客戶端控制的參數(shù)集進(jìn)行編譯,生成對應(yīng)的臨時(shí)變量集,再使用對應(yīng)的設(shè)置方法,為臨時(shí)變量集里面的元素進(jìn)行賦值,賦值函數(shù)setString(),會(huì)對傳入的參數(shù)進(jìn)行強(qiáng)制類型檢查和安全檢查,所以就避免了SQL注入的產(chǎn)生。下面具體分析
(1):為什么Statement會(huì)被sql注入
因?yàn)镾tatement之所以會(huì)被sql注入是因?yàn)镾QL語句結(jié)構(gòu)發(fā)生了變化。比如:
"select*from tablename where username='"+uesrname+"'and password='"+password+"'"
在用戶輸入'or true or'之后sql語句結(jié)構(gòu)改變。
select * from tablename where username=''or true or'' and password=''
這樣本來是判斷用戶名和密碼都匹配時(shí)才會(huì)計(jì)數(shù),但是經(jīng)過改變后變成了或的邏輯關(guān)系,不管用戶名和密碼是否匹配該式的返回值永遠(yuǎn)為true;
(2)為什么Preparement可以防止SQL注入。
因?yàn)镻reparement樣式為
select * from tablename where username=? and password=?
該SQL語句會(huì)在得到用戶的輸入之前先用數(shù)據(jù)庫進(jìn)行預(yù)編譯,這樣的話不管用戶輸入什么用戶名和密碼的判斷始終都是并的邏輯關(guān)系,防止了SQL注入
簡單總結(jié),參數(shù)化能防注入的原因在于,語句是語句,參數(shù)是參數(shù),參數(shù)的值并不是語句的一部分,數(shù)據(jù)庫只按語句的語義跑,至于跑的時(shí)候是帶一個(gè)普通背包還是一個(gè)怪物,不會(huì)影響行進(jìn)路線,無非跑的快點(diǎn)與慢點(diǎn)的區(qū)別。
七:mybatis是如何防止SQL注入的
首先看一下下面兩個(gè)sql語句的區(qū)別:
<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中的#和$的區(qū)別:
- 1、#將傳入的數(shù)據(jù)都當(dāng)成一個(gè)字符串,會(huì)對自動(dòng)傳入的數(shù)據(jù)加一個(gè)雙引號(hào)。
如:where username=#{username},如果傳入的值是111,那么解析成sql時(shí)的值為where username="111", 如果傳入的值是id,則解析成的sql為where username="id". - 2、$將傳入的數(shù)據(jù)直接顯示生成在sql中。
如:where username=${username},如果傳入的值是111,那么解析成sql時(shí)的值為where username=111;
如果傳入的值是;drop table user;,則解析成的sql為:select id, username, password, role from user where username=;drop table user; - 3、#方式能夠很大程度防止sql注入,$方式無法防止Sql注入。
- 4、$方式一般用于傳入數(shù)據(jù)庫對象,例如傳入表名.
- 5、一般能用#的就別用$,若不得不使用“${xxx}”這樣的參數(shù),要手工地做好過濾工作,來防止sql注入攻擊。
- 6、在MyBatis中,“${xxx}”這樣格式的參數(shù)會(huì)直接參與SQL編譯,從而不能避免注入攻擊。但涉及到動(dòng)態(tài)表名和列名時(shí),只能使用“${xxx}”這樣的參數(shù)格式。所以,這樣的參數(shù)需要我們在代碼中手工進(jìn)行處理來防止注入。
【結(jié)論】在編寫MyBatis的映射語句時(shí),盡量采用
“#{xxx}”
這樣的格式。若不得不使用“${xxx}”
這樣的參數(shù),要手工地做好過濾工作,來防止SQL注入攻擊。
mybatis是如何做到防止sql注入的
MyBatis框架作為一款半自動(dòng)化的持久層框架,其SQL語句都要我們自己手動(dòng)編寫,這個(gè)時(shí)候當(dāng)然需要防止SQL注入。其實(shí),MyBatis的SQL是一個(gè)具有“輸入+輸出”
的功能,類似于函數(shù)的結(jié)構(gòu),參考上面的兩個(gè)例子。其中,parameterType表示了輸入的參數(shù)類型,resultType表示了輸出的參數(shù)類型。回應(yīng)上文,如果我們想防止SQL注入,理所當(dāng)然地要在輸入?yún)?shù)上下功夫。上面代碼中使用#的即輸入?yún)?shù)在SQL中拼接的部分,傳入?yún)?shù)后,打印出執(zhí)行的SQL語句,會(huì)看到SQL是這樣的:
select id, username, password, role from user where username=? and password=?
不管輸入什么參數(shù),打印出的SQL都是這樣的。這是因?yàn)镸yBatis啟用了預(yù)編譯功能,在SQL執(zhí)行前,會(huì)先將上面的SQL發(fā)送給數(shù)據(jù)庫進(jìn)行編譯;執(zhí)行時(shí),直接使用編譯好的SQL,替換占位符“?”就可以了。因?yàn)镾QL注入只能對編譯過程起作用,所以這樣的方式就很好地避免了SQL注入的問題。
【底層實(shí)現(xiàn)原理】MyBatis是如何做到SQL預(yù)編譯的呢?其實(shí)在框架底層,是JDBC中的PreparedStatement類在起作用,PreparedStatement是我們很熟悉的Statement的子類,它的對象包含了編譯好的SQL語句。這種“準(zhǔn)備好”的方式不僅能提高安全性,而且在多次執(zhí)行同一個(gè)SQL時(shí),能夠提高效率。原因是SQL已編譯好,再次執(zhí)行時(shí)無需再編譯
參考資料:
http://www.dbjr.com.cn/article/231051.htm
http://www.dbjr.com.cn/article/231040.htm
到此這篇關(guān)于SQL注入詳解及防范方法的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
windows環(huán)境下python連接openGauss數(shù)據(jù)庫的全過程
openGauss是一款全面友好開放,攜手伙伴共同打造的企業(yè)級(jí)開源關(guān)系型數(shù)據(jù)庫,這篇文章主要給大家介紹了關(guān)于windows環(huán)境下python連接openGauss數(shù)據(jù)庫的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01Apache?Doris?Colocate?Join?原理實(shí)踐教程
這篇文章主要為大家介紹了Apache?Doris?Colocate?Join?原理實(shí)踐教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10MySQL與Oracle 差異比較之一數(shù)據(jù)類型
這篇文章主要介紹了MySQL與Oracle 差異比較之一數(shù)據(jù)類型,需要的朋友可以參考下2017-04-04詳解Flink同步Kafka數(shù)據(jù)到ClickHouse分布式表
這篇文章主要為大家介紹了Flink同步Kafka數(shù)據(jù)到ClickHouse分布式表實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12站內(nèi)群發(fā)消息三種不同用戶量的數(shù)據(jù)庫設(shè)計(jì)
很多SNS網(wǎng)站和一部分CMS網(wǎng)站都廣泛地應(yīng)用了站內(nèi)信這一模塊,這個(gè)看似簡單的東西其實(shí)背后隱藏著很多需要設(shè)計(jì)師重視的設(shè)計(jì)細(xì)節(jié),要做好這個(gè)“郵遞員”是很不容易的,本文講述站內(nèi)群發(fā)消息三種不同用戶量的數(shù)據(jù)庫設(shè)計(jì),逐漸設(shè)計(jì)一個(gè)百萬級(jí)用戶量的站內(nèi)信群發(fā)數(shù)據(jù)庫2023-12-12基于navicat連接登錄windows10本地wsl數(shù)據(jù)庫
這篇文章主要介紹了基于navicat連接登錄windows10本地wsl數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11SQL中游標(biāo)(cursor)的基本使用實(shí)例
當(dāng)你檢索的數(shù)據(jù)只是一條記錄時(shí),你所編寫的事務(wù)語句代碼往往使用SELECT INSERT語句,但如果從某一結(jié)果集中逐一地讀取一條記錄呢?游標(biāo)為我們提供了一種極為優(yōu)秀的解決方案,這篇文章主要給大家介紹了關(guān)于SQL中游標(biāo)(cursor)基本使用的相關(guān)資料,需要的朋友可以參考下2021-11-11Clickhouse系列之整合Hive數(shù)據(jù)倉庫示例詳解
這篇文章主要為大家介紹了Clickhouse系列之整合Hive數(shù)據(jù)倉庫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10