Photo by mari lezhava on Unsplash

A few years back, I wrote dotnet-retire as I was missing a quick way of auditing my .NET Core applications for vulnerable dependencies. The tool handles the dependencies check at build time. Microsoft did not have any tooling in that space at that time, and is still lacking it some years later. The good news is that they recently have started working on it, and there is now a design spec at the NuGet team for similar functionality. When that hits release, I’m hoping to deprecate dotnet-retire .

What I realised in creating that tool, is that it does not cover the whole story. The .NET runtimes we install and run our applications on are also subject to security flaws.

Example:

As the announcements reports, our .NET Core app is vulnerable if it is

[..] running on .NET Core 2.1.0–2.1.12, or .NET Core 2.2.0–2.2.6

When they release these announcements, they have ready security patches for us to download and install. In the sample announcement above, the security patches were 2.1.13 and 2.2.7. Before these were released, 2.1.8 was another security release (now deemed vulnerable). So, this space can change rather quickly.

The announcement provides guidance into how we can check if we are affected, which is to log in to the server and run dotnet info . Alternatively, dotnet --list-runtimes . It will list out all runtimes installed on the server. Our job is to look out for any of the known vulnerable runtimes, and check if our app is running on any of them.

So, what runtime is my app really running on?

In .NET Core, there’s a roll forward mechanism that impacts the choice of runtime. One may install new minor versions of a runtime, and restart the app — and the app will run on the new compatible runtime. No re-compilation needed. Although we specify a specific version as a package reference, it actually has little effect (source):

Specifying a version number on the Microsoft.AspNetCore.App reference does not guarantee that version of the shared framework will be chosen. For example, suppose version "2.2.1" is specified, but "2.2.3" is installed. In that case, the app will use "2.2.3"

What this basically means, is that examining the package reference in the csproj is not sufficient to resolve what runtime is actually in use. The resolved runtime stems from a combination of the target framework as well as the latest runtimes installed on the server.

For ASP.NET Core 3, they removed the need to specify the ASP.NET Core framework reference as a package reference alltogether.

Projects that target the Microsoft.NET.Sdk.Web SDK implicitly reference the Microsoft.AspNetCore.App framework.

Automate it

To automate the detection, I created a small ASP.NET Core endpoint listing what runtime our application is running on, and checks if it is among the vulnerable. To host it, I run it as a middleware in separate request pipeline:

app.Map("/report", a => a.UseRuntimeVulnerabilityReport());

Given an ASP.NET Core app running on the vulnerable 2.1.11 runtime, it outputs the following payload — linking to relevant CVEs the security patch includes.

{

"isVulnerable": true,

"appRuntimeDetails": {

"appRuntimeVersion": "2.1.11"

},

"securityRelease": {

"runtimeVersion": "2.1.13",

"cvEs": [ .. ]

}

}

With that in place, we can add monitoring. Hook a recurring request into any monitoring tool and the result is live reporting of security patches for us to install. Of course, we don’t want to expose these kind of vulnerabilities to the public, so it would be wise to run this pipeline protected by a preferred authorization scheme, or an internal port.

Another monitoring option is use a .NET Core BackgroundService (.NET Core 2.1+). In the dotnet-retire project, I created a separate package for it. Installing the check is done via a registration into the container:

services.AddRetireRuntimeHostedService();

Running on regular intervals, it checks and logs if a report if vulnerable runtime is detected. It’s using the configured ILogger , and logs it at the Warning log level. It includes an optional config setting, so you can check as often or seldom as you prefer. A sample log statement it provides, given you have configured the Console logger:

warn: RetireRuntimeBackgroundService[0]

Running on vulnerable runtime 2.1.11. Security release 2.1.13

Both the endpoint and the background services are available on nuget.org if you want to try them out (see links at the end).

Other commercial options

Of course, there are tools out there like Snyk (free for Open Source) and Dependabot (free for Open Source on GitHub) one might use for build time dependency check. As far as the runtime check goes, I haven’t seen if they have any similar offerings as the endpoint here.

A (not so) funny side note. I checked in a sample vulnerable web app to GitHub I use for testing, and soon after received a PR from Dependabot to update from one vulnerable version of the shared runtime to another vulnerable version. Perhaps a glitch, but nevertheless still a very much out of date “fix”. ️

No thanks, bot! 🤷‍♂️🤖😅

For more info about the reporting code, browse it at GitHub:

Relevant NuGets on nuget.org: Middleware, BackgroundService