-
Notifications
You must be signed in to change notification settings - Fork 115
8. Contributing and Extending SQLRecon
Adding your own module to SQLRecon is pretty straight forward.
The following example demonstrates extending SQLRecon to add a new SQL server module called demo
, which supports standard, impersonation, linked and chain execution. The demo module will take one argument.
The demo
module will be executed using the demo
command. This module will take an arbitrary SQL query as a command line argument and execute it on a remote SQL server.
The commands/GlobalVariables.cs
file contains all of the commands supported by SQLRecon.
To create a SQL server module called demo
that takes one argument, update the SqlModulesAndArgumentCount
dictionary to include {"demo", new[] { 1, 2, 2 }},
. The multi-dimensional array translates to:
- When executed in a standard context, the demo module needs 1 argument, which is the query.
- When executed in a impersonation context, the demo module needs 2 arguments, which is the user to impersonate, and the query.
- When executed in a linked context, the demo module needs 2 arguments, which is the linked SQL server, and the query.
For example:
internal static Dictionary<string, int[]> SqlModulesAndArgumentCount
{
get
{
return new Dictionary<string, int[]>()
{
{"agentstatus", new[] { 0, 1, 1 }},
{"checkrpc", new[] { 0, 1, 1 }},
{"agentcmd", new[] { 1, 2, 2 }},
{"disablerpc", new[] { 1, 2, -1 }},
...
<snipped>
...
---------> {"demo", new[] { 1, 2, 3 }},
{"enablerpc", new[] { 1, 2, -1 }},
{"olecmd", new[] { 1, 2, 2 }},
...
<snipped>
...
};
}
}
If impersonation is not supported, then the array would be `{ 1, -1, 2}`, as -1 ensures that this module will not be compatible in an impersonation context.
If execution against a linked or chain link server is not supported, then the array would be `{ 1, 2, -1}`, as -1 ensures that this module will not be compatible in a linked context.
The commands/SqlModules.cs
file contains a class called CheckSqlArguments
, within this class we can create argument handling for the demo
module. The argument that we will pass into the demo
module will be the command
flag.
/// <summary>
/// Required arguments for the demo module.
/// Checks performed for standard, impersonation, linked, and chained.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
internal static bool Demo(string context)
{
switch (context)
{
case "standard":
if (Var.ParsedArguments.ContainsKey("command") && !string.IsNullOrEmpty(Var.ParsedArguments["command"]))
{
Var.Arg1 = Var.ParsedArguments["command"];
return true;
}
else
{
Print.Error("Must supply a command (/c:, /command:) for this module.", true);
// Go no further. Gracefully exit.
return false;
}
case "impersonation":
if (Var.ParsedArguments.ContainsKey("command") && !string.IsNullOrEmpty(Var.ParsedArguments["command"]) &&
Var.ParsedArguments.ContainsKey("iuser") && !string.IsNullOrEmpty(Var.ParsedArguments["iuser"]))
{
Var.Arg1 = Var.ParsedArguments["command"];
return true;
}
else
{
Print.Error("Must supply a user to impersonate (/i:, /iuser:) and a command for this module (/c, /command:).", true);
// Go no further. Gracefully exit.
return false;
}
case "linked" or "chained":
if (Var.ParsedArguments.ContainsKey("command") && !string.IsNullOrEmpty(Var.ParsedArguments["command"]) &&
Var.ParsedArguments.ContainsKey("link") && !string.IsNullOrEmpty(Var.ParsedArguments["link"]))
{
Var.Arg1 = Var.ParsedArguments["command"];
return true;
}
else
{
Print.Error("Must supply a linked SQL server (/l:, /link:) " +
"and a command for this module (/c, /command:).", true);
// Go no further. Gracefully exit.
return false;
}
default:
Print.Error($"'{context}' is not a valid context.", true);
// Go no further. Gracefully exit.
return false;
}
}
The commands/SqlModules.cs
file contains a class called SqlModules
which contains methods that execute the desired functionality of commands.
Reflection is used to match the command name against the method name to execute the module. Therefore, when creating method, the method's name must match the command name.
The following example demonstrates creating a method called demo
, which will execute when the demo
command is provided as a command line argument.
/// <summary>
/// The demo method is used against SQL server to execute a user supplied SQL query.
/// This module supports execution against SQL server using a standard authentication context,
/// impersonation, linked SQL servers, and chained SQL servers.
/// This method needs to be public as reflection is used to match the
/// module name that is supplied via command line, to the actual method name.
/// </summary>
public static void demo()
{
// Check if the required arguments are in place, otherwise, gracefully exit.
if (CheckSqlArguments.Demo(Var.Context) == false) return;
Print.Status($"Executing '{Var.Arg1}'", true);
switch(Var.Context)
{
case "standard":
_query = Var.Arg1;
break;
case "impersonation":
_query = Format.ImpersonationQuery(Var.Impersonate, Var.Arg1);
break;
case "linked":
_query = Format.LinkedQuery(Var.LinkedSqlServer, Var.Arg1);
break;
case "chained":
_query = Format.LinkedChainQuery(Var.LinkedSqlServersChain, Var.Arg1);
break;
default:
Print.Error($"'{Var.Context}' is not a valid context.", true);
break;
}
Console.WriteLine();
Print.IsOutputEmpty(Sql.CustomQuery(Var.Connect, _query), true);
}
Now that the demo
command has been registered, update the help menu in utilities/Help.cs
to include a brief description of the command.
To validate that the new command has been registered and the module works as expected, compile the project and execute the demo
command with a command line argument.
SQLRecon.exe /a:wintoken /h:SQL01 /m:demo /command:"SELECT @@SERVERNAME";
[*] Executing the 'demo' module on SQL01
[*] Executing 'SELECT @@SERVERNAME'
| column0 |
| ------- |
| SQL01 |
SQLRecon.exe /a:wintoken /h:SQL02 /i:sa /m:demo /command:"SELECT @@SERVERNAME";
[*] Executing the 'demo' module on SQL02 as 'sa'
[*] Executing 'SELECT @@SERVERNAME'
| column0 |
| ------- |
| SQL02 |
SQLRecon.exe /a:wintoken /h:SQL02 /l:SQL03 /m:demo /command:"SELECT @@SERVERNAME";
[*] Executing the 'demo' on SQL03 via SQL02
[*] Executing 'SELECT @@SERVERNAME'
| column0 |
| ------- |
| SQL03 |
SQLRecon.exe /a:wintoken /h:SQL01 /l:SQL02,SQL03,MECM01 /chain /m:demo /command:"SELECT @@SERVERNAME";
[*] Setting the chain path to SQL01 -> SQL02 -> SQL03 -> MECM01
[*] Executing the 'demo' module on MECM01
[*] Executing 'SELECT @@SERVERNAME'
| column0 |
| ------- |
| MECM01 |
After testing that the command works you should also update the README.md file, test harnesses and wiki.