Wordpress es una de las plataformas CMS más extendidas en Internet, rozando el 50% del todos los CMS del mercado. Los plugins desarrollados para esta plataforma se cuentan por miles. La facilidad para publicar tu plugin (tan solo es necesario registrarse gratuitamente) permite que haya una legión de empresas y autónomos desarrollando para este CMS. Esta gran variedad de desarrolladores tiene su parte buena y su parte mala. La buena es la gran oferta de plugins que permiten a los usuarios personalizar su web rápidamente y tener infinidad de funcionalidades a su disposición, la parte mala es que algunos de estos desarrolladores no siguen unas buenas prácticas de seguridad a la hora de programar estos plugins, de ahí que nos encontremos también con esa ingente cantidad de vulnerabilidades para esta plataforma.
En este post, el primero de dos partes, voy a publicar 5 vulnerabilidades encontradas en unos de estos plugin. En este hablaré de dos inyecciones SQL ciegas sin autenticar y en el siguiente hablaré de otras que prefiero no detallar hasta que no estén solucionadas (IMPORTANTE: La empresa ya ha sido notificada y estas dos SQLi que publico ya están solucionadas en la versión 3.1.3).
Plugin afectado
El plugin afectado es Ultimate Product Cataloge en su versión v2.1.2 (y probablemente en anteriores), un plugin con alrededor de 61.000 descargas y más de 4.000 instalaciones activas.Explorando entre el código de este plugin con un par de greps y expresiones regulares se puede encontrar en qué ficheros se están ejecutando algunas queries SQL que no están siendo filtradas adecuadamente mediante el uso de $wpdb->prepare o esq_sql.
En el plugin analizado se llegaron a identificar dos funcionalidades en los que un usuario sin autenticar interactua con el este plugin, que construye la consulta SQL de forma insegura.
1º SQLi: Inyección en la consulta de un producto único [1] [4]
En el momento que un usuario selecciona del catálogo de productos uno de ellos, dependiendo de la configuración del plugin, este le redirigirá a la página que detalla la información del producto. Se nos redirigirá a una página con una URL similar a la siguiente:http://<wordpress site>/?&SingleProduct=2
Esta URL hará una llamada a la función "SingleProductPage()" localizada en el fichero "Functions/Shortcodes.php", donde la query que se construye es como la siguiente:
[...]
else {$Product = $wpdb->get_row("SELECT * FROM $items_table_name WHERE Item_ID='".$_GET['SingleProduct']."'");}
[...]
Supongo que ya vemos por donde van los tiros con esta query y si tenemos claro los conceptos de una inyección SQL ciega, podéis saltaros el siguiente apartado. En caso contrario, os dejo un ejemplo de cómo se puede probar la vulnerabilidad.
POC
Si el parámetro es un entero como un 2, la query será la siguiente:
SELECT * FROM $items_table_name WHERE Item_ID='2'
y devolverá los detalles del producto con este identificador que se encuentre almacenado en la base de datos, pero también devolverá el mismo resultado si en vez de insertar un 2, insertamos 2' and 'a'='a y posteriormente 2' and 'a'='b produciendo la siguientes queries:
SELECT * FROM $items_table_name WHERE Item_ID='2' and 'a'='a'
SELECT * FROM $items_table_name WHERE Item_ID='2' and 'a'='b'
Comparando respuesta del servidor del primer caso con la del segundo, podremos ver que se pueden inyectar condiciones lógicas en el parámetro SingleProduct y que a través de la respuesta del servidor se podrán identificar si se han evaluado como verdaderas o falsas.
Llevando esta prueba de concepto a una utilidad más amplia para un atacante, podrían inyectarse pruebas como preguntar al servidor si la primera letra del usuario que accede a la base de datos es la "c" inyectando lo siguiente 2' and substr(user(),1,1)='c, ejecutando la query final siguiente:
SELECT * FROM $items_table_name WHERE Item_ID='2' and substr(user(),1,1)='c'
Si la respuesta del servidor es igual a aquella que obtuvimos cuando el parámetro era tan solo un 2 (es decir, se visualiza la misma página), entonces sabemos que se ha evaluado a True esta query inyectada y que por tanto, el nombre del usuario de la base de datos empieza efectivamente por una 'c'.
Para no eternizarnos con las pruebas manuales, SQLMap puede ayudarnos con la extracción de datos.
Es importante comentaros que esta vulnerabilidad será explotable si el servidor tiene desactivadas las magic_quotes_gpc en la configuración de php.ini, en caso contrario, automáticamente se escaparán las comillas introducidas en los parámetros con una contrabarra '\' y la explotación no será posible tan fácilmente.
2º SQLi: Inyección en el contador de visitas de un producto [2] [3]
En este caso, la vulnerabilidad, similar a la anterior, pero con la dificultad añadida de que la respuesta del servidor siempre es vacía y por tanto no se podrá distinguir entre una inyección evaluada a True a través de lo que se visualice. Por tanto, se deberán utilizar inyecciones basadas en tiempo, como por ejemplo SLEEP o BENCHMARK, pudiendo introducir condiciones como "si la primera letra del usuario es una "c" duerme 5 segundos". Así pues, se podrá utilizar la medición del tiempo de respuseta del servidor como método de detectar si una query se ha evaluado a True o False (de nuevo, el uso de SQLMap o similares es necesario si no quieres eternizarte con las pruebas manuales)
La inyección se encuentra en la función que se ejecuta automáticamente cada vez que se visita un producto por parte de un visitante a la web. Esta función parece ser la encargada de contar cuantas veces los usuarios han visitado un producto, almacenando en la base de datos un contador de visualizaciones.
La query con el parámetro vulnerables se encuentran en Functions/Process_Ajax.php y el parámetro inyectable es Item_ID dentro de la función Record_Item_View():
[...]
$Item_ID = $_POST['Item_ID'];$Item = $wpdb->get_row("SELECT Item_Views FROM $items_table_name WHERE Item_ID=" . $Item_ID);
[...]
add_action('wp_ajax_record_view', 'Record_Item_View');add_action('wp_ajax_nopriv_record_view', 'Record_Item_View' );
[...]
[...]
add_action('wp_ajax_record_view', 'Record_Item_View');add_action('wp_ajax_nopriv_record_view', 'Record_Item_View' );
[...]
En este caso, a diferencia de la anterior vulnerabilidad no es necesario que magic_quotes_gpc estén deshabilitadas en el servidor ya que la query que se construye no hace uso de comillas simples o compuestas en el parámetro inyectable.
POC
Utilizando burp para visualizar las peticiones que se realizan al hacer click sobre uno de los productos, vemos que automáticamente se llama a la acción "record_view" a través de un POST a "/wp-admin/admin-ajax.php". Los parámetros enviados a esta pagina se verán en el burp similares a:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: <wordpress host>
[...]
Cookie: wordpress_f305[...]
Item_ID=2&action=record_view
La mecánica para extraer los datos de la base de datos es similar al anterior SQLi y como ya se ha mencionado, no será necesario que el servidor de destino tenga deshabilitado las magic quotes.
En el próximo artículo espero poder liberar de un par de vulnerabilidades diferentes a SQLi también muy interesantes.
[1] https://www.exploit-db.com/exploits/36823/
[2] https://www.exploit-db.com/exploits/36824/
[3] https://wpvulndb.com/vulnerabilities/7946
[4] https://wpvulndb.com/vulnerabilities/7948
PD: ¡Actualizad siempre!