Scanning Windows container images is (surprisingly) easy!

When it comes to Linux containers, there are plenty of tools out there that can scan container images, generate Software Bill of Materials (SBOM), or list vulnerabilities. However, Windows container images are more like the forgotten stepchild in the container ecosystem. And that means we’re forgetting the countless developers using Windows containers, too.

I wanted to see what I’d need to make scanning tools for Windows container images. Turns out it’s pretty easy. So easy, in fact, I think the existing container tools should add support for Windows container images.

What version of Windows am I running?

The first question I needed to answer was: what version of Windows was the container image based on? This tells me what date the container image is from, what updates are applicable, and what vulnerabilities it has.

Container images are really just tar files, and Windows container images are no different. So first I saved a Windows container image locally using skopeo:

1$ skopeo --insecure-policy --override-os windows copy docker://mcr.microsoft.com/windows/nanoserver:ltsc2022 dir:///tmp/nanoserver
2$ ls /tmp/nanoserver
30db1879370e5c72dae7bff5d013772cbbfb95f30bfe1660dcef99e0176752f1c  7d843aa7407d9a5b1678482851d2e81f78b08185b72c18ffb6dfabcfed383858 manifest.json version

Next, I inspected the manifest using jq to find the layer that had the Windows files.

 1$ jq . manifest.json
 2{
 3  "schemaVersion": 2,
 4  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
 5  "config": {
 6    "mediaType": "application/vnd.docker.container.image.v1+json",
 7    "size": 638,
 8    "digest": "sha256:0db1879370e5c72dae7bff5d013772cbbfb95f30bfe1660dcef99e0176752f1c"
 9  },
10  "layers": [
11    {
12      "mediaType": "application/vnd.docker.image.rootfs.foreign.diff.tar",
13      "size": 304908800,
14      "digest": "sha256:7d843aa7407d9a5b1678482851d2e81f78b08185b72c18ffb6dfabcfed383858"
15    }
16  ]
17}

I then extracted the layer and fixed the permissions.

 1$ mkdir layer
 2$ tar -xf 7d843aa7407d9a5b1678482851d2e81f78b08185b72c18ffb6dfabcfed383858 -C ./layer/
 3$ sudo find ./layer -type f -exec chmod 0644 {} \;
 4$ sudo find ./layer -type d -exec chmod 0755 {} \;
 5$ ls -lah layer/
 6total 16K
 7drwxr-xr-x 4 jamie users 4.0K Dec 28 15:05 .
 8drwxr-xr-x 3 jamie users 4.0K Dec 28 15:00 ..
 9drwxr-xr-x 5 jamie users 4.0K Dec  9 01:18 Files
10drwxr-xr-x 3 jamie users 4.0K Dec  9 01:22 UtilityVM
11$ ls -lah layer/Files/
12total 28K
13drwxr-xr-x  5 jamie users 4.0K Dec  9 01:18 .
14drwxr-xr-x  4 jamie users 4.0K Dec 28 15:05 ..
15-rw-r--r--  1 jamie users 5.6K Dec  9 01:18 License.txt
16drwxr-xr-x  4 jamie users 4.0K May  7  2021 ProgramData
17drwxr-xr-x  6 jamie users 4.0K Dec  9 01:19 Users
18drwxr-xr-x 20 jamie users 4.0K Dec  9 01:19 Windows

Inside the extracted layer there are two directories: Files and UtilityVM. Files had the filesystem of the Windows container image, while UtilityVM is used by Hyper-V behind the scenes. So I just needed to focus on Files.

How did I figure out the specific version of Windows the container is running? From the registry of course! The SOFTWARE registry hive contained information about installed software, including Windows itself, and was found at Files/Windows/System32/config/SOFTWARE.

Thankfully, there’s a great NuGet package called Registry that let me easily load and parse the registry, but there are also packages for Go, Rust, and even Node.js.

1using Registry;
2
3var registryHive = new RegistryHive("/tmp/nanoserver/layer/Files/Windows/System32/config/SOFTWARE");
4registryHive.ParseHive();
5var currentVersion = registryHive.GetKey(@"Microsoft\Windows NT\CurrentVersion");
6var fullVersion =
7    $"{currentVersion.GetValue("CurrentMajorVersionNumber")}.{currentVersion.GetValue("CurrentMinorVersionNumber")}.{currentVersion.GetValue("CurrentBuildNumber")}.{currentVersion.GetValue("UBR")}";
8Console.WriteLine(fullVersion);

Running this code, I got version 10.0.20348.1366 which was apparently released on 13th December 2022.

What about Windows updates?

The version of Windows doesn’t tell the whole story. There are also updates that can be applied on top. You might have seen them referred to by their KB number, for example KB1234567. Information on what updates have been applied is also stored in the registry.

By extending my earlier code, I can find out what updates this container image has.

 1var packages = registryHive.GetKey(@"Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages");
 2var updatePackageRegex = new Regex(@"^Package_\d+_for_(KB\d+)~\w{16}~\w+~~((?:\d+\.){3}\d+)$");
 3
 4var updates = new Dictionary<string, string>();
 5foreach (var packageKey in packages.SubKeys)
 6{
 7    if (!updatePackageRegex.IsMatch(packageKey.KeyName))
 8    {
 9        continue;
10    }
11
12    var currentState = packageKey.Values.Find(v => v.ValueName == "CurrentState")?.ValueData;
13
14    // Installed
15    if (currentState == "112")
16    {
17        var groups = updatePackageRegex.Match(packageKey.KeyName).Groups;
18        updates[groups[1].Value] = groups[2].Value;
19    }
20}
21
22foreach (var update in updates)
23{
24    Console.WriteLine($"{update.Key}: {update.Value}");
25}

Running this gave me a single update: KB5020373: 20348.1300.1.0. Searching online for KB5020373 led me to the documentation for the update. It’s the November 2022 security update for .NET Framework and has a fix for CVE-2022-41064.

Done! …Now what if we scaled this?

It turns out it’s not that difficult to find out info about Windows container images. It took me a couple of hours to figure out, but that’s only because no one seems to have done this before. The actual code is only about 30 lines.

Windows containers are widely used for legacy applications, like .NET Framework applications, that haven’t been rewritten but could benefit from the cloud. All of the big three cloud providers offer managed Kubernetes services that support Windows nodes out of the box (yes, Kubernetes supports Windows nodes). There is clearly a demand for Windows containers, but there is a gap in the kind of container tooling that has sprung up for Linux containers.

Instead of allowing this gap to widen further, I think that container tool authors—especially SBOM tools and vulnerability scanners—should add support for Windows container images. These tools should then correlate the extracted information with the Microsoft Security Research Center (MSRC) API. MSRC publishes information every month on security updates. Comparing the Windows version from a container image with the fixed versions provided by the MSRC API, you could easily see your container image’s security vulnerabilities.

As my proof-of-concept has shown, it’s low-hanging fruit. A small addition that would have a big impact for the many forgotten developers and the applications they work on.

comments powered by Disqus