◆劉存普 胡 勇
?
Web應用的SQL注入防范研究
◆劉存普 胡 勇
(四川大學電子信息學院 四川 610065)
SQL注入嚴重威脅Web應用的安全。本文介紹SQL注入的基本原理,分析SQL注入的基本過程以及幾種常見的方法,并從多方面提出防御SQL注入攻擊的安全措施。
網絡信息安全;SQL注入;防御措施
網絡安全是信息化的重要基石。網絡信息涉及國家的軍事、政府、經濟等領域及公民的個人信息,其中許多信息是敏感信息,甚至是機密信息,而層出不窮的各種安全漏洞和不斷變化的攻擊手段,使得網絡和信息安全面臨艱巨挑戰。
目前,針對Web應用程序和數據庫的攻擊已成為網絡安全隱患的主要方面,其具有技術門檻低、可繞過安全設備、攻擊回報高等特點。據開放Web應用程序安全項目(Open Web Application Security Project,OWASP)近些年的統計中,注入問題仍然是影響網絡安全最嚴重的漏洞[1]。在許多安全論壇,SQL注入攻擊也是熱點話題之一,對SQL注入問題的研究非常重要。
SQL是操作數據庫數據的結構化查詢語言,網頁的應用數據和后臺數據庫中的數據進行交互時會采用SQL。而SQL注入是將Web頁面的原URL、表單域或數據包輸入的參數,修改拼接成SQL語句,傳遞給Web服務器,進而傳給數據庫服務器以執行數據庫命令。如Web應用程序的開發人員對用戶所輸入的數據或cookie等內容不進行過濾或驗證(即存在注入點)就直接傳輸給數據庫,就可能導致拼接的SQL被執行,獲取對數據庫的信息以及提權,發生SQL注入攻擊。
SQL注入攻擊的一般過程如圖1所示。

圖1 SQL注入過程
2.1 尋找注入點
下面是一種經典的通過報錯判斷是否存在SQL注入點的方法:
①http://www.xxx.com/xxx.asp?id=1
②http://www.xxx.com/xxx.asp?id=1 and 1=1
③http://www.xxx.com/xxx.asp?id=1 and 1=2
返回結果如果如下就可以基本確定這個網站存在SQL注入點。
①正常顯示。
②正常顯示,與①相同。
③頁面報錯。
2.2 獲取后臺數據庫的信息
不同的數據庫注入的方式略有不同。在注入參數后添加一個空格,再寫如下代碼,根據頁面的報錯返回信息,就能猜測出網站的數據庫類型。
and user>0
user是MS SQL Server的一個固定函數,將user與0相比較,頁面會顯示錯誤信息:將nvarchar值“xxx”轉換數據類型為int的列時發生語法錯誤。而Access數據庫不存在user,不會產生上述錯誤信息。另外,下面的兩個語句選其中一句執行就能猜測到數據庫是MS SQL Server還是Access。因為MS SQL Server中有固定的表sysobject,①返回正常。而Access中的固定表是msysobject,②返回正常。
①and(select count(*)from sysobject)>0
②and(select count(*)from msysobject)>0
在MYSQL中,可以用@@version或是version()來返回當前的版本信息。但同樣MSSQL中,也能用@@version返回版本信息。③④返回都是正確時,則是MYSQL。如果③返回錯誤,④返回正確時,則是MSSQL。
③and version()>0
④and @@version>0
在MSSQL中可以使用函數substring,使用⑤返回成功。ORACLE則只能使用函數substr,使用⑥返回成功。
⑤substring('abc',1,1)=a
⑥substr('abc',1,1)=a
ORACLE還能使用語句banner FROM v$version和banner FROM v$version WHERE rownum=1來返回數據庫服務器版本信息。
2.3 讀取數據庫表的信息
獲取數據庫表信息包括猜解表名、字段名。
(1)猜表名
可利用語句:and exists(select * from 表名)猜測表名。如果頁面返回是正常的,就證明所猜測的表名正確;如果頁面返回不正常,那么就證明所猜測的表名不正確,換表名繼續執行上述語句,一直到返回正常頁面而猜到正確的表名。這里可以用工具組成庫表字典進行猜解。如系統常用的存儲用戶的表名有admin、manage、user、member等。
(2)猜字段
假設user是正確的表名,可利用語句:and exists(select 字段名 from user)猜測字段名。過程與猜解表名相同。常用的字段名有username、password、user、pass、name、pass、pwd、usr、psd等。
(3)查詢管理員用戶名和口令
①聯合查詢注入
http://www.xxx.com/xxx.asp?id=1 order by n
這里的n是任意自然數。如果輸入n,返回正常的頁面,而輸入n+1,頁面顯示不正常,則該網頁上的字段數為n+1。這里假設字段數為10,表名為admin。運用聯合查詢:
http://www.xxx.com/xxx.asp?id=1 and 1=2 union select 1,2,3,4,5,6,7,8,9,10 from admin
網頁會返回幾個數字(數字在字段數之內),而這些數字就是顯示內容的字段,如圖2所示,顯示位為4,5,7。這時可以將上述所提交4,7分別替換為字段名如username,password如圖3所示,可能查詢到管理員的用戶名和口令。

圖2頁面字段數顯示
圖3替換數字后得到的用戶名及口令
②ASCII碼暴力破解
有些注入點不支持聯合查詢,用union select網頁會報錯,得不到字段的內容[2]。這時就要用ASCII碼暴力破解。語句:and (select top 1 len(字段名)from 表名)>n,其中n為自然數。當輸入n,返回正常的網頁,而替換n為n+1,頁面報錯,則這個字段的內容長度為n+1。再用語句:and(select top 1 asc(mid(字段名,m,1))from 表名)>k,其中m、k也是自然數(n>=m)。分析該語句,最里邊的mid(字段名,m,1)函數,1表示截取字段的字符長度為1,m表示從第m個字符開始截取。函數asc()是將函數mid中截取的字符轉換成ASCII碼。函數top 1,表示返回的第一條記錄,上述語句中最右邊的“>k”,是將轉換的ASCII碼值與k比較。經過多次調整k的值,最終得到截取字符的ASCII碼。
③Access偏移注入
有些網站能夠得到表名,但是由于字段名過于生僻,難以猜解。如果這個網站所用的數據庫類型是Access,這時就可以用Access偏移注入。如一個網站通過order by的方法得到字段數為47,表名為admin,其中有個字段為id。用上述的聯合查詢法沒有得到顯示位,用union select 1,* from admin。如果返回錯誤,則繼續用union select 1,2,* from admin。以此類推,一直到頁面返回正常為止。這里假設到33返回正常頁面。這里的*就代表了14(47-33=14)個字段。
union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,* from(admin as a inner join admin as b on a.id=b.id)
這里(admin as a inner join admin as b on a.id=b.id)是admin表自連接,這樣from 后面的表就會成為字段數加倍的表。*(14*2=28)代表的字段就會拓寬,就加大了顯示用戶名和密碼在可顯示位置的幾率。如果還沒顯示出來就在19(47-14*2=19)后面加個a.id,還沒顯示再加b.id。
union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,a.id,b.id,* from(admin as a inner join admin as b on a.id=b.id)。如果還沒顯示出來就繼續加個c.id,因為又多了一個c.id所以*(14*3=42)所代表的字段數也要加倍。
union select 1,2,3,4,5,a.id,b.id,c.id,* from((admin as a inner join admin as b on a.id=b.id)inner join admin as c on a.id=c.id)。如圖4所示。

圖4 偏移注入
3.1 敏感字符直接過濾
使用過濾敏感字符的方法對用戶提交信息進行檢查。SQL注入攻擊的敏感字符有:“and”“or”“union”“order”“cmd”“update”“’” “//”“create”“--”等。針對上列的敏感字符,設置相應的字符處理函數。這種過濾方法容易漏掉某些字符,容易繞過,已漸漸被淘汰。
3.2 SQL語句預編譯和綁定變量[4]
不將用戶提交的內容直接嵌入到數據庫執行命令的語句中,而是通過參數進行傳遞,能有效地防御SQL注入。如:
String sql = "select id,no from user where id=?";
PreparedStatementps = conn.prepareStatement(sql);
ps.setInt(1,id);
ps.executeQuery();
代碼中的PreparedStatement,會把SQL語句:"select id,no from user where id=?" 先編譯好,也就是SQL的引擎會先進行語法分析,產生語法樹,生成執行計劃。輸入的內容不能造成該SQL語句的命令變化,即使后來提交類似SQL的語句,也不會被當作是SQL語句來執行命令,而只會被當作字符串。
還有種類似的方法,比較用戶輸入提交前后動態SQL語句的語法結構是否一致來檢測SQL注入攻擊[3]。
3.3 檢查參數類型
SQL語句預編譯不能在所有情況下使用,在一些情況下必需得使用字符串拼接的方法。所以應該嚴格檢測參數的類型,也可以使用一些安全性高的函數,用來防止SQL注入攻擊。
例如 String sql = "select id,no from user where id=" + id;
在收到使用者輸入的數據時,檢測字段“id”的內容是否是整數類型,如果不是則一律報錯或者彈窗警告。在復雜的情況下還能采用正則表達式來檢測數據是否合法。這樣也能防御SQL注入攻擊。下例就是強制轉換所輸入數據中特殊字符的代碼:
MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
name = ESAPI.encoder().encodeForSQL(codec,name);
String sql = "select id,no from user where name=" + name;
函數“ESAPI.encoder().encodeForSQL(codec,name)”可以把“name”中的一些特殊字符進行轉譯,這樣SQL引擎就不會把“name”中的特殊字符當成SQL命令來進行語法編譯了。
3.4 權限管理
刪除sa用戶,新建一個權限為sa的用戶,用戶名和口令要有較高的復雜性,以防暴力破解。針對web應用,新建一個web連接用戶,去掉所有服務器角色,在用戶映射中加入此用戶要操作的數據庫db_owner和db_public,并另加必須的權限(如insert/delete/select/update等)。
一般的SQL注入都是從網站的前臺網頁尋找漏洞,可針對前臺操作和后臺操作分別建立不同的數據庫操作用戶。只賦予用戶需要的最低權限。后臺用戶的權限也只能是當前數據庫的權限。千萬不能使用root級的賬戶連接數據庫。
3.5 SQL注入防御模型[5-8]
由于針對SQL注入的攻擊方式多種多樣,沒有單獨的一種SQL注入防御技術能夠有效的預防所有的SQL注入攻擊。所以需要將各種防御方法聯合起來運用,共同建立一個防御模型才能更加有效地防御SQL注入攻擊。
本文對SQL注入的幾種方法進行比較詳細的講解。同時從屏蔽關鍵字、語句預編譯、參數類型、用戶權限進行對SQL注入的防御。但是這里并沒有對如何防止繞過防火墻的SQL注入進行研究,還需要進一步的探索。網站開發與網站管理才是最重要的防御環節,管理人員與開發人員切不可大意馬虎。
[1]OWASPFoundation.OWASPTopTenProject[EB/OL].https://www.owasp.org/index.php/Category:OWASP_Top_Ten_ Project,2015.
[2]陳煒.基于手工SQL注入的Web滲透測試技術研究[D].中北大學,2015.
[3]田玉杰,趙澤茂,王麗君等.基于分類的SQL注入攻擊雙層防御模型研究[J].信息網絡安全,2015.
[4]周敬利,王曉峰,余勝生等.一種新的飯SQL注入策略的研究與實現[J].計算機科學,2006.
[5]田宇杰,趙澤茂,張海川等.二階SQL注入攻擊防御模型[J].信息網絡安全,2014.
[6]李元鵬.SQL注入掃描分析工具的實現與攻擊防范技術研究[D].北京交通大學,2014.
[7]趙陽,郭玉翠.新型SQL注入攻擊的研究與防范[J].計算機系統運用,2016.
[8]沈壽忠.基于網絡爬蟲的SQL注入與XSS漏洞挖掘[D].西安電子科技大學,2009.