| Cuando miro el código que escribe la mayoría de los programadores no me extraña que muchos sistemas fallen, lo que me sorprende es que haya tantos sistemas que no fallan de forma estrepitosa diariamente. |
Una de las pifias que veo con más frecuencia en los desarrollos de software que usan bases de datos es la forma como se construyen las consultas que dependen de valores ingresados por el usuario. Por ejemplo, si se busca un registro en la tabla de Personas por su nombre, lo más común es ver la consulta construida así:
string sql = "SELECT * FROM PERSONA WHERE NOMBRE LIKE '"
textBoxNombre.Text + "'%";
SqlDataAdapter da = new SqlDataAdapter(sql, connectionString);
DataSet ds = new DataSet();
da.Fill(ds);
Esta forma de construir las consultas tiene un montón de problemas. Para comenzar: ¿Qué pasaría si el usuario ingresa en el textbox el texto: Oso's?
Analizando el código es fácil darse cuenta que con ese texto el sistema se estrella porque la consulta se convierte en: SELECT * FROM PERSONA WHERE NOMBRE LIKE 'Oso's%'
Lo que ocurre es que el conector de datos interpretará el segundo apóstrofo como el fin de la cadena y luego se encuentra la secuencia s%' por lo que se produce un error de sintaxis. Si esta excepción no es capturada, el usuario se encontrará frente la HPES (Horrible Página de Error en el Servidor).
Eso, por si solo, ya sería bastante malo, pero un "usuario" malintencionado podría introducir algo como esto:
ZZ';DELETE PERSONA;SELECT '
Al realizar la concatenación, la consulta se convierte en:
SELECT * FROM PERSONA WHERE NOMBRE LIKE 'ZZ%';
DELETE PERSONA;SELECT ''
Esa sentencia es un lote SQL perfectamente válido, así que un usuario malicioso podría borrarnos la base de datos sin mucho trabajo.
La forma de solucionar este problema es muy sencilla: usar un objeto SqlCommand y llenar la colección Parameters con tantos objetos SqlParameter como haga falta.
El primer paso es construir la consulta sustituyendo los criterios de búsqueda por placeholders. Un placeholder es un signo o identificador que se coloca en el texto de la consulta para ser sustituido por un valor. Cada conector de base de datos (como SqlClient, OracleClient, OleDb, ODBC, etc) definen su propio signo de placeholder. En el caso de OleDb el placeholder es el signo de interrogación. En SqlClient se puede usar el signo arroba seguido del nombre que se desea dar al placeholder o parámetro:
string sql = "SELECT * FROM PERSONA WHERE NOMBRE LIKE @Nombre";
El siguiente paso es construir un objeto SqlCommand con la consulta:
SqlConnection cn = new SqlConnection(connectionString); cn.Open(); SqlCommand cm = new SqlCommand(sql;cn);
Nos hace falta llenar los placeholders que hemos colocado en la consulta. Hay muchas maneras de hacerlo. El objeto SqlCommand tiene una propiedad Parameters que implementa las interfaces IList, ICollection e IEnumerable, por lo que podemos tratarlo como una lista y agregarle elementos uno a uno, pero la forma que a mi más me gusta es usar el método AddWithValue que permite crear el parámetro y darle valor de una sola vez:
cm.AddWithValue("@Nombre", texBoxPersona.Text);
De esta forma estamos transfiriéndole al conector de base de datos la responsabilidad de arreglar el valor del criterio de búsqueda y formatearlo según su tipo.
El uso de Parámetros no nos exime de realizar validaciones (por ejemplo, garantizar que un campo que se supone es numérico tenga un número válido). Tampoco garantiza que el diseño sea modular, que haya separación de funciones y que los módulos tengan una buena cohesión y un bajo acoplamiento, sólo es un primer paso en la dirección de hacer software robusto.
En los ejemplos he usado el cliente de SQL Server, pero esta técnica también es válida para OleDB, Oracle Client, ODBC y cualquier conector de base de datos compatible con Ado.Net.
No hay comentarios:
Publicar un comentario