Tablas de base de datos personalizadas la seguridad es lo primero

Esta es la segunda parte de una serie sobre tablas de bases de datos personalizadas en WordPress. En la primera parte cubrimos las razones para, y en contra, el uso de tablas personalizadas. Analizamos algunos de los detalles que deberían considerarse (nombre de columna, tipos de columna) y cómo crear la tabla. Antes de continuar, debemos cubrir cómo interactuar con esta nueva tabla. sin peligro. En un artículo anterior, traté la desinfección general y la validación. En este tutorial, lo veremos con más detalle en el contexto de las bases de datos..

La seguridad al interactuar con una tabla de base de datos es primordial, por lo que la estamos cubriendo al principio de la serie. Si no se realiza correctamente, puede dejar su tabla abierta para la manipulación mediante inyección de SQL. Podría permitir que un pirata informático extraiga información, reemplace el contenido o incluso altere el comportamiento de su sitio, y el daño que podrían hacer no está restringido a su tabla personalizada..

Supongamos que queremos permitir que los administradores eliminen registros de nuestro registro de actividad. Un error común que he visto es el siguiente:

 if (! empty ($ _ GET ['action']) && 'delete-activity-log' == $ _GET ['action'] && isset ($ _ GET ['log_id'])) global $ wpdb; unsafe_delete_log ($ _ GET ['log_id']);  function unsafe_delete_log ($ log_id) global $ wpdb; $ sql = "ELIMINAR DE $ wpdb-> wptuts_activity_log WHERE log_id = $ log_id"; $ deleted = $ wpdb-> query ($ sql); 

Entonces, ¿qué está mal aquí? Mucho: no han comprobado los permisos, por lo que cualquier persona puede eliminar un registro de actividad. Tampoco han revisado los datos, por lo que incluso con las verificaciones de permisos, un usuario administrador puede ser engañado para que elimine un registro. Todo esto fue cubierto en este tutorial. Pero su tercer error agrava los dos primeros: el unsafe_delete_log () La función utiliza el valor pasado en un comando SQL sin escapar de él primero. Esto lo deja abierto a la manipulación..

Supongamos que su uso previsto es

 www.unsafe-site.com?action=delete-activity-log&log_id=7

¿Qué pasa si un atacante visita (o engaña a un administrador para que visite): www.unsafe-site.com?action=delete-activity-log&log_id=1;%20DROP%20TABLE%20wp_posts. los log_id contiene un comando SQL, que posteriormente se inyecta en $ sql y sería ejecutado como:

 ELIMINAR de wp_wptuts_activity_log DONDE log_id = 1; TABLA DE GOTA wp_posts

El resultado: la totalidad. wp_posts tabla se borra. He visto un código como este en los foros, y el resultado es que cualquiera que visite su sitio puede actualizar o eliminar alguna mesa en su base de datos.

Si los dos primeros errores se corrigieron, entonces hace que sea más difícil que funcione este tipo de ataque, pero no imposible, y no protegería contra un "atacante" que tiene permiso para eliminar los registros de actividad. Es increíblemente importante proteger su sitio contra las inyecciones de SQL. También es increíblemente simple: WordPress proporciona el preparar método. En este ejemplo particular:

 function safe_delete_log ($ log_id) global $ wpdb; $ sql = $ wpdb-> prepare ("ELIMINAR de $ wpdb-> wptuts_activity_log DONDE log_id =% d", $ log_id); $ eliminado = $ wpdb-> consulta ($ sql)

El comando SQL ahora se ejecutaría como

 ELIMINAR de wp_wptuts_activity_log DONDE log_id = 1;

Desinfección de consultas de base de datos

La mayor parte de la desinfección se puede realizar únicamente con $ wpdb global - notablemente a través de su preparar método. También proporciona métodos para insertar y actualizar datos en tablas de forma segura. Estos generalmente funcionan reemplazando una entrada desconocida, o asociando una entrada, con un marcador de posición de formato. Este formato le dice a WordPress qué datos es lo que debe esperar:

  • % s denota una cadena
  • %re denota un número entero
  • %F denota un flotador

Comenzamos analizando tres métodos que no solo sanean las consultas, sino que las construyen para usted también..

Insertando Datos

WordPress proporciona el método $ wpdb-> insertar (). Es un envoltorio para insertar datos en la base de datos y maneja la desinfección. Toma tres parámetros:

  • Nombre de la tabla - El nombre de las mesa
  • Datos - matriz de datos para insertar como columna-> pares de valores
  • Los formatos - matriz de formatos para el valor correspondiente en la matriz de datos (por ejemplo,. % s, %re,%F)

Tenga en cuenta que las claves de los datos deben ser columnas: si hay una clave que no coincide con una columna, se puede generar un error.

En los ejemplos que siguen, hemos establecido explícitamente los datos, pero, por supuesto, en general, estos datos provendrían de las aportaciones de los usuarios, por lo que podría ser cualquier cosa. Como se discute en este artículo los datos. debería se han validado primero, para devolver cualquier error al usuario, pero aún debemos limpiar los datos antes de agregarlos a nuestra tabla. Veremos la validación en el siguiente artículo de esta serie..

 $ wpdb global; // $ user_id = 1; $ actividad = 1; $ object_id = 1479; $ activity_date = date_i18n ('Y-m-d H: i: s', false, true); $ insert = $ wpdb-> insert ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'activity' => $ activity, 'object_id' => $ object_id, 'activity_date' => $ activity_date,) , array ('% d', '% s', '% d', '% s',)); if ($ insertado) $ insert_id = $ wpdb-> insert_id;  else // Error al insertar

Actualización de datos

Para actualizar los datos en la base de datos tenemos $ wpdb-> actualizar (). Este método acepta cinco argumentos:

  • Nombre de la tabla - El nombre de las mesa
  • Datos - matriz de datos para actualizar como columna-> pares de valores
  • Dónde - matriz de datos para coincidir como columnas-> pares de valores
  • Formato de datos - Arreglo de formatos para los valores de 'datos' correspondientes.
  • Donde format - Matriz de formatos para los valores correspondientes de 'dónde'

Esto actualiza las filas que coinciden con la matriz de where con los valores de la matriz de datos. De nuevo, como con $ wpdb-> insertar () Las claves de la matriz de datos deben coincidir con una columna. Vuelve falso en caso de error, o el número de filas actualizadas.

En el siguiente ejemplo, actualizamos todos los registros con el ID de registro '14' (que debe tener como máximo un registro, ya que esta es nuestra clave principal). Actualiza el ID de usuario a 2 y la actividad a 'editado'.

 $ wpdb global; $ user_id = 2; $ actividad = 'editado'; $ log_id = 14; $ updated = $ wpdb-> update ($ wpdb-> wptuts_activity_log, array ('user_id' => $ user_id, 'activity' => $ activity,), array ('log_id' => $ log_id,), array (' % d ','% s ​​'), matriz ('% d '),); if ($ updated) // Número de filas actualizadas = $ updated

Borrando

Desde 3.4 WordPress también ha proporcionado la $ wpdb-> delete () Método para borrar fácilmente (y con seguridad) las filas. Este método toma tres parámetros:

  • Nombre de la tabla - El nombre de las mesa
  • Dónde - matriz de datos para coincidir como columnas-> pares de valores
  • Los formatos - matriz de formatos para el tipo de valor correspondiente (por ejemplo,. % s, %re,%F)

Si desea que su código sea compatible con WordPress pre-3.4, deberá utilizar el código $ wpdb-> preparar Método para sanear la declaración SQL apropiada. Un ejemplo de esto fue dado arriba. los $ wpdb-> eliminar el método devuelve el número de filas que se eliminan, o falso de lo contrario, para que pueda determinar si la eliminación se realizó correctamente.

 $ wpdb global; $ deleted = $ wpdb-> delete ($ wpdb-> wptuts_activity_log, array ('log_id' => 14,), array ('% d'),); if ($ eliminado) // Número de filas eliminadas = $ eliminado

esc_sql

A la luz de los métodos anteriores, y los más generales $ wpdb-> preparar () Método discutido a continuación, esta función es un poco redundante. Se proporciona como un envoltorio útil para el $ wpdb-> escape () Método, en sí mismo un glorificado. barras de adición. Ya que suele ser más apropiado y recomendable utilizar los tres métodos anteriores, o $ wpdb-> preparar (), Probablemente encontrará que rara vez necesita usar esc_sql ().

Como un simple ejemplo:

 $ activity = 'comentó'; $ sql = "ELIMINAR DE $ wpdb-> wptuts_activity_log WHERE.esc_sql ($ activity)." ";";

Consultas generales

Para los comandos SQL generales donde (es decir, aquellos que no insertan, eliminan o actualizan filas) tenemos que usar el método $ wpdb-> preparar (). Acepta un número variable de argumentos. La primera es la consulta SQL que deseamos ejecutar con todos los datos 'desconocidos' reemplazados por su marcador de posición de formato apropiado. Estos valores se pasan como argumentos adicionales, en el orden en que aparecen.

Por ejemplo, en lugar de:

 $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE user_id = $ user_id AND object_id = $ object_id AND activity = $ activity ORDER BY activity_date $ order"; $ logs = $ wpdb-> get_results ($ sql);

tenemos

 $ sql = $ wpdb-> prepare ("SELECT * FROM $ wpdb-> wptuts_activity_log WHERE user_id =% d AND object_id =% d AND activity =% s ORDER BY activity_date% s", $ user_id, $ object_id, $ activity , $ orden); $ logs = $ wpdb-> get_results ($ sql);

los preparar método hace dos cosas.

  1. Aplica mysql_real_escape_string () (o tiras de adición ()) a los valores que se están insertando. En particular, esto evitará que los valores que contienen comillas salten de la consulta..
  2. Aplica vsprintf () al agregar los valores a la consulta para asegurarse de que tengan el formato adecuado (por lo que los enteros son enteros, los flotantes son flotantes, etc.). Esta es la razón por la que nuestro ejemplo al principio del artículo eliminó todo menos el '1'..

Consultas más complicadas

Usted debe encontrar que $ wpdb-> preparar, junto con los métodos de inserción, actualización y eliminación son todo lo que realmente necesita. A veces, aunque existen circunstancias en las que se desea un enfoque más "manual", a veces solo desde el punto de vista de la legibilidad. Por ejemplo, supongamos que tenemos una matriz desconocida de actividades para las que queremos todos los registros. * Podríamos * añadir dinámicamente el % s marcadores de posición a la consulta SQL, pero un enfoque más directo parece más fácil:

 // Una matriz desconocida que debe contener cadenas que se consultan para $ activities = array (…); // Sanitizar el contenido de la matriz $ activities = array_map ('esc_sql', $ activities); $ activities = array_map ('sanitize_title_for_query', $ activities); // Cree una cadena desde el arreglo saneado que forma la parte interna de la declaración IN (…) $ in_sql = "'". implode ("','", $ actividades). "'"; // Agregar esto a la consulta $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log WHERE activity IN ($ in_sql);" // Realice la consulta $ logs = $ wpdb-> get_results ($ sql);

La idea es aplicar. esc_sql y sanitize_title_for_query a cada elemento de la matriz. El primero agrega barras para escapar de los términos, similar a lo que $ wpdb-> preparar () hace. El segundo simplemente aplica sanitize_title_with_dashes () - aunque el comportamiento puede ser modificado completamente a través de filtros. La instrucción SQL real se forma implosionando la matriz ahora saneada en una cadena separada por comas, que se agrega a la EN (…) parte de la consulta.

Si se espera que la matriz contenga enteros, entonces es suficiente usar intval () o absint () Para sanear cada elemento de la matriz..

Lista blanca

En otros casos, la lista blanca puede ser apropiada. Por ejemplo, la entrada desconocida puede ser una matriz de columnas que se devolverán en la consulta. Ya que sabemos cuáles son las columnas de la base de datos, simplemente podemos incluirlas en la lista blanca, eliminando los campos que no reconocemos. Sin embargo, para hacer que nuestro código sea amigable, debemos ser sensibles a mayúsculas y minúsculas. Para hacer esto, convertiremos todo lo que recibamos en minúsculas, ya que en la primera parte usamos específicamente los nombres de las columnas en minúsculas.

 // Una matriz desconocida que debe contener columnas que se incluirán en la consulta $ fields = array (…); // Una lista blanca de campos permitidos $ allowed_fields = array (…); // Convertir los campos en minúsculas (ya que nuestros nombres de columna están en minúscula, ver parte 1) $ fields = array_map ('strtolower', $ fields); // Desinfectar mediante la lista blanca $ fields = array_intersect ($ fields, $ allowed_fields); // Devuelve solo los campos seleccionados. Los $ campos vacíos se interpretan como todos si (vacíos ($ campos)) $ sql = "SELECT * FROM $ wpdb-> wptuts_activity_log";  else $ sql = "SELECT" .implode (',', $ fields). "FROM $ wpdb-> wptuts_activity_log";  // Realice la consulta $ logs = $ wpdb-> get_results ($ sql);

La lista blanca también es conveniente cuando se configura ORDEN POR parte de la consulta (si esto se establece mediante la entrada del usuario): los datos se pueden ordenar como DESC o ASC solamente.

 // Entrada de usuario desconocido (se espera que sea asc o desc) $ order = $ _GET ['order']; // Permitir que la entrada sea cualquiera, o mixta, case $ order = strtoupper ($ order); // Valor del pedido desinfectado $ order = ('ASC' == $ order? 'ASC': 'DEC');

Consultas como

Las instrucciones SQL LIKE admiten el uso de comodines, como % (cero o más caracteres) y _ (exactamente un carácter) al hacer coincidir los valores con la consulta. Por ejemplo el valor foobar coincidiría con cualquiera de las consultas:

 SELECCIONAR * DESDE $ wpdb-> wptuts_activity_log DONDE ACTIVARSE COMO 'foo%' SELECCIONAR * DESDE $ wpdb-> wptuts_activity_log DONDE ACTIVARSE '% bar' SELECCIONAR * DESDE $ wpdb-> wptuts_activity_log DONDE actividad LIKE '% oba%' SELECT * FROM $ wpdb-> wptuts_activity_log DONDE ACTIVA la actividad "fo_bar%"

Sin embargo, estos caracteres especiales pueden estar presentes en el término que se está buscando, y para evitar que se interpreten como comodines, debemos escapar de ellos. Para esto WordPress proporciona la like_escape () función. Tenga en cuenta que esto no impide la inyección de SQL, sino que solo escapa a la % y _ personajes: todavía necesitas usar esc_sql () o $ wpdb-> preparar ().

 // Recoger término $ término = $ _GET ['actividad']; // Escape cualquier comodín $ term = like_escape ($ term); $ sql = $ wpdb-> prepare ("SELECT * FROM $ wpdb-> wptuts_activity_log DONDE LA actividad ES LIKE% s", '%'. $ term. '%'); $ logs = $ wpdb-> get_results ($ sql);

Funciones de consulta de envoltorio

En los ejemplos que hemos visto hemos usado otros dos métodos de $ wpdb:

  • $ wpdb-> consulta ($ sql) - Esto realiza cualquier consulta que se le dé y devuelve el número de filas afectadas.
  • $ wpdb-> get_results ($ sql, $ ouput) - Esto realiza la consulta que se le da y devuelve el conjunto de resultados coincidentes (es decir, las filas coincidentes). $ salida Establece el formato de los resultados devueltos:
    • ARRAY_A - matriz numérica de filas, donde cada fila es una matriz asociativa, codificada por las columnas.
    • ARRAY_N - matriz numérica de filas, donde cada fila es una matriz numérica.
    • OBJETO - matriz numérica de filas, donde cada fila es un objeto de fila. Defecto.
    • OBJECT_K - Matriz asociativa de filas (codificada por el valor de la primera columna), donde cada fila es una matriz asociativa.

Hay otros que no hemos mencionado también:

  • $ wpdb-> get_row ($ sql, $ ouput, $ row) - Esto realiza la consulta y devuelve una fila.. $ fila Establece qué fila se va a devolver, de forma predeterminada, esto es 0, la primera fila coincidente. $ salida establece el formato de la fila:
    • ARRAY_A - Fila es una columna => valor par.
    • ARRAY_N - La fila es una matriz numérica de valores.
    • OBJETO - La fila se devuelve como un objeto. Defecto.
  • $ wpdb-> get_col ($ sql, $ column) - Esto realiza la consulta y devuelve una matriz numérica de valores de la columna especificada. $ columna Especifica qué columna devolver como entero. Por defecto esto es 0, la primera columna..
  • $ wpdb-> get_var ($ sql, $ column, $ row) - Esto realiza la consulta y devuelve un valor particular. $ fila y $ columna son como los anteriores, y especifique qué valor devolver. Por ejemplo,
     $ activities_by_user_1 = $ wpdb-> get_var ("SELECT COUNT (*) FROM $ wpdb-> wptuts_activity_log WHERE user_id = 1");

Es importante tener en cuenta que estos métodos son solo envoltorios para realizar una consulta SQL y formatear el resultado. No desinfectan la consulta. - por lo que no debe usarlos solo cuando la consulta contenga algunos datos 'desconocidos'.


Resumen

Hemos cubierto mucho en este tutorial, y la desinfección de los datos es un tema importante que hay que entender. En el siguiente artículo lo aplicaremos a nuestro complemento. Buscaremos desarrollar un conjunto de funciones de envoltorio (similares a funciones como wp_insert_post (), wp_delete_post () etc.) que agregará una capa de abstracción entre nuestro complemento y la base de datos.