
C# and the .NET platform power everything from enterprise APIs to desktop tools and cloud-native services, and their popularity makes them a natural target for attackers. The good news is that secure C# development is highly achievable: the runtime is memory-safe, the framework ships with strong security primitives, and most vulnerabilities come from predictable, fixable patterns rather than deep language flaws. This guide walks through the risks that matter most for .NET security and the concrete practices that address them, so you can ship features without shipping soft spots.
The mindset that underpins secure coding in C# is simple: treat all external input as untrusted, keep secrets out of code, and let the framework do the heavy lifting instead of hand-rolling your own defenses. With that foundation, the specific vulnerability classes become far easier to reason about.
SQL injection: parameterize everything
SQL injection remains one of the most damaging and most avoidable flaws. It happens when user input is concatenated directly into a query string. In C#, the fix is to never build SQL by hand. With ADO.NET, use parameterized commands: create a SqlCommand and add values through command.Parameters.AddWithValue("@id", userId) rather than interpolating userId into the query text. With Entity Framework Core, LINQ queries are parameterized automatically, and when you need raw SQL, use FromSqlInterpolated so interpolated values are passed as parameters rather than concatenated. Avoid FromSqlRaw with string-built input. The rule of thumb: if you can see user data inside a query string, you have a bug.
Insecure deserialization: retire BinaryFormatter
Deserialization turns bytes back into objects, and if those bytes come from an untrusted source, an attacker can craft a payload that executes code or corrupts state as it is reconstructed. BinaryFormatter is the classic offender. It is now obsolete and disabled by default in modern .NET, and Microsoft's own guidance is unambiguous that it cannot be made safe for untrusted data. Remove it from your codebase entirely.
For data interchange, prefer System.Text.Json, which does not deserialize arbitrary types by default. The danger returns the moment you enable polymorphic or type-embedded handling. In Newtonsoft.Json, setting TypeNameHandling to anything other than None on untrusted input reintroduces the same remote code execution risk. Keep type handling off, deserialize into concrete DTO types you control, and validate the result before use.
XXE in XML parsers
XML External Entity (XXE) attacks abuse XML parsers that resolve external entities, enabling file disclosure or SSRF. Modern .NET is safe by default. XmlReader and XDocument do not resolve external entities unless you opt in. The risk appears when legacy code sets an XmlResolver or uses older DtdProcessing settings. When parsing untrusted XML, explicitly set DtdProcessing = DtdProcessing.Prohibit and leave XmlResolver = null. Do not re-enable entity resolution to accommodate a convenient input format.
SSRF and path traversal
Server-Side Request Forgery (SSRF) occurs when your app makes an outbound HttpClient request to a URL influenced by user input, letting an attacker reach internal services or cloud metadata endpoints. Never pass a raw user-supplied URL to HttpClient. Validate against an allowlist of permitted hosts and schemes, and resolve and check the destination before connecting. Path traversal is the file-system equivalent: input like ../../etc/passwd escapes an intended directory. Combine user input with a base path, call Path.GetFullPath, and confirm the resolved path still sits under the directory you expect before opening the file.
Mass assignment (over-posting) in ASP.NET Core
ASP.NET Core model binding maps request fields onto your objects automatically, which is convenient and occasionally dangerous. If you bind directly to an entity that has an IsAdmin or Balance property, an attacker can set fields you never intended to expose, a pattern known as over-posting or mass assignment. The defense is to bind to purpose-built view models or DTOs that contain only the fields a given endpoint should accept, then map them to your domain entities deliberately. Use [Bind] lists or [FromBody] on narrow types rather than exposing your data model directly.
Weak cryptography
Cryptography fails quietly. Avoid outdated algorithms such as MD5, SHA-1, and DES. For hashing passwords, use a purpose-built, slow algorithm like PBKDF2 via Rfc2898DeriveBytes with a strong iteration count and a unique per-user salt, or another adaptive function. For general hashing use SHA-256 or better, and for symmetric encryption use AES with a securely generated key. Let the framework generate randomness with RandomNumberGenerator, never System.Random, for anything security-sensitive. Store keys and secrets in a managed vault or configuration provider, not in source.
Dependency and supply-chain risk (NuGet)
A modern .NET app is mostly other people's code. A single vulnerable or malicious NuGet package can compromise the whole application, so your dependencies belong squarely inside your threat model. Pin versions, review new packages before adopting them, and run dotnet list package --vulnerable --include-transitive to surface known issues, including those buried in transitive dependencies. Enable lock files for reproducible restores, watch for typosquatted package names, and remove dependencies you no longer use. Learn more in our overview of software composition analysis.
Tooling: make security automatic
Manual diligence does not scale, so bake these checks into your pipeline. Static application security testing (SAST) analyzes your C# source for injection, unsafe deserialization, and crypto misuse before code merges. Software composition analysis (SCA) tracks vulnerable NuGet packages. Secret scanning catches credentials before they reach a repository. Running all three in CI means every commit is checked the same way, every time. For the difference between static and runtime analysis, see SAST vs DAST, and for the broader picture, our guide to secure software development and the OWASP Top 10, whose 2025 edition maps closely to the risks above.
A practical secure C# checklist
- Use parameterized ADO.NET commands and EF Core LINQ or
FromSqlInterpolated; never concatenate SQL. - Remove
BinaryFormatter; keep JSON type handling off for untrusted input and deserialize into concrete DTOs. - Prohibit DTD processing and null the
XmlResolverwhen parsing untrusted XML. - Validate outbound URLs against an allowlist and canonicalize file paths to block SSRF and traversal.
- Bind to view models, not entities, to prevent over-posting.
- Use modern crypto and
RandomNumberGenerator; keep secrets in a vault. - Audit NuGet dependencies continuously and enforce SAST, SCA, and secret scanning in CI.
How Rainforest helps
Rainforest brings application security testing into the workflow your team already uses. It scans your C# and .NET code for the vulnerability patterns above, flags vulnerable NuGet dependencies, and catches exposed secrets, all integrated into your CI/CD pipeline so findings surface at commit time with the context needed to fix them fast. Instead of a security review that happens late and slows everyone down, you get continuous, developer-friendly feedback that keeps risk out of production.
Ready to see it on your own codebase? Book a demo and we will walk through how Rainforest fits your .NET stack.
Frequently asked questions
Is C# secure?
C# and .NET are memory-safe and ship with strong security defaults, so the language itself is a solid foundation. Real-world risk comes from how applications handle untrusted input, manage secrets, parse data, and pull in dependencies. Follow secure coding practices and C# can be very secure.
What are common C#/.NET security vulnerabilities?
The most frequent serious issues are SQL injection from string-built queries, insecure deserialization, XXE in misconfigured XML parsers, SSRF and path traversal from unvalidated input, mass assignment in ASP.NET Core model binding, weak cryptography, and vulnerable NuGet dependencies.
Why is BinaryFormatter dangerous?
BinaryFormatter reconstructs arbitrary object graphs from serialized bytes, so a crafted payload can trigger remote code execution when deserialized. It cannot be made safe for untrusted data, is now obsolete, and is disabled by default in modern .NET. Replace it with System.Text.Json and concrete DTO types.
How do I manage NuGet dependency risk?
Treat dependencies as part of your attack surface. Pin versions, vet new packages, enable lock files, remove unused ones, and run dotnet list package --vulnerable --include-transitive regularly. Automate this with software composition analysis in CI so known vulnerabilities are caught on every build.
How do I scan C# code for vulnerabilities?
Use static application security testing (SAST) to analyze source for insecure patterns, software composition analysis (SCA) for vulnerable NuGet packages, and secret scanning for exposed credentials. Running these in your CI/CD pipeline, as Rainforest does, checks every commit automatically.

Written by
Bruno Baldo
CMO
Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Secure Software Development: Practices for a Secure SDLC
Learn secure software development: how to build a secure SDLC phase by phase, apply secure coding practices, and shift security left in CI/CD.

SAST vs DAST: The Differences and When to Use Each
SAST vs DAST explained: how static and dynamic application security testing differ, when to use each, and why mature AppSec programs run both.
