C# y la plataforma .NET impulsan desde APIs empresariales hasta herramientas de escritorio y servicios cloud-native, y su popularidad los convierte en un objetivo natural para los atacantes. La buena noticia es que el desarrollo seguro en C# es muy alcanzable: el runtime es seguro en cuanto a memoria, el framework incluye sólidas primitivas de seguridad y la mayoría de las vulnerabilidades provienen de patrones predecibles y corregibles, en lugar de fallas profundas del lenguaje. Esta guía recorre los riesgos más importantes para la seguridad en .NET y las prácticas concretas que los abordan, para que puedas entregar funcionalidades sin entregar puntos débiles.
La mentalidad que sustenta la codificación segura en C# es simple: trata toda entrada externa como no confiable, mantén los secretos fuera del código y deja que el framework haga el trabajo pesado en lugar de crear tus propias defensas desde cero. Con esa base, las clases específicas de vulnerabilidades se vuelven mucho más fáciles de razonar.
SQL injection: parametriza todo
El SQL injection sigue siendo una de las fallas más dañinas y más evitables. Ocurre cuando la entrada del usuario se concatena directamente en una cadena de consulta. En C#, la solución es nunca construir SQL a mano. Con ADO.NET, usa comandos parametrizados: crea un SqlCommand y agrega valores mediante command.Parameters.AddWithValue("@id", userId) en lugar de interpolar userId en el texto de la consulta. Con Entity Framework Core, las consultas LINQ se parametrizan automáticamente y, cuando necesites SQL sin procesar, usa FromSqlInterpolated para que los valores interpolados se pasen como parámetros en lugar de concatenarse. Evita FromSqlRaw con entrada construida a partir de cadenas. La regla general: si puedes ver datos del usuario dentro de una cadena de consulta, tienes un bug.
Deserialización insegura: retira BinaryFormatter
La deserialización convierte los bytes nuevamente en objetos y, si esos bytes provienen de una fuente no confiable, un atacante puede elaborar un payload que ejecute código o corrompa el estado a medida que se reconstruye. BinaryFormatter es el infractor clásico. Ahora es obsoleto y está deshabilitado de forma predeterminada en el .NET moderno, y la propia guía de Microsoft es inequívoca en que no se puede hacer seguro para datos no confiables. Elimínalo por completo de tu base de código.
Para el intercambio de datos, prefiere System.Text.Json, que no deserializa tipos arbitrarios de forma predeterminada. El peligro regresa en el momento en que habilitas el manejo polimórfico o con tipos incrustados. En Newtonsoft.Json, establecer TypeNameHandling en cualquier valor distinto de None sobre entrada no confiable reintroduce el mismo riesgo de ejecución remota de código. Mantén el manejo de tipos desactivado, deserializa en tipos DTO concretos que tú controles y valida el resultado antes de usarlo.
XXE en analizadores de XML
Los ataques de XML External Entity (XXE) abusan de los analizadores de XML que resuelven entidades externas, lo que permite la divulgación de archivos o SSRF. El .NET moderno es seguro de forma predeterminada. XmlReader y XDocument no resuelven entidades externas a menos que lo habilites. El riesgo aparece cuando el código heredado establece un XmlResolver o usa configuraciones más antiguas de DtdProcessing. Al analizar XML no confiable, establece explícitamente DtdProcessing = DtdProcessing.Prohibit y deja XmlResolver = null. No vuelvas a habilitar la resolución de entidades para acomodar un formato de entrada conveniente.
SSRF y path traversal
El Server-Side Request Forgery (SSRF) ocurre cuando tu aplicación realiza una solicitud saliente con HttpClient hacia una URL influenciada por la entrada del usuario, lo que permite que un atacante alcance servicios internos o endpoints de metadatos de la nube. Nunca pases una URL sin procesar proporcionada por el usuario a HttpClient. Valida contra una allowlist de hosts y schemes permitidos, y resuelve y verifica el destino antes de conectar. El path traversal es el equivalente en el sistema de archivos: una entrada como ../../etc/passwd escapa de un directorio previsto. Combina la entrada del usuario con una ruta base, llama a Path.GetFullPath y confirma que la ruta resuelta sigue estando bajo el directorio que esperas antes de abrir el archivo.
Mass assignment (over-posting) en ASP.NET Core
El model binding de ASP.NET Core asigna los campos de la solicitud a tus objetos de forma automática, lo cual es conveniente y, en ocasiones, peligroso. Si haces el binding directamente a una entidad que tiene una propiedad IsAdmin o Balance, un atacante puede establecer campos que nunca pretendiste exponer, un patrón conocido como over-posting o mass assignment. La defensa consiste en hacer el binding a view models o DTOs creados para ese fin, que contengan únicamente los campos que un endpoint determinado debe aceptar, y luego asignarlos deliberadamente a tus entidades de dominio. Usa listas [Bind] o [FromBody] sobre tipos acotados en lugar de exponer tu modelo de datos directamente.
Criptografía débil
La criptografía falla en silencio. Evita algoritmos obsoletos como MD5, SHA-1 y DES. Para generar hashes de contraseñas, usa un algoritmo lento y creado para ese propósito, como PBKDF2 mediante Rfc2898DeriveBytes, con un conteo de iteraciones alto y un salt único por usuario, u otra función adaptativa. Para el hashing general usa SHA-256 o mejor, y para el cifrado simétrico usa AES con una clave generada de forma segura. Deja que el framework genere aleatoriedad con RandomNumberGenerator, nunca con System.Random, para cualquier cosa sensible a la seguridad. Almacena las claves y los secretos en un vault gestionado o proveedor de configuración, no en el código fuente.
Riesgo de dependencias y de cadena de suministro (NuGet)
Una aplicación .NET moderna es, en su mayor parte, código de otras personas. Un solo paquete de NuGet vulnerable o malicioso puede comprometer toda la aplicación, así que tus dependencias pertenecen de lleno a tu modelo de amenazas. Fija versiones, revisa los paquetes nuevos antes de adoptarlos y ejecuta dotnet list package --vulnerable --include-transitive para sacar a la luz problemas conocidos, incluidos los que se ocultan en dependencias transitivas. Habilita archivos de lock para restauraciones reproducibles, mantente atento a nombres de paquetes con typosquatting y elimina las dependencias que ya no uses. Obtén más información en nuestra descripción general sobre el análisis de composición de software.
Herramientas: haz que la seguridad sea automática
La diligencia manual no escala, así que integra estas verificaciones en tu pipeline. El static application security testing (SAST) analiza tu código fuente en C# en busca de inyección, deserialización insegura y uso incorrecto de criptografía antes de que el código se fusione. El software composition analysis (SCA) rastrea los paquetes de NuGet vulnerables. El escaneo de secretos detecta credenciales antes de que lleguen a un repositorio. Ejecutar los tres en el CI significa que cada commit se verifica de la misma manera, todas las veces. Para conocer la diferencia entre el análisis estático y el de tiempo de ejecución, consulta SAST vs DAST y, para el panorama más amplio, nuestra guía sobre el desarrollo seguro de software y el OWASP Top 10, cuya edición de 2025 se relaciona estrechamente con los riesgos anteriores.
Una checklist práctica de C# seguro
- Usa comandos parametrizados de ADO.NET y LINQ de EF Core o
FromSqlInterpolated; nunca concatenes SQL. - Elimina
BinaryFormatter; mantén desactivado el manejo de tipos en JSON para entrada no confiable y deserializa en DTOs concretos. - Prohíbe el procesamiento de DTD y anula el
XmlResolveral analizar XML no confiable. - Valida las URLs salientes contra una allowlist y canonicaliza las rutas de archivo para bloquear SSRF y traversal.
- Haz el binding a view models, no a entidades, para evitar el over-posting.
- Usa criptografía moderna y
RandomNumberGenerator; mantén los secretos en un vault. - Audita las dependencias de NuGet de forma continua e impón SAST, SCA y escaneo de secretos en el CI.
Cómo ayuda Rainforest
Rainforest lleva las pruebas de seguridad de aplicaciones al flujo de trabajo que tu equipo ya utiliza. Analiza tu código C# y .NET en busca de los patrones de vulnerabilidad anteriores, señala las dependencias de NuGet vulnerables y detecta los secretos expuestos, todo integrado en tu pipeline de CI/CD para que los hallazgos aparezcan en el momento del commit, con el contexto necesario para corregirlos rápidamente. En lugar de una revisión de seguridad que ocurre tarde y ralentiza a todos, obtienes retroalimentación continua y amigable para el desarrollador, que mantiene el riesgo fuera de producción.
¿Listo para verlo en tu propia base de código? Agenda una demostración y te mostraremos cómo Rainforest se adapta a tu stack de .NET.
Preguntas frecuentes
¿Es seguro C#?
C# y .NET son seguros en cuanto a memoria e incluyen sólidos valores predeterminados de seguridad, así que el lenguaje en sí es una base sólida. El riesgo en el mundo real proviene de cómo las aplicaciones manejan la entrada no confiable, gestionan los secretos, analizan los datos e incorporan dependencias. Sigue prácticas de codificación segura y C# puede ser muy seguro.
¿Cuáles son las vulnerabilidades de seguridad comunes en C#/.NET?
Los problemas graves más frecuentes son el SQL injection a partir de consultas construidas con cadenas, la deserialización insegura, el XXE en analizadores de XML mal configurados, el SSRF y el path traversal a partir de entrada no validada, el mass assignment en el model binding de ASP.NET Core, la criptografía débil y las dependencias de NuGet vulnerables.
¿Por qué es peligroso BinaryFormatter?
BinaryFormatter reconstruye grafos de objetos arbitrarios a partir de bytes serializados, así que un payload elaborado puede desencadenar la ejecución remota de código durante la deserialización. No se puede hacer seguro para datos no confiables, ahora es obsoleto y está deshabilitado de forma predeterminada en el .NET moderno. Reemplázalo por System.Text.Json y tipos DTO concretos.
¿Cómo gestiono el riesgo de las dependencias de NuGet?
Trata las dependencias como parte de tu superficie de ataque. Fija versiones, evalúa los paquetes nuevos, habilita archivos de lock, elimina los que no uses y ejecuta dotnet list package --vulnerable --include-transitive con regularidad. Automatiza esto con software composition analysis en el CI, para que las vulnerabilidades conocidas se detecten en cada build.
¿Cómo escaneo el código C# en busca de vulnerabilidades?
Usa el static application security testing (SAST) para analizar el código fuente en busca de patrones inseguros, el software composition analysis (SCA) para los paquetes de NuGet vulnerables y el escaneo de secretos para las credenciales expuestas. Ejecutar estas verificaciones en tu pipeline de CI/CD, como lo hace Rainforest, revisa cada commit de forma automática.

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

Desarrollo seguro de software: prácticas para un SDLC seguro
Qué es el desarrollo seguro, cómo aplicar un SDLC seguro fase por fase, prácticas de codificación segura y automatización DevSecOps en CI/CD.

SAST vs DAST: las diferencias y cuándo usar cada uno
SAST vs DAST: en qué se diferencian las pruebas estática y dinámica de seguridad de aplicaciones, cuándo usar cada una y por qué conviene ambas.
