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
- What dotnet audit finds—and doesn't
- How GeekWala scans .NET projects
- Real-world vulnerability patterns in .NET libraries
- Building a scanning workflow for ASP.NET Core teams
- Best practices and common pitfalls
- FAQ
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.propsfor 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.1vs13.0.*vs[13.0, 14.0)all have different attack surfaces Directory.Packages.propscentralizes 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.configfiles
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 automatically ✅ Detects most published CVEs (data from National Vulnerability Database) ✅ Shows direct and transitive dependencies ✅ Zero 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
| Feature | dotnet audit | GeekWala |
|---|---|---|
| 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:
- File discovery: We recursively scan for
.csproj,packages.config, andDirectory.Packages.props - XML parsing: Extract package names and versions using XPath queries
- Transitive resolution: For projects using modern tooling, we resolve the full dependency tree (we parse
.csprojversion constraints like13.0.*and resolve them against NuGet.org) - 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
- Risk scoring: Combine severity, EPSS, and exploitation status
- 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:
| Package | Version | Issues | EPSS | KEV | Action |
|---|---|---|---|---|---|
| Newtonsoft.Json | 11.0.2 | CVE-2022-24765 (RCE) | 0.87 | ✅ | Upgrade to 13.0.3 |
| log4net | 2.0.8 | CVE-2022-24265 (RCE) | 0.91 | ✅ | Upgrade to 2.0.14 |
| EntityFramework | 6.4.4 | CVE-2021-24107 (Info Disc) | 0.52 | ❌ | Monitor or upgrade |
| Microsoft.AspNetCore.Mvc | 2.2.0 | 12 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:
- Intercept encrypted traffic
- Use timing analysis to decrypt cookies/tokens
- 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:
- Fork and patch: Create a private fork, apply a fix, deploy your patched version
- Switch libraries: Evaluate a maintained alternative
- Mitigate in code: If the vulnerability requires specific conditions, can you eliminate those conditions?
- 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:
- Check the CVE details: Read the full advisory
- Audit your usage: Search your codebase for the vulnerable code pattern
- Check configuration: Is the dangerous setting enabled in your config?
- 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.
Related Reading
GeekWala guides:
- What is EPSS? Exploit Prediction Scoring Explained
- CISA KEV: What "Known Exploited Vulnerabilities" Means for Your Project
- npm audit vs GeekWala: Why CVSS Scores Alone Are Misleading
- Python Dependency Security: Scanning PyPI Vulnerabilities
- Rust Vulnerability Scanning: What cargo audit Misses
- Java Dependency Security: Scanning Maven Vulnerabilities
- Go Vulnerability Scanning: What govulncheck Misses
Official Microsoft resources:


