Popping Microsoft's Sandbox: What Falls Out of a Dataverse Container

Microsoft Dataverse lets you deploy custom .NET plugins that run server-side in process-isolated Windows Server containers. We deployed one. Within minutes we had SYSTEM on the box, a full LSASS dump, NTLM hashes, DPAPI master keys, a production TLS private key for Microsoft’s sandbox infrastructure, internal Microsoft tenant IDs, 52 other customers’ organization GUIDs, and 46 proprietary Microsoft DLLs that were never meant to leave that container.

By decompiling those DLLs (nearly 14,000 C# source files), we reverse-engineered the gRPC protocol that the sandbox uses internally, discovered every method is unauthenticated, and built custom tooling to call them. That path eventually led us to explore cross-tenant code execution, though we’ll be honest about what we could and couldn’t prove there.

This talk is about what you can pull out of a cloud sandbox when the defaults are too permissive, and how a pile of exfiltrated DLLs turned into a much bigger problem than anyone expected.

  1. The Plugin (5 min) • Quick primer on Dataverse Custom API plugins and how deployment works over the OData REST API. • Our EchoPlugin: a .NET assembly that runs commands via cmd.exe and returns output through the Dataverse API. Built and deployed using only documented platform features. • The deployment tooling we wrote (MSAL device-code auth, strong name signing, automated registration). We plan to release this. • No exploits involved. This is a standard Dataverse feature. You just need a license.

  2. SYSTEM in One Command (5 min) • We land as ContainerAdministrator on Windows Server 2022 (Build 20348) with SeDebugPrivilege and SeImpersonatePrivilege. • SYSTEM via sc create with obj=LocalSystem. One command. • This sets the stage for everything that follows. We now have full access to the container’s memory, filesystem, and registry.

  3. What We Pulled Out (15 min) • This is the core of the talk. Once you have SYSTEM on one of these containers, the amount of sensitive material you can grab is alarming. • LSASS dump via ProcDump, which Microsoft helpfully left in the container. From that: the local Administrator NTLM hash, 28 DPAPI master keys, the boot key, LSA secrets, cached credential decryption keys. • Registry hive export (SAM, SECURITY, SYSTEM). Exfiltrated via certutil base64 encoding through the API. • Full SandboxWorker process memory dump (349 MB). Inside we found: a production RSA 2048-bit TLS private key for wus107.prd.sbx.dynamics.com (confirmed matching via OpenSSL), 52 co-located customer organization GUIDs, 4 internal Microsoft tenant IDs, cluster names and internal endpoint URIs. • Environment variables from the worker process: auth nonces, Azure app and tenant IDs, sidecar host addresses, internal service configuration. • 46 proprietary Microsoft DLLs totaling 30 MB. These include the identity model libraries (Microsoft.IdentityModel.S2S and friends), the SidecarContract library with full gRPC protobuf definitions, the SandboxWorker binary itself, and various CRM runtime components. We decompiled all of them: 13,889 C# source files. • 400 MB+ exfiltrated to our own Azure Blob Storage. Azure-to-Azure, same region, took seconds. No DLP, no alerts.

  4. From DLLs to gRPC (10 min) • The SidecarContract DLLs contained the full protobuf definitions for the gRPC protocol between SandboxWorker and a host-side sidecar process. This was the key find in the DLL haul. • We built custom Go gRPC clients using those definitions to call every sidecar method. There are 20+ across 3 services. None of them require authentication. • Read methods: GetEnvironmentVariables (worker nonces, internal tenant IDs), GetWorkerAssignedMetadata (co-located org GUIDs), GetOpenIdSigningKeys (full JWKS with 5 RSA keys and cert chains), GetClusterEnvironmentSettings, GetServiceParameters. • Write methods: ReportWorkerBusy (DoS for all tenants on the container), SendCrashEvent (inject fake telemetry), SetNamingServiceProperty (modify Service Fabric naming), ProcessPortProxyRequest (create network routes to arbitrary IPs, including 169.254.169.254). • We produced an OpenAPI spec documenting 27 methods across 3 services. We’ll walk through the interesting ones live.

  5. Cross-Tenant Execution (7 min) • The unauthenticated sidecar, combined with org identity stored in patchable process memory, opens a path to cross-tenant code execution: steal the worker nonce, patch the org GUID in memory, send a crafted Execute request with a target org ID and your own .NET assembly. • We got context.OrganizationId to return another customer’s GUID. On one container we intercepted their SDK callbacks (RetrieveMultiple for systemuser, businessunit, solution tables). • To be upfront: we proved the execution context switches, but we did not achieve full data exfiltration from a victim tenant. The bidirectional callback protocol needs more work. So this is real, and it’s scary, but we’re not going to oversell it.

  6. What Held and What Didn’t (3 min) • Credit where it’s due. Microsoft blocked IMDS, filtered cross-container networking, stubbed device IOCTLs, sandboxed driver loading (returns success but never executes), no host filesystem, no Docker socket. • What failed: no auth on the sidecar, no network isolation between plugin code and infrastructure services, privileged container defaults, wide-open outbound internet, ProcDump sitting in the container, org identity in patchable memory.

  7. Takeaways (5 min) • What this means if you’re running Dataverse plugins or Power Platform in your environment. • The pattern here (over-privileged sandbox, unauthenticated internal services, identity in patchable memory) is not unique to Dataverse. How to audit for it in other multi-tenant platforms. • Disclosure timeline and Microsoft’s response.

About the Speaker