Blog

How to Develop Safer Applications with C#

A practical guide to secure C# development: prevent SQL injection, insecure deserialization, XXE, SSRF, and NuGet supply-chain risk in .NET.

Bruno Baldo·Aug 10, 2022·Updated Sep 14, 2026·6 min read·Reviewed by Bruno Baldo
How To Develop Safer Applications With C#​

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 XmlResolver when 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.

Bruno Baldo

Written by

Bruno Baldo

CMO

Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Keep reading