1. 程式人生 > 資料庫 >SQL注入詳解

SQL注入詳解

參考:

https://www.cnblogs.com/myseries/p/10821372.html

https://www.cnblogs.com/liuzeyu12a/p/10046228.html

https://blog.csdn.net/newtelcom/article/details/56844186

 

 

 

 

一:什麼是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接收到最終執行完畢返回可以分為下面三個過程:

    1.   詞法和語義解析
    2.   優化sql語句,制定執行計劃
    3.   執行並返回結果

  我們把這種普通語句稱作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

 

 

 

 

 

WEB安全之SQL注入

引言:

在開發網站的時候,出於安全考慮,需要過濾從頁面傳遞過來的字元。通常,使用者可以通過以下介面呼叫資料庫的內容:URL位址列、登陸介面、留言板、搜尋框等。這往往給駭客留下了可乘之機。輕則資料遭到洩露,重則伺服器被拿下。

1、SQL注入步驟

a)尋找注入點,構造特殊的語句

傳入SQL語句可控引數分為兩類 
1. 數字型別,引數不用被引號括起來,如?id=1 
2. 其他型別,引數要被引號擴起來,如?name="phone"

b)使用者構造SQL語句(如:'or 1=1#;admin'#(這個注入又稱PHP的萬能密碼,是已知使用者名稱的情況下,可繞過輸入密碼)以後再做解釋)

c)將SQL語句傳送給DBMS資料庫

d)DBMS收到返回的結果,並將該請求解釋成機器程式碼指令,執行必要得到操作

e)DBMS接受返回結果,處理後,返回給使用者

因為使用者構造了特殊的SQL語句,必定返回特殊的結果(只要你的SQL語句夠靈活)

下面,我通過一個例項具體來演示下SQL注入
二、SQL注入例項詳解(以上測試均假設伺服器未開啟magic_quote_gpc) 

1) 前期準備工作
先來演示通過SQL注入漏洞,登入後臺管理員介面
首先,建立一張試驗用的資料表: 

複製程式碼
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`email` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1; 
複製程式碼

新增一條記錄用於測試:

INSERT INTO users (username,password,email)
VALUES('MarcoFly',md5('test'),'[email protected]'); 

接下來,貼上登入介面的原始碼

複製程式碼
<html>
<head>
<title>Sql注入演示</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body >
<form action="validate.php" method="post">
<fieldset >
<legend>Sql注入演示</legend>
<table>
<tr>
<td>使用者名稱:</td><td><input type="text" name="username"></td>
</tr>
<tr>
<td>密  碼:</td><td><input type="text" name="password"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td><td><input type="reset" value="重置"></td>
</tr>
</table>
</fieldset>
</form>
</body>
</html> 
複製程式碼

附上效果圖:

當用戶點選提交按鈕的時候,將會把表單資料提交給validate.php頁面,validate.php頁面用來判斷使用者輸入的使用者名稱和密碼有沒有都符合要求(這一步至關重要,也往往是SQL漏洞所在)

複製程式碼
!                                         <!--前臺和後臺對接-->
<html>
<head>
<title>登入驗證</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<?php
$conn=@mysql_connect("localhost",'root','') or die("資料庫連線失敗!");;
mysql_select_db("injection",$conn) or die("您要選擇的資料庫不存在");
$name=$_POST['username'];
$pwd=$_POST['password'];
$sql="select * from users where username='$name' and password='$pwd'";
$query=mysql_query($sql);
$arr=mysql_fetch_array($query);
if(is_array($arr)){
header("Location:manager.php");
}else{
echo "您的使用者名稱或密碼輸入有誤,<a href=\"Login.php\">請重新登入!</a>";
}
?>
</body>
</html>                         
複製程式碼

注意到了沒有,我們直接將使用者提交過來的資料(使用者名稱和密碼)直接拿去執行,並沒有實現進行特殊字元過濾,待會你們將明白,這是致命的。
程式碼分析:如果,使用者名稱和密碼都匹配成功的話,將跳轉到管理員操作介面(manager.php),不成功,則給出友好提示資訊。
登入成功的介面:

 登入失敗的提示:

到這裡,前期工作已經做好了,接下來將展開我們的重頭戲:SQL注入

 2) 構造SQL語句
填好正確的使用者名稱(marcofly)和密碼(test)後,點選提交,將會返回給我們“歡迎管理員”的介面。
因為根據我們提交的使用者名稱和密碼被合成到SQL查詢語句當中之後是這樣的:
select * from users where username='marcofly' and password=md5('test')
很明顯,使用者名稱和密碼都和我們之前給出的一樣,肯定能夠成功登陸。但是,如果我們輸入一個錯誤的使用者名稱或密碼呢?很明顯,肯定登入不了吧。恩,正常情況下是如此,但是對於有SQL注入漏洞的網站來說,只要構造個特殊的“字串”,照樣能夠成功登入。

比如:在使用者名稱輸入框中輸入:' or 1=1#,密碼隨便輸入,這時候的合成後的SQL查詢語句為:
select * from users where username='' or 1=1#' and password=md5('')
語義分析:“#”在mysql中是註釋符,這樣井號後面的內容將被mysql視為註釋內容,這樣就不會去執行了,換句話說,以下的兩句sql語句等價: 

select * from users where username='' or 1=1#' and password=md5('') 

等價於

select* from users where usrername='' or 1=1

因為1=1永遠是都是成立的,即where子句總是為真,將該sql進一步簡化之後,等價於如下select語句:

select * from users 
沒錯,該sql語句的作用是檢索users表中的所有欄位 

 

上面是一種輸入方法,這裡再介紹一種注入的方法,這個方法又稱PHP的萬能密碼

我們再已知使用者名稱的條件下,可以不能密碼即可登入,假設使用者名稱:admin

構造語句:

select * from users where username='admin'#' and password=md5('')

等價於

select * from users where username='admin'

這樣即可不能輸入密碼登入上去的。

資料庫就會錯認為不用使用者名稱既可以登入,繞過後臺的驗證,已到達注入的目的。

同樣利用了SQL語法的漏洞。

 

看到了吧,一個經構造後的sql語句竟有如此可怕的破壞力,相信你看到這後,開始對sql注入有了一個理性的認識了吧~
沒錯,SQL注入就是這麼容易。但是,要根據實際情況構造靈活的sql語句卻不是那麼容易的。有了基礎之後,自己再去慢慢摸索吧。
有沒有想過,如果經由後臺登入視窗提交的資料都被管理員過濾掉特殊字元之後呢?這樣的話,我們的萬能使用者名稱' or 1=1#就無法使用了。但這並不是說我們就毫無對策,要知道使用者和資料庫打交道的途徑不止這一條。

 

剛開始學這SQL注入比較迷糊,因為自己資料庫沒學好,看了大佬的解釋https://www.jb51.net/article/29444.htm

自己再加了一些自己的看法,才對SQL注入有了一個比較直觀地認識。

 

 

 

 

 

模糊查詢LIKE語句的SQL注入預防

    1、儘量避免採用$的方式,$會導致SQL注入, LIKE '%$institutionName$%' 和  LIKE  concat( '%',$institutionName$,'%') 都會導致SQL注入 ;     2、儘量採用 #的方式,#將傳入的資料都當成一個字串,會對自動傳入的資料加一個雙引號; 更詳細的可以檢視$和#的區別;     3、對於例子中的模糊查詢, 可以用#結合 concat函式,即修改為 LIKE concat('%',#institutionName#,'%') ;     4、以上只是編碼LIKE語句的SQL注入防範,實際中需要對使用者輸入進行過濾處理;    

like查詢不小心會有漏動,正確寫法如下:

mysql: select * from tbl_school where school_name like concat('%',#name#,'%')    
 
oracle: select * from tbl_school where school_name like '%'||#name#||'%'    
   
sql server:select * from tbl_school where school_name like '%'+#name#+'%'