viernes, 16 de agosto de 2013

Evasión de restricciones en la subida de archivos de Joomla

El equipo de Joomla, el popular CMS libre escrito en PHP, ha corregido un error que permitía a un usuario, con los permisos correspondientes, subir un archivo con cualquier extensión.

La funcionalidad de subida de archivos a Joomla restringe el tipo de los archivos en base a una lista blanca. Por ejemplo, un usuario podría subir archivos del tipo jpeg, pdf, xls, etc, pero por otro lado no estaría permitida la subida de archivos que contuvieran código PHP. Para impedir esto en esa lista blanca se excluyen extensiones como ‘.php’.

El problema reside en que la función que comprueba esto no tiene en cuenta extensiones nulas, es decir, extensiones que solo tengan el '.'.

Veamos el código de 'libraries/joomla/filesystem/file.php'.

La función miembro que obtiene las extensiones de la clase JFile:

public static function getExt($file)
        {
               $dot = strrpos($file, '.') + 1;

               return substr($file, $dot);
        }

'strrpos' es una función que busca la última ocurrencia de una subcadena dentro de una cadena o lo que es lo mismo (y más eficiente): busca la primera ocurrencia de una subcadena en una cadena comenzando desde el final hasta el principio.

Ante un archivo denominado 'shell.php.' el valor de la variable 'dot' valdrá 10 ya que la cadena termina con un punto.

La función miembro termina retornando el valor de la función 'substr'. Esta función retorna una subcadena correspondiente a la cadena 'file' desde la posición 'dot'. Como sabemos que 'dot' vale 10 y la cadena 'file' tiene como longitud 10 lo que retornará la función será una cadena vacía.

Si ahora observamos el código sin parchear del archivo '/administrator/components/com_media/helpers/media.php':

$format = strtolower(JFile::getExt($file['name']));

$allowable = explode(',', $params->get('upload_extensions'));
$ignored = explode(',', $params->get('ignore_extensions'));
if (!in_array($format, $allowable) && !in_array($format, $ignored))
{
        $err = 'COM_MEDIA_ERROR_WARNFILETYPE';
        return false;
}


En la primera línea, con nuestra cadena 'shell.php.', sabemos que retornará falso:

$format = strtolower(JFile::getExt($file['name']));

Posteriormente dicho valor se usará en la comparación:

if (!in_array($format, $allowable) && !in_array($format, $ignored))

Esto quiere decir que si de dan las dos condiciones entonces y solo entonces se provocará un error en la subida de archivos y se abortará la operación.

La primera expresión retornará 'true' ya que la cadena vacía no forma parte de las extensiones permitidas y el 'false' está negado. La segunda parte, y aquí viene lo ingenioso, es que estamos buscando la cadena vacía en un array vacío. Esto es debido a que Joomla no tienen extensiones ignoradas por defecto y por lo tanto dicha función retornará 'true', pero como estamos negando el valor, finalmente lo convertimos a 'false' y con esto hemos evadido la restricción, ya que siendo un AND se deberían haber evaluado las dos condiciones a 'true'.

El fallo ha sido corregido y afectaría a las versiones anteriores a la 2.5.13 inclusive de la rama 2.5 y anteriores a la 3.1.4 también inclusiva. El CVE está pendiente de su publicación.

Más información:

[20130801] - Core - Unauthorised Uploads

Critical Joomla File Upload Vulnerability


David García
Twitter: @dgn1729


1 comentario:

  1. Hola, muy buena explicación. Pero quería hacer una observación. En realidad el array "$ignored" no está vacío sino que contiene como único elemento una cadena vacía. Esto último por acción de explode. Por ello, al buscar una "extensión vacía" en el array, se obtiene "true".

    Un saludo.

    ResponderEliminar