Skip to main content

Cómo prevenir la inyección SQL en C# con Entity Framework

Escrito por
feature security

30 de julio de 2024

0 minutos de lectura

La importancia de prevenir la inyección SQL

La inyección SQL (SQLi) es una de las vulnerabilidades de seguridad más graves en las aplicaciones web. Se produce cuando un atacante puede manipular las consultas SQL que ejecuta una aplicación al inyectar código SQL malicioso en los campos de entrada del usuario. La SQLi puede provocar acceso no autorizado a datos confidenciales, corrupción de datos o incluso el control total del servidor de base de datos. Prevenir la SQLi es fundamental para mantener la integridad, confidencialidad y disponibilidad de los datos, así como la seguridad general de la aplicación.

Uno de los errores más comunes que cometen los desarrolladores es usar la concatenación de cadenas para construir consultas SQL. Con este enfoque, los parámetros que ingresa el usuario forman parte de la consulta SQL y pueden influir en la ruta de ejecución de una consulta. Este es un ejemplo de código vulnerable en C#:

public List SearchProduct(string search)
{
    var query = $"SELECT * FROM Product WHERE Name LIKE '%{search}%' OR Description LIKE '%{search}%'";
    return context.Users.FromSqlRaw(query).ToList();
}

Al usar caracteres como ; y --, podemos influir en la ruta de ejecución de la consulta al terminarla antes de lo previsto (;) y definir el resto de la cadena como comentarios (--).

Si el nombre se establece como Brian'; DROP TABLE Users;-- , la consulta queda así:

SELECT * FROM Product WHERE Name LIKE = '%Brian'; DROP TABLE Users; --' OR Description LIKE '%{search}%'";

La consulta inicial se detiene después de ;. Luego se ejecuta una nueva consulta que elimina la tabla Users, y el resto de la consulta original se considera un comentario. De forma similar, es posible modificar o insertar datos nuevos en la base de datos si las consultas SQL se implementan de esta manera.

Escape frente a consultas preparadas

La clave está en separar los parámetros de entrada de la consulta en sí para que los datos ingresados por el usuario ya no puedan influir en la ruta de ejecución de la consulta. En muchos lenguajes, hay funciones que permiten usar consultas parametrizadas y, por lo tanto, separar los datos ingresados por el usuario de la consulta en sí.

La pregunta es cómo se implementa esto internamente, ya que hay aproximadamente dos formas de hacerlo: mediante el escape o el uso de consultas preparadas.

Escape

El escape consiste en sanitizar los datos ingresados por el usuario agregando caracteres de escape antes de los caracteres potencialmente peligrosos (como las comillas). Sin embargo, el escape es propenso a errores y no es infalible. El escape se realiza en el lado del cliente (la aplicación), y la consulta escapada se envía a la base de datos como una sola instrucción. Requiere manejar cuidadosamente varios casos límite y, por lo general, no se recomienda como defensa principal contra la SQLi.

Consultas preparadas

Por otro lado, las consultas preparadas con parámetros funcionan de manera un poco diferente. La estructura SQL se define por separado y se envía al servidor de base de datos en una primera etapa. La base de datos puede crear la ruta de ejecución antes de que entren en juego los parámetros. En una segunda etapa, los parámetros se envían a la base de datos. Como el plan de ejecución ya se creó, los parámetros siempre se tratan como datos y no como código ejecutable, por lo que no pueden influir en la ejecución.

Cómo usar Entity Framework para prevenir la inyección SQL

Entity Framework (EF) ofrece varios métodos para interactuar con la base de datos de forma segura, sin exponer la aplicación a vulnerabilidades de SQLi. Estos métodos incluyen las consultas LINQ, FromSqlInterpolated y FromSqlRaw con parámetros explícitos.

Uso de LINQ

Language Integrated Query (LINQ) ofrece una forma segura de interactuar con las bases de datos. Es la forma recomendada de consultar la base de datos en EF para la mayoría de las consultas. LINQ proporciona un alto nivel de abstracción, lo que te permite escribir consultas directamente en C# sin conocer la sintaxis de SQL. Está integrado en C#, por lo que puedes aprovechar la verificación de sintaxis en tiempo de compilación.
Entity Framework convierte automáticamente las consultas LINQ en SQL mediante consultas preparadas y parametrización, lo que previene la inyección SQL.

public SearchProduct List(string search)
{
   return dbContext.Products
       .Where(p => p.Name.Contains(search) ||
                   p.Description.Contains(search))
       .ToList();
}

Aunque LINQ es muy práctico y seguro, en ocasiones puede generar SQL menos eficiente que las consultas SQL escritas manualmente, especialmente en el caso de las consultas complejas. La capa de traducción puede introducir una sobrecarga de rendimiento. Además, LINQ está limitado por la capacidad del proveedor de EF para traducir código C# a SQL. Algunas funcionalidades complejas de SQL, como ciertos tipos de combinaciones, subconsultas o funciones de ventana, pueden ser difíciles o imposibles de expresar en LINQ.

Uso de FromSqlInterpolated

FromSqlInterpolated es otra forma segura de ejecutar consultas SQL sin procesar. Usa interpolación de cadenas y gestiona automáticamente la parametrización mediante consultas preparadas.

public List<User> GetUserByName(string name)
{
 	return context.Users
.FromSqlInterpolated($"SELECT * FROM Users WHERE Name = {name}")
.ToList();
}

Con FromSqlInterpolated, EF garantiza que la variable name se parametrice de forma segura y, por lo tanto, protege contra la inyección SQL. Entity Framework Core analiza la cadena interpolada e identifica las expresiones interpoladas. Luego reemplaza estas expresiones por marcadores de posición de parámetros dentro del comando SQL. Se crean los parámetros SQL correspondientes y se asignan a estos parámetros los valores de las expresiones interpoladas.

Internamente, la función FromSqlInterpolated usa una consulta preparada con consultas parametrizadas. Por lo tanto, es segura contra la inyección SQL. Cuando necesitas hacer consultas más complejas que resultan más fáciles de expresar en SQL que con la sintaxis LINQ, FromSqlInterpolated es una excelente alternativa.

Uso de FromSqlRaw con parámetros explícitos

En el ejemplo inicial, primero creamos la consulta y la pasamos completa a la instrucción FromSQLRaw. Sin embargo, FromSqlRaw se puede usar de forma segura. Al usar parámetros explícitos con FromSqlRaw, EF garantiza que los datos ingresados por el usuario se parametrizan correctamente, lo que previene la inyección SQL. Este método consiste en definir consultas SQL con marcadores de posición para los parámetros y proporcionar los valores por separado, para garantizar que los datos ingresados por el usuario se traten como datos y no como código ejecutable.

public List<Product> SearchProduct(string search) 
{ 
return context.Products
.FromSqlRaw( 
"SELECT * FROM Product WHERE Name LIKE {0} OR Description LIKE {0}", "%" + search + "%" ).ToList(); 
}

En esta versión, se usa el marcador de posición {0} en la cadena de consulta SQL, y el valor del parámetro se pasa directamente como argumento a FromSqlRaw. Esto garantiza que los datos ingresados por el usuario se parametrizan de forma segura, lo que protege contra la inyección SQL.

Detectar, corregir y prevenir

La diferencia entre usar FromSqlRaw de forma segura o insegura es muy pequeña y puede pasar desapercibida fácilmente. Por eso, todos los desarrolladores deberían usar herramientas como Snyk Code para recibir advertencias sobre el uso de construcciones de código inseguras en C#.

La pantalla de Snyk Code señala una entrada HTTP sin sanitizar que llega a FromSqlRaw e identifica una vulnerabilidad de inyección SQL en TasksController.cs.

En el ejemplo anterior, se detecta un problema similar de inyección SQL mediante la integración con git. Detectar este tipo de problemas durante el ciclo de desarrollo es fundamental si te importa la seguridad, pero no quieres sacrificar la velocidad.

Sin embargo, también es fundamental contar con una buena capacitación y conocer los frameworks que usas. Cuando trabajes con EF, deberías usar LINQ de forma predeterminada. LINQ debería ser suficiente, a menos que necesites el máximo rendimiento o crear consultas muy complejas. Cuando necesites hacer consultas SQL muy específicas y complejas, usa el enfoque FromSqlInterpolated que describimos anteriormente. Mi consejo es evitar FromSqlRaw. Aunque se puede usar de forma segura, la diferencia entre el enfoque correcto y el incorrecto es sutil y puede pasar desapercibida fácilmente.

En resumen, aquí tienes una lista fácil de consultar. Al usar Entity Framework:

  • Usa la sintaxis LINQ para interactuar con la base de datos y evitar SQL.

  • Usa FromSQLInterpolated solo en situaciones complejas.

  • Usa una herramienta de análisis de código como Snyk Code para ayudarte a detectar errores.

Ahora que sabes un poco más, ya puedes usar Entity Framework en C# para proteger tu código contra las vulnerabilidades de inyección SQL. Para facilitar el proceso, crea una cuenta gratuita de Snyk o agenda una demostración hoy mismo y descubre cómo Snyk protege tu código mientras lo escribes.

Comienza con Capture the Flag

Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.

Leer más

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

illustration hero ai
Blog

El huracán de la IA ha llegado

La IA está acelerando por igual la creación de software y los ciberataques. Los líderes deben proteger los agentes y el código desde el inicio, aplicar controles en tiempo de ejecución y validar las defensas de forma independiente.

feature insights context
Blog

¿La prevención es, en esencia, un problema ya resuelto?

La prevención en el código generado por agentes está resuelta desde el punto de vista arquitectónico, pero elegir controles que protejan la seguridad sin ralentizar el desarrollo sigue siendo el desafío.