~/anand/writeups $ cat dependency-confusion-teams.md

dependency confusion on microsoft teams infrastructure

=======================================================
may 2024 · dependency confusion · supply chain · msrc

this write-up covers a dependency confusion vulnerability i found against microsoft teams infrastructure and reported to msrc (microsoft security response center). a single internal npm package that was missing from the public registry was enough to get a package i controlled pulled and executed on microsoft-owned systems.

in line with responsible disclosure, the internal hostname and all source ip addresses are redacted throughout this post.

recon
-----

while enumerating teams-related infrastructure, i found a publicly reachable package.json exposed on one of the teams sub-services:

https://<redacted>.teams.microsoft.com/package.json

an exposed manifest like this is a goldmine for supply-chain testing — it lists every direct dependency a service pulls in, including internal-only packages that were never meant to be visible to the outside world.

finding the gap
---------------

i ran the dependency list through dustilock — a tool that flags npm/pypi packages susceptible to dependency confusion — and cross-checked the results manually against the public npm registry.

one dependency stood out: hydra-player-sdk. it was referenced in the manifest, but it did not exist on the public npmjs.com registry. that gap is the exact precondition for dependency confusion — an internal package name with no public owner, free for anyone to claim.

at that point the idea to test for dependency confusion was obvious.

the technique
-------------

dependency confusion (a.k.a. namespace / substitution attack, first detailed by alex birsan) abuses how package managers resolve names. if a build resolves hydra-player-sdk and can reach the public registry, it will happily fetch a public package of that name — and most resolvers prefer the highest available version when the source isn't explicitly pinned.

attack flow
-----------

how dependency confusion works, in general:

ATTACKER owns a public npm account PRIVATE REGISTRY internal-lib @ 1.2.3 the intended package PUBLIC REGISTRY (npm) internal-lib @ 99.0.0 uploaded by attacker PACKAGE MANAGER install internal-lib — picks the HIGHEST version it can see MALICIOUS CODE RUNS install / postinstall script secrets & shell → attacker 1. publish the same name at a higher version v1.2.3 v99.0.0 2. installs the malicious one

because the build can also reach the public registry, the attacker's same-named package at a higher version wins over the private one — unless internal scopes are pinned to the private registry.

exploitation
------------

so i claimed the name. i registered hydra-player-sdk on npmjs.com under my own account and published it with version 2.2.x — deliberately higher than any plausible internal version.

note — always publish with a version higher than the one in the target manifest. if the internal source/version isn't explicitly pinned, the package manager prefers the higher (public) version on the next install or build.

the package's index.js was harmless by design — it only phoned home. i embedded two independent confirmation channels:

this proved code execution without touching any data or running a single destructive command.

the callback
------------

a couple of hours after publishing, the callbacks started landing. i received dns interactions from two microsoft-owned azure ip addresses (verified via ipinfo.io):

104.208.•••.•••   →  microsoft / azure   (redacted)
40.70.•••.•••     →  microsoft / azure   (redacted)

in parallel, the canary token tied to the package fired. collaborator dns hits and a canary trigger, both originating from microsoft-owned infrastructure — that combination confirmed my public hydra-player-sdk had been pulled and executed by microsoft systems.

the payload was intentionally limited to beaconing. in a real attack, the same index.js could have carried os-command execution and data exfiltration — which is exactly what turns dependency confusion from a curiosity into a supply-chain remote-code-execution primitive.

how dependency confusion happens
--------------------------------
remediation
-----------
disclosure
----------

i reported this to msrc with the canary and collaborator evidence plus the microsoft-owned source ips. i flagged that if it was already known or considered acceptable risk, it could be closed as informative — but the out-of-band execution from microsoft infrastructure made the impact clear.

timeline
--------
outcome
-------

msrc accepted the report — classified as remote code execution, marked complete, and a bounty was awarded.

msrc researcher portal showing the dependency confusion report marked complete with bounty awarded closed
msrc researcher portal — report complete, bounty awarded closed, impact: remote code execution (case number redacted)

cd ..