NuGet Vulnerability Scanning: What dotnet audit... | GeekWala
Loading...
Skip to main content
Ecosystem Guide

.NET Dependency Security: Beyond dotnet audit and NuGet Advisories

.NET's NuGet ecosystem powers millions of enterprise applications, but built-in tools like dotnet list package --vulnerable only show you advisory matches — not which vulnerabilities are being actively exploited. Learn how to scan your .csproj and packages.config files and prioritize real threats.

GeekWala Team4 min read

The Silent Risk in Your .csproj File

Your team ships a new feature to your ASP.NET Core microservice. It's polished, tested, and your CI pipeline passes. But buried in your packages.config (or worse, not even checked since 2019) is an older version of Newtonsoft.Json with a known deserialization vulnerability—CVE-2022-24765.

You run dotnet list package --vulnerable and get output. A few advisories show up. Your team marks the task as "medium priority" and moves on. Three months later, you're reading a security disclosure: that Newtonsoft.Json vulnerability is now on the CISA Known Exploited Vulnerabilities (KEV) catalog. Attackers have working exploits. Now it's "critical."

This is the gap between having vulnerability advisories and knowing which ones matter right now. The .NET ecosystem has grown to 350+ million NuGet packages, but most teams still rely on basic tooling that can't distinguish between a bug report and an active attack vector.


TL;DR

NuGet dependency scanning goes beyond dotnet audit: You need visibility into which vulnerabilities have active exploits (CISA KEV) and which are likely to be exploited soon (EPSS scores). Learn the difference in our guides on EPSS and exploit prediction and CISA KEV-tracked vulnerabilities.


What We'll Cover


The NuGet Ecosystem: PackageReference vs packages.config

PackageReference (Modern Standard)

Since .NET Core 1.0 (2016), the PackageReference format in .csproj files has been the recommended way to declare dependencies:

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
  <PackageReference Include="Dapper" Version="2.0.123" />
  <PackageReference Include="StackExchange.Redis" Version="2.6.86" />
</ItemGroup>

Strengths:

  • Transitive dependencies are resolved automatically
  • Supports version ranges ([12.0, 13.0))
  • Works with Directory.Packages.props for centralized version management
  • Integrates cleanly with MSBuild and NuGet restore
  • First-class support in modern tooling

Vulnerability scanning implications:

  • Direct and transitive dependencies are explicit in the lock graph
  • Version pinning strategies matter: 13.0.1 vs 13.0.* vs [13.0, 14.0) all have different attack surfaces
  • Directory.Packages.props centralizes risk: if your shared version is vulnerable, all projects inherit the issue

packages.config (Legacy Format)

Older .NET Framework projects (pre-SDK) still use packages.config, an XML file in the project root:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Newtonsoft.Json" version="11.0.2" targetFramework="net462" />
  <package id="log4net" version="2.0.8" targetFramework="net462" />
</packages>

Weaknesses for security:

  • Transitive dependencies aren't always explicit
  • Harder to detect unused packages (security hygiene issue)
  • Version pinning less flexible
  • Slower to update: often requires manual removal and re-add
  • Many teams never audit packages.config files

Reality check: Even though .NET Framework reached end-of-life in April 2024, millions of legacy applications still depend on packages.config. If you're supporting enterprise customers, you're probably maintaining at least one. Microsoft's .NET Support Policy provides guidance on migration timelines.

Transitive Dependency Risks

A critical .NET security principle: your project's risk profile includes not just direct dependencies, but the entire dependency tree.

MyApp.csproj
├── Newtonsoft.Json (13.0.1) ← you explicitly depend on this
├── EntityFramework (6.4.4) ← you explicitly depend on this
│   └── EntityFramework.SqlServer (6.4.4)
│       └── System.Data.SqlClient (4.8.5)
│           └── Microsoft.Identity.Client (4.46.0)
│               └── System.Net.Http (4.3.1) ← VULNERABLE to OpenSSL padding oracle
│                   └── System.Security.Cryptography.Algorithms (4.3.0) ← VULNERABLE
└── Serilog (3.0.1)
    └── Serilog.Sinks.File (5.0.0)

In this tree, you have five layers of dependencies. A vulnerability in System.Security.Cryptography.Algorithms (layer 5) is your problem even though you never directly referenced it. The .NET runtime doesn't know to patch it until you update EntityFramework, which depends on SqlClient, which depends on Microsoft.Identity.Client.

This cascading effect is why transitive dependency visibility is critical.


What dotnet audit Finds—and Doesn't

dotnet audit: The Built-In Scanner

Microsoft's dotnet audit command is the official way to scan for known vulnerabilities:

dotnet list package --vulnerable
# or
dotnet audit

Sample output:

Package          Current  Resolved  Severity
Newtonsoft.Json  11.0.2   11.0.2    high     Microsoft Security Advisory
log4net          2.0.8    2.0.8     critical RCE via unsafe logging patterns
System.Net.Http  4.3.1    4.3.1     high     OpenSSL padding oracle

What It Does Well

Scans .csproj and packages.config files automaticallyDetects most published CVEs (data from National Vulnerability Database) ✅ Shows direct and transitive dependenciesZero setup required (part of .NET SDK) ✅ Fast (typically <1 second) ✅ Supports suppression (through NuGet.config)

The Critical Gaps

No EPSS scoring: You don't know which vulnerabilities are likely to be exploited next ❌ No CISA KEV matching: You don't know which vulnerabilities already have working exploits ❌ No context about active exploitation: All "critical" vulnerabilities look the same ❌ No prioritization: A 10-year-old patched advisory and a 3-day-old zero-day both show as "high" ❌ Limited remediation guidance: Just tells you to upgrade, not whether the upgrade is easy or introduces breaking changes ❌ No dependency update impact analysis: Doesn't warn you if an upgrade could break your code

Comparison Table: dotnet audit vs GeekWala

Featuredotnet auditGeekWala
Scans .csproj
Scans packages.config
Scans Directory.Packages.props
Shows EPSS scores✅ (0.0–1.0 scale)
Shows CISA KEV status✅ (actively exploited)
Indicates exploit availability
Tracks exploitation trends
Suggests update paths⚠️ (suggests newest)✅ (analyzes breaking changes)
Multi-project workspace support
API for CI/CD integration⚠️ (exit code only)✅ (JSON, webhooks)
Historical vulnerability tracking

How GeekWala Scans .NET Projects

Supported File Formats

GeekWala parses three NuGet dependency declaration formats:

1. PackageReference (.csproj)

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

Extraction: Package name + exact version pinned in XML

2. packages.config

<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net6.0" />

Extraction: Same approach; targetFramework is noted but not a blocker

3. Directory.Packages.props (Central Package Management)

<ItemGroup>
  <PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

Extraction: Central versions; linked to consuming projects automatically

The Scanning Process

When you upload your .NET project to GeekWala:

  1. File discovery: We recursively scan for .csproj, packages.config, and Directory.Packages.props
  2. XML parsing: Extract package names and versions using XPath queries
  3. Transitive resolution: For projects using modern tooling, we resolve the full dependency tree (we parse .csproj version constraints like 13.0.* and resolve them against NuGet.org)
  4. Vulnerability lookup: Cross-reference each package+version against:
    • OSV (Open Source Vulnerabilities): Primary CVE source
    • EPSS: Exploitation Prediction Scoring System (0.0–1.0)
    • CISA KEV: Known Exploited Vulnerabilities catalog
  5. Risk scoring: Combine severity, EPSS, and exploitation status
  6. Remediation routing: Suggest compatible upgrade paths (using NuGet semantic versioning rules)

Example: Scanning a Real ASP.NET Core Project

Given this .csproj:

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
  <PackageReference Include="log4net" Version="2.0.8" />
  <PackageReference Include="EntityFramework" Version="6.4.4" />
  <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
</ItemGroup>

GeekWala's scan output would look like:

PackageVersionIssuesEPSSKEVAction
Newtonsoft.Json11.0.2CVE-2022-24765 (RCE)0.87Upgrade to 13.0.3
log4net2.0.8CVE-2022-24265 (RCE)0.91Upgrade to 2.0.14
EntityFramework6.4.4CVE-2021-24107 (Info Disc)0.52Monitor or upgrade
Microsoft.AspNetCore.Mvc2.2.012 vulnerabilities (EOL)avg 0.68✅ (3 CVEs)Migrate to 6.0 LTS

Each row includes:

  • EPSS score: How likely this CVE will be exploited in the next 30 days
  • KEV status: Whether CISA has confirmed active exploitation
  • Remediation difficulty: An upgrade from 2.2 to 6.0 is major; we flag that
  • Patch availability: We verify the patch version exists on NuGet.org

Real-World Vulnerability Patterns in .NET Libraries

Pattern 1: Newtonsoft.Json Deserialization RCE (CVE-2022-24765)

Impact: Affects millions of ASP.NET applications

Root cause: Unsafe JSON deserialization with TypeNameHandling = TypeNameHandling.All

// DANGEROUS: Allows arbitrary type instantiation
var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All
};
var obj = JsonConvert.DeserializeObject(userInput, settings);

An attacker can craft JSON that instantiates arbitrary .NET types and invoke methods:

{
  "$type": "System.Diagnostics.ProcessStartInfo",
  "FileName": "cmd.exe",
  "Arguments": "/c evil-command"
}

Vulnerable versions: < 13.0.1 EPSS score: 0.87 (87% probability of exploitation in the next 30 days) CISA KEV status: Yes (added March 2022) Patched in: 13.0.1 (October 2022) Remediation: Upgrade immediately. If you can't upgrade, set TypeNameHandling = TypeNameHandling.None (the default in 13+)

Pattern 2: log4net Serialization RCE (CVE-2022-24265)

Impact: Affects ASP.NET apps using log4net for structured logging

Root cause: Unsafe serialization of event properties in the ObjectStateFormatter

When log4net serializes objects to storage (file, database, or network sink), it uses the .NET BinaryFormatter, which is inherently unsafe:

// Vulnerable pattern: logging untrusted objects
var userData = JsonConvert.DeserializeObject<User>(request.Body); // attacker input
Logger.Info($"User activity: {userData}"); // log4net serializes userData

If an attacker controls the serialized object, they can achieve RCE during deserialization.

Vulnerable versions: < 2.0.14 EPSS score: 0.91 (91% probability of exploitation in the next 30 days) CISA KEV status: Yes (added August 2022) Patched in: 2.0.14 (August 2022) Remediation: Upgrade to 2.0.14+. If you're on legacy .NET Framework, this is non-negotiable.

Pattern 3: Microsoft.AspNetCore.Mvc EOL Cascade (2.2 vs 6.0 LTS)

Impact: Affects older ASP.NET Core projects that haven't kept up

The issue: ASP.NET Core 2.2 reached end-of-life in December 2019. Its vulnerability database is frozen.

ASP.NET Core 2.2 (EOL Dec 2019)
├── 50+ known CVEs across the framework
├── No security patches
└── Transitive dependencies pinned to vulnerable versions

ASP.NET Core 6.0 LTS (supported until Nov 2024)
├── All 50+ CVEs fixed
├── Active security updates
└── Modern dependency versions

Remediation path: ASP.NET Core upgrades are major undertakings. Common blockers:

  • Breaking API changes in middleware
  • Changes to DI container behavior
  • Entity Framework Core API shifts
  • Authentication/authorization attribute changes

Typical timeline: 2-4 weeks for a typical microservice, 2-3 months for a monolith.

Pattern 4: System.Net.Http & OpenSSL Padding Oracle (CVE-2018-0296)

The scenario: A legacy .NET Framework project depends on an old version of System.Net.Http:

<package id="System.Net.Http" version="4.3.1" targetFramework="net462" />

This package shipped with a specific OpenSSL build that's vulnerable to padding oracle attacks on TLS connections. An attacker on the network can:

  1. Intercept encrypted traffic
  2. Use timing analysis to decrypt cookies/tokens
  3. Forge authentication claims

The cascade problem:

Your app depends on:
  - EntityFramework 6.4.4
    - EntityFramework.SqlServer 6.4.4
      - System.Data.SqlClient 4.8.5
        - Microsoft.Identity.Client 4.46.0
          - System.Net.Http 4.3.1 ← VULNERABLE

Updating System.Net.Http alone won't help (it's not directly referenced). You must trace the dependency tree backward and either:

  • Update EntityFramework (bringing newer SQL Server packages)
  • Use a compatibility shim
  • Accept the risk (not recommended)

Building a Scanning Workflow for ASP.NET Core Teams

Step 1: Initial Scan Setup

Create a simple GitHub Actions workflow that scans on every PR:

name: Scan NuGet Dependencies

on: [pull_request, push]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # GeekWala API scan
      - name: Scan NuGet dependencies with GeekWala
        run: |
          curl -X POST https://api.geekwala.com/api/v1/vulnerability-scan \
            -H "Authorization: Bearer ${{ secrets.GEEKWALA_API_TOKEN }}" \
            -F "file=@packages.zip" \
            | jq '.scan_results'

      # Fallback to dotnet audit
      - name: Run dotnet audit
        run: dotnet audit

Step 2: Interpretation & Triage

When GeekWala returns results, triage based on EPSS + KEV status:

Tier 1 (Patch immediately):
- KEV = true OR EPSS >= 0.7
- Example: Newtonsoft.Json RCE, log4net RCE
- SLA: Same day (24 hours)
- Note: If EPSS >= 0.7 but no patch exists yet, mitigate (WAF rule, feature flag) and monitor daily

Tier 2 (Patch within sprint):
- EPSS >= 0.5 AND KEV = false
- Example: Info disclosure, auth bypass (no active exploit yet)
- SLA: Within 7 days

Tier 3 (Monitor):
- EPSS < 0.5 and KEV = false
- OR complexity of patch is very high
- Example: Obscure edge-case vulnerability
- SLA: Next quarterly release

Step 3: Update Strategy

For Tier 1 vulnerabilities, use this update checklist:

□ Run `dotnet add package PackageName --version X.Y.Z`
□ Run full test suite (unit + integration)
□ Check for breaking API changes (common in major versions)
□ If Entity Framework changed: check migrations
□ If ASP.NET Core changed: test middleware registration
□ If authentication changed: test login flow
□ Commit with message: "Security: Patch CVE-2022-24765 (Newtonsoft.Json RCE)"
□ Tag as `[security-hotfix]` in PR title
□ Fast-track review (no lengthy approval process)

Step 4: Continuous Monitoring

Set up daily scans for long-lived projects:

# Scheduled job (runs daily at 2 AM UTC)
0 2 * * * /usr/local/bin/geekwala-scan-project.sh

Best Practices and Common Pitfalls

✅ DO: Centralize Versions with Directory.Packages.props

<!-- Directory.Packages.props in repository root -->
<ItemGroup>
  <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
  <PackageVersion Include="log4net" Version="2.0.15" />
  <PackageVersion Include="Serilog" Version="3.0.1" />
</ItemGroup>

Benefits:

  • Single source of truth for all project versions
  • Easier to audit (one file to scan)
  • Simpler to patch (update once, all projects inherit)
  • Reduces version mismatch bugs

✅ DO: Use Semantic Versioning Constraints (When Appropriate)

<!-- Allow patch updates automatically -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.*" />

<!-- Allow minor + patch (typically safe) -->
<PackageReference Include="Serilog" Version="3.*" />

<!-- Pin exactly for stability -->
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="6.0.11" />

Scanning implication: GeekWala resolves these constraints against NuGet.org to find the latest matching version and check that too.

❌ DON'T: Leave packages.config Unscanned

If you have any legacy .NET Framework projects:

// In your CI pipeline
if (File.Exists("packages.config"))
{
    Console.WriteLine("⚠️ Legacy packages.config detected!");
    Console.WriteLine("Include in your GeekWala scan immediately");
}

packages.config files are often forgotten because the project is "legacy" and "not actively maintained." But vulnerabilities in legacy code are especially dangerous because attackers know they'll be neglected.

❌ DON'T: Ignore Transitive Dependencies

When you see:

Newtonsoft.Json 11.0.2 -> 13.0.3 (requires major version bump)

Don't assume it's safe because you "don't directly use Newtonsoft.Json APIs." Transitive dependencies can be exploited without your explicit code calling them. A deserialization vulnerability in a transitive dependency is just as dangerous as one in a direct dependency.

❌ DON'T: Patch Without Testing

Even a "safe" patch can introduce subtle bugs:

// Before upgrade (Newtonsoft.Json 11.0.2)
var settings = new JsonSerializerSettings { };
var data = JsonConvert.DeserializeObject<MyClass>(json, settings);

// After upgrade (13.0.3)
// TypeNameHandling defaults to None now (safer)
// But if your code relied on implicit type resolution, it breaks

Always run your full test suite after patching dependencies.

✅ DO: Use NuGet.config for Suppression (Carefully)

<!-- NuGet.config -->
<configuration>
  <config>
    <add key="nuget.org/v3/index.json" value="https://api.nuget.org/v3/index.json" allowInsecureConnections="false" />
  </config>

  <!-- Suppress only if you have explicit justification -->
  <packageManagement>
    <packageSource>
      <suppressed>
        <package id="Vulnerable.Package" version="1.0.0" />
      </suppressed>
    </packageSource>
  </packageManagement>
</configuration>

Suppression use case: You've confirmed the vulnerable code path is unreachable in your app, and an upgrade would introduce a breaking change. Document why:

<!-- Justification for CVE-2021-XXXXX suppression -->
CVE-2021-XXXXX affects LegacyLibrary.dll when used with X509 certificates
on Windows XP. Our application:
- Only runs on Windows Server 2016+
- Does not use X509 certificates
- Patched version breaks API compatibility with our code

Decision: Suppress until next major release cycle (Q3 2026)
Reviewed by: @security-team
Date: 2026-02-25
-->

Never suppress security vulnerabilities silently.


Frequently Asked Questions

Should I upgrade to the latest version of every dependency?

Not necessarily. The latest version might have:

  • Breaking API changes (especially true for major version bumps)
  • Different performance characteristics (newer isn't always faster)
  • Unrelated features you don't need (increased surface area)

Instead, upgrade to the earliest version that fixes the vulnerability AND maintains compatibility with your code. For example:

Current: Newtonsoft.Json 11.0.2 (vulnerable to RCE)
Latest: Newtonsoft.Json 13.0.3

Your app only uses basic JSON serialization (no type handling).
Safe upgrade: 13.0.1 (first version to fix CVE-2022-24765)
Test it. If no breaking changes, you can upgrade to 13.0.3 later for features.

How often should I scan my NuGet dependencies?

Minimum: Weekly automated scans (via CI/CD). Recommended: Daily automated scans for production applications. Emergency: Immediate ad-hoc scans when a critical vulnerability is announced.

If you're running ASP.NET Core in production, a new critical CVE can be announced any day. The longer you wait to scan, the longer you're exposed.

What's the difference between EPSS and CVSS scores?

CVSS (Common Vulnerability Scoring System): Measures the severity of a vulnerability (how bad it is if exploited). Range: 0.0-10.0.

  • 0-3.9: Low
  • 4.0-6.9: Medium
  • 7.0-8.9: High
  • 9.0-10.0: Critical

Example: Newtonsoft.Json RCE has CVSS 9.8 (critical).

EPSS (Exploit Prediction Scoring System): Predicts the likelihood of active exploitation in the next 30 days. Range: 0.0–1.0.

  • 0.0–0.1: Unlikely to be exploited
  • 0.1–0.4: Possible
  • 0.4+: Likely to be exploited soon

Example: Same Newtonsoft.Json RCE has EPSS 0.87, meaning an 87% chance of exploitation in the next month.

Why both matter: A critical vulnerability (CVSS 9.8) that's not being exploited yet (EPSS 0.02) might be lower priority than a medium vulnerability (CVSS 6.5) being actively exploited today (EPSS 0.92). Learn more: Understanding EPSS.

Can I scan NuGet packages in private repositories?

Yes, but you need to provide NuGet credentials. GeekWala supports:

  • NuGet.config authentication (API keys, Azure AD, GitHub Packages)
  • Custom registry URLs (internal myget.org feeds, Azure Artifacts)

When you upload your project, include your NuGet.config (with credentials masked), and GeekWala will resolve packages from private feeds.

What if there's no patch available for a vulnerability?

This happens. An open-source library might be unmaintained, or the vulnerability might be in an old version that the maintainer no longer supports.

Your options:

  1. Fork and patch: Create a private fork, apply a fix, deploy your patched version
  2. Switch libraries: Evaluate a maintained alternative
  3. Mitigate in code: If the vulnerability requires specific conditions, can you eliminate those conditions?
  4. Accept the risk: Document why, set a review date, monitor for active exploitation

Example: A library is vulnerable to XXE attacks. You can mitigate by:

var xmlSettings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,
    XmlResolver = null // Disable external entity resolution
};
using var reader = XmlReader.Create(stream, xmlSettings);
var doc = XDocument.Load(reader);

If you mitigate the root cause, the vulnerability is no longer exploitable in your context—but still flag it as "mitigated, not patched" in your scan results.

How do I know if a vulnerability affects my specific code?

Not all vulnerabilities affect all projects using a library. For example, Newtonsoft.Json's RCE only triggers if you explicitly enable TypeNameHandling = TypeNameHandling.All.

To determine impact:

  1. Check the CVE details: Read the full advisory
  2. Audit your usage: Search your codebase for the vulnerable code pattern
  3. Check configuration: Is the dangerous setting enabled in your config?
  4. Test the POC: If a proof-of-concept exists, run it against your app

Example decision tree:

Newtonsoft.Json 11.0.2 detected (CVE-2022-24765)
│
├─ Search codebase: "TypeNameHandling.All" → NOT FOUND
├─ Search config: "TypeNameHandling" → NOT FOUND
└─ Check JsonSerializerSettings defaults → TypeNameHandling.None (safe)

Result: VULNERABILITY PRESENT BUT NOT EXPLOITABLE
Action: Still upgrade (defense in depth), but not emergency-level

Next Steps: Start Scanning Your .NET Projects

Your NuGet dependencies are a critical attack surface. dotnet audit is a good start, but it doesn't tell you which vulnerabilities are actually being exploited or likely to be exploited soon.

For each vulnerability, GeekWala gives you:

  • Exploitation risk (EPSS score + trend)
  • Active exploitation status (CISA KEV)
  • Recommended upgrade path
  • API integration for CI/CD automation

Start with your most critical projects (production microservices, customer-facing applications) and work backward.


Stop patching by CVSS severity. Start patching by exploitation probability.

Scan your .NET project with GeekWala → — upload your .csproj or packages.config and get EPSS scores, CISA KEV status, and prioritized remediation guidance in under a minute. No account needed.


GeekWala guides:

Official Microsoft resources: