consultas parametrizadas frente a inyección SQL

Soy nuevo en Asp.net y estoy empezando a trabajar con clases. Hace poco creé una clase que manejará la mayoría de mis consultas SQL para que no tenga que crear nuevas conexiones repetidamente sobre todos mis archivos.

Uno de los métodos que he creado toma una consulta SQL como parámetro y devuelve el resultado. Sé que debería usar consultas parametrizadas para evitar inyecciones de SQL. Mi pregunta es, ¿cómo puedo hacer esto cuando paso la consulta como un parámetro de cadena?

Por ejemplo, aquí hay un método que llamaré:

public static DataTable SqlDataTable(string sql) { using (SqlConnection conn = new SqlConnection(DatabaseConnectionString)) { SqlCommand cmd = new SqlCommand(sql, conn); cmd.Connection.Open(); DataTable TempTable = new DataTable(); TempTable.Load(cmd.ExecuteReader()); return TempTable; } } 

Así que desde otro archivo me gustaría usar este método así:

 DataTable dt = new DataTable(); dt = SqlComm.SqlDataTable("SELECT * FROM Users WHERE UserName='" + login.Text + "' and Password='" + password.Text + "'"); if (dt.Rows.Count > 0) { // do something if the query returns rows } 

Esto funciona, pero aún sería vulnerable a las inyecciones, ¿verdad? ¿Hay alguna forma de que pueda pasar las variables a la cadena como parámetros? Sé que puedo hacer esto si creo un nuevo objeto SQLCommand para la consulta y uso Parameters.AddWithValue, pero quería que todos mis comandos SQL estuvieran en la clase separada.

Esto funciona, pero aún sería vulnerable a las inyecciones, ¿verdad?

Sí, tu código es terriblemente vulnerable a las inyecciones de SQL.

Sé que debería usar consultas parametrizadas para evitar inyecciones de SQL.

Oh, absolutamente, sí.

Mi pregunta es, ¿cómo puedo hacer esto cuando paso la consulta como un parámetro de cadena?

Simplemente no debería pasar la consulta como un parámetro de cadena. En su lugar, debe pasar la consulta como parámetro de cadena que contiene marcadores de posición y los valores para esos marcadores de posición:

 public static DataTable SqlDataTable(string sql, IDictionary values) { using (SqlConnection conn = new SqlConnection(DatabaseConnectionString)) using (SqlCommand cmd = conn.CreateCommand()) { conn.Open(); cmd.CommandText = sql; foreach (KeyValuePair item in values) { cmd.Parameters.AddWithValue("@" + item.Key, item.Value); } DataTable table = new DataTable(); using (var reader = cmd.ExecuteReader()) { table.Load(reader); return table; } } } 

y luego usa tu función de esta manera:

 DataTable dt = SqlComm.SqlDataTable( "SELECT * FROM Users WHERE UserName = @UserName AND Password = @Password", new Dictionary { { "UserName", login.Text }, { "Password", password.Text }, } ); if (dt.Rows.Count > 0) { // do something if the query returns rows } 

Lo que estás tratando de hacer tiene un sentido lógico perfecto y puedo entender por qué llegarías a esta implementación. Sin embargo, lo que está intentando hacer es muy peligroso y, como es nuevo en ASP.NET, es posible que no sepa que hay muchas otras opciones disponibles que hacen que la administración de sus datos sea mucho más fácil y mucho más segura.

@iamkrillin insinuó una de esas tecnologías: mapeo relacional de objetos (ORM). El framework .NET en realidad tiene soporte de primera clase para un ORM llamado Entity Framework . Creo que la razón por la que sugirió que estudies un ORM es porque tu diseño es en realidad muy similar a la forma en que funciona el ORM. Son clases abstractas que representan tablas en su base de datos que se pueden consultar fácilmente con LINQ. Las consultas de LINQ se parametrizan automáticamente y lo liberan del estrés de administrar la seguridad de sus consultas. Generan SQL sobre la marcha (de la misma manera que cuando transfiere cadenas a su clase de acceso a datos) y son mucho más flexibles en cuanto a la forma en que pueden devolver datos (matrices, listas, nombre).

Una desventaja de los ORM, sin embargo, es que tienen curvas de aprendizaje bastante empinadas. Una opción más simple (aunque un poco más antigua que EF) sería utilizar conjuntos de datos tipados. Los conjuntos de datos tipados son mucho más fáciles de crear que los ORM de pie y, en general, son mucho más fáciles de implementar. Aunque no son tan flexibles como los de ORM, logran exactamente lo que estás tratando de hacer de una manera simple, segura y ya resuelta. Afortunadamente, cuando apareció por primera vez ASP.NET, los videos de capacitación se centraron principalmente en los conjuntos de datos tipados y, como tal, hay una variedad de videos / tutoriales disponibles de alta calidad para que pueda comenzar a utilizarlos rápidamente.

Estás en el camino correcto, y yo también he hecho lo que realmente buscas. Sin embargo, en lugar de simplemente pasar una cadena a su función, paso un objeto de Comando SQL … Así, de esta manera, puede construir correctamente todos sus comandos y parámetros y luego decir … aquí, vaya a ejecutar esto, está listo para ir. Algo como

 public static DataTable SqlDataTable(SqlCommand cmd) { using (SqlConnection conn = new SqlConnection(DatabaseConnectionString)) { cmd.Connection = conn; // store your connection to the command object.. cmd.Connection.Open(); DataTable TempTable = new DataTable(); TempTable.Load(cmd.ExecuteReader()); return TempTable; } } public DataTable GetMyCustomers(string likeName) { SqlCommand cmd = new SqlCommand(); cmd.CommandText = "select * from SomeTable where LastName like "@someParm%"; cmd.Parameters.Add( "whateverParm", likeName ); // don't have SQL with me now, guessing syntax // so now your SQL Command is all built with parameters and ready to go. return SqlDataTable( cmd ); } 

Mi sugerencia: usa un orm. Hay mucho para elegir de hoy en día