Back to JetBrains

Building a Supply Chain Attack With .NET and NuGet | Maarten Balliauw | .NET Day 2026

JetBrainsOctober 15, 202640m
In a Nutshell

Supply chain attacks against .NET/NuGet rely on dependency confusion, Unicode typosquatting, source generators, MSBuild targets, and startup hooks to inject malicious code during package restore or build. Attackers can use module initializers and Covenant C2 frameworks to deploy stealthy droppers that execute before the application starts, while lock files, package signing, and SBOM generation provide critical detection points. Effective defense requires layered mitigations—source mapping, thorough PR reviews with Unicode detection, dependency tracking, and behavioral monitoring—since single controls can be bypassed.

AI-Generated Notes

These notes were generated by AI and may contain inaccuracies.

A software supply chain attack occurs when a malicious party attacks elements in a software supply network. This could involve accessing dependencies or hacking into tools to send malicious payloads to customers. The goal is to infiltrate the supply chain and infiltrate systems of end customers or end users. These attacks often use a "sleeper cell" approach where the attacker injects something and waits, sometimes a year after the initial attack, before the actual attack begins.

A notable example is the Shah Hulot attack on NPM, where an attacker compromised over 500 packages on NPM. The attack involved running a script whenever someone installed the package, including the bun environment.js file with the malicious payload. This was installed on developers' machines and CI runners, creating persistent integration triggers that allowed attackers to capture commands, deploy wiper tools, and extract secrets like AWS and Azure passwords.

Supply chain attacks and malware typically contain several components. The dropper serves as primary code executed on the victim's device, ideally being discreet to establish a foothold. Payloads are then run by the dropper to perform actions often received via a command and control (C2) server. This C2 infrastructure gives instructions to collect environment variables, inspect files, conduct network reconnaissance, and execute commands.

Attack vectors include accessing source code through social engineering, pull requests, or obtaining maintainer account details. Dependency confusion involves posting packages that should be private to the public registry. Typosquatting uses Unicode characters that look similar to legitimate package names, such as using a dotted Ukrainian letter I instead of a regular letter I.

When installing packages in NuGet, the system searches through all configured package sources, with the first source to respond winning. Since nuget.org is very fast and supported by a CDN, it often wins. An attacker could publish a package named companyname.theframework on nuget.org, and organizations would download the malicious copy because nuget.org is faster than internal servers.

Mitigation strategies include using package source mapping in nuget.config to route specific package patterns to internal servers. Package signing and verification allows specifying trusted signatures for particular authors or owners. Package ID prefix registration on nuget.org prevents others from uploading packages with company name prefixes.

NuGet supports lock files that specify dependencies with their source and hash values. NuGet can then refuse to restore packages that don't match the original lock file.

In early 2025, experiments showed Unicode typosquatting vulnerabilities. A package called Microsoft.Extensions.AutoMapper was uploaded using a Ukrainian letter "I". After reporting to NuGet support, the package was removed and detection improved. Further experiments showed Microsoft.EntityFrameworkCore could be uploaded using "RN" characters that look like "M" when narrowed, and later with multiple Unicode symbols.

Pull requests were submitted to popular projects including IdentityServer showing the Unicode changes. While the PR was never merged, the diff view on GitHub didn't clearly indicate the package ID change. Microsoft Security initially classified this as not meeting vulnerability requirements but later acknowledged it as a medium-risk vulnerability. GitHub stated Unicode symbols don't pose major security risks.

GitHub Copilot can detect non-ASCII characters that are similar in form when reviewing pull requests. NuGet has fixed the Unicode symbol upload issue, but the problem persists on GitHub. Recommended mitigations include careful examination of pull requests, using package source assignment, package signing, and lock files. Tools that create software bill of materials (SBOM) lists can alert when dependencies change.

The dropper should execute code close to NuGet package installation. Module Initializers can run code before the application starts. A static class with the "module initializer" attribute can execute code, such as writing to console, before the host starts.

When packaged as a dependency and referenced in a project, the module initializer runs automatically. The code can be hidden by marking it as "created by the translator" and catching exceptions to avoid detection.

Source generators inject code into applications without requiring explicit calls. The generator adds classes to build output automatically. Environment variable checks can prevent execution in CI environments. Rider shows source generators in the dependency chain, though they can be made to look legitimate.

Mitigations include using the "emits compiler generated files" build property to write output to disk for examination, and reviewing new dependencies for suspicious additions.

NuGet packages can include package.targets files that add generated files to build output. This allows code execution without source generators by shipping MSBuild targets. When the dependency is updated, the MSBuild targets execute during compilation.

Mitigation requires reviewing targets and imported properties in development environments, though blocking targets would disable core .NET functionality like Razor and Blazor pages. Auditing is the primary defense.

Startup hooks fire when the DOTNET_STARTUP_HOOKS environment variable is initialized, running whenever .NET commands execute. This allows code execution at the beginning of any .NET process on the system. The hook can mirror console output or perform other actions.

Other attack surfaces include NuGet packages containing only source code with hidden malicious content, development environment plugins, user profiles, GitHub Actions, and NPM packages used alongside .NET development. AI-assisted coding tools could potentially inject malicious code that might not be detected, especially in large pull requests that receive quick approval.

Payloads should be lightweight and can use obfuscation, Base64 encoding, invisible Unicode characters, and steganography to hide instructions within images. These techniques help evade antivirus scanners and avoid detection.

As far as payloads go, whatever you want. Construction simply depends on what you want to do. If you want to collect credentials, go ahead. If you want to drain cryptocurrency wallets, you can do that too. If you desperately need AI code, maybe you can just use cloud-based code executables on the victim's machine as well. Creative combinations are plenty.

The next thing needed for a supply chain attack is command and control - the ability to direct and send new execution paths and new commands to the malware on the victim's system. A place to connect to the main server is required. If collecting credentials, the data must be collected and sent somewhere. This should be done as stealthily as possible. HTTP is a good option. A dedicated server, posting something in a GitHub discussion, using a public GitHub calendar, using OneDrive, or anything else. All of these techniques have been used in the past in different environments. ICMP can be used in ping message fields. In the past, malware simply added memos to the Solana blockchain and said "Okay, here are your new commands." These memos never disappear because they become part of the public blockchain, so anything listening or monitoring them can receive the new commands.

Building command and control infrastructure requires a lot of work. Covenant is a .NET-based C2 framework that aims to highlight the .NET attack surface. It is a Docker container that can be run on any host and will serve as ready-to-use C2 infrastructure. Covenant provides dashboards where listeners for incoming traffic can be set up, whether it's an IP address, a virtual host somewhere, or something else. Endpoints are deployed around the world to receive incoming data. Launchers come with droppers and other tools that can be reused in supply chain attacks. Custom implementations are also possible.

When deployed and systems are compromised, a graph of the number of nodes is visible. Clicking on a node allows remote connection and seeing what's inside.

All of this already exists, and executing an attack like this would be relatively easy. To mitigate these risks, explore the various tools and practices on NuGet and GitHub. There is a lot of guidance there that contains valuable information. When there are pull requests, do thorough reviews using Copilot, manually, or by whatever method is available. Knowing what's embedded in software is important.

Tools like Trivy, Aikido Security, Sneak, and Striker are available. Endpoint detection on developers' machines helps, even though developers hate it. Environment variable changes or DNS hijacking can be detected. If something suspicious is seen, dig deeper.

Andres Freund, who works at Microsoft, was contributing to the Linux library but noticed that logging into the SSH interface took 500 milliseconds longer than usual. He started investigating and actually prevented one of the most catastrophic supply chain attacks ever. If something suspicious is seen, even a 500-millisecond delay that wasn't there before, it should be looked into.

The Swiss cheese model applies here. Every product has a supply chain and quality assurance in many steps. For example, with breakfast cereal, there is the farm that performs quality assurance, then the factory that does it, and maybe the stores as well. Different steps ensure quality in various ways, but ultimately there should be a safe, good box of cereal on the table in the morning.

Software needs different places where the supply chain and quality assurance are checked for everything that goes into it. The supply chain already has a number of checks. There are warnings in NuGet about CVEs. There is GitHub security scanning, and Dependabots to perform updates. NuGet has package signing, assembly signatures, log files, and all that.

NuGet essentially unifies all the different ways packages are built - whether CS Proj, NuGet Spec, or something else. There is no real difference in how the package is built as long as the components are inside it.

Some of these attacks seem a bit theoretical, like name manipulation. The attacks often operate covertly, and attackers take their time to deploy them. Visiting NuGet and downloading all packages reveals a lot of Unicode and similar content. They might not all be malicious, but there are likely packages waiting for the moment someone sends 500 pull requests and passes things into AutoMapper, MVC, or something else.

The pull request example is the most concerning - someone can do something that looks very innocent while introducing a completely different dependency than what is being used. Through open-source maintenance, people in the community often send pull requests saying they updated Entity Framework because there is a new version of .NET. These requests are often very helpful, but without seeing what is being accepted, something can be added to the supply chain that wasn't supposed to be there.

There is no way to disable imported source code generators. .NET uses a lot of source code generation in its tasks. Using System.Text.Json shows generated JSON classes and deserializations taking place. Using regular expressions in modern .NET shows regular expressions compiled by a source code generator. Source code generators are used in many places, and disabling them would break legitimate functionality.

As a user of potentially infected dependencies, a materials list can be created with every build of projects. Barry Dorrance, one of the security officers on Microsoft's .NET team, has a package that creates a materials list with every build. This can be put in a database, and whenever a change in any dependency in the chain looks suspicious, those things can be checked out. Tracking dependencies, and more importantly transitive dependencies, in projects is essential. This can be automated in Continuous Integration.

The S-bomb package will issue an XML file or standard format during each build that can be loaded into a system for comparison.

Just opening the project is bad too. Running the payload as soon as possible after the package is installed is the goal, and having source generators is a very good way to do that because there is a design-time build process that ensures the ability to build and work on the project, getting IntelliSense and all that. The source generator approach is a very straightforward method that works as soon as the package is installed.

The source generator does not necessarily have to issue code. It might also put an environment variable or add something to the macOS profile. The next time the system boots up or reboots, that is when the malicious payload runs. It does not have to be inside the source code being built.

NPM added a cooling-off period, so a new package has to be a certain age before it can be installed. Nobody releases a weak package and everyone else updates it immediately. There is a wait of a day or two for people to actually see that. NuGet does not have anything like that. What NuGet has is a typo-grabbing check when a new package is uploaded. NPM is also part of Microsoft, so the teams communicate and ideas will pass between them.

NPM has command-line commands that can be run to prevent shell execution or commands upon restoration. NuGet used to have something similar where PowerShell scripts could be run when packages were installed, but that was removed a long time ago.

Thank you very much for this very interesting talk.

Keep JetBrains in your library

Save the videos and channels worth coming back to, and find them again in one place.