El ataque de eu2010.es en otra web de la administración

Mucho revuelo se ha armado con el ataque a la web de la presidencia europea de turno. Llegó incluso a estar en la portada de El Mundo (edición impresa).

Por su parte, desde la Secretaría de Estado de Comunicación reiteraron que la página no ha sido alterada por ningún intruso, ya que la imagen mostrada en el engaño ha sido cargada desde un servidor remoto y no es posible acceder a ella usando normalmente la web.

Y la verdad es que, aunque 11,9 millones por adaptar un OpenCMS y darle mantenimiento y “seguridad” es el robo del siglo, hay que admitir que el error era del propio OpenCMS (el gestor de contenidos que utilizan… que no es PHP :-P), y además un XSS, si no se utiliza para robar cookies o hijacking es prácticamente inofensivo.

No obstante, este error de principiante no es el único pecado de Telefónica (o de la cárnica que haya subcontratado para el proyecto). Santi Saez nos desvela que la web fue alojada en un servidor con un sistema operativo obsoleto (Debian 4), el panel de control Plesk accesible públicamente, el SSH accesible públicamente y permitiendo el login a root y un BIND (servidor de DNS) vulnerable. Además, el servidor de nombres no era dedicado. Ahora han intentado arreglarlo externalizando el hosting en Akamai.

Pero no nos desviemos del tema, que es explicar cómo funciona esta vulnerabilidad, y cómo explotarla en otra web de la administración que todavía no ha parcheado el OpenCMS.

Qué es XSS

Un ataque XSS o cross-site scripting es una vulnerabilidad de aplicaciones web que consiste básicamente en conseguir que en la página víctima se cargue un script de otro sitio. Como sabemos que los scripts (casi siempre JavaScript) se ejecutan en el navegador del cliente, es evidente que este ataque no va a alterar el servidor en nada.

Pero ¿cómo hacer que una página cargue un script? Para buscar vulnerabilidades XSS y cualquier otra vulnerabilidad web es esencial que encontrar puntos de entrada de datos por parte del usuario. Estas puertas por las que introducimos datos suelen ser:

  • URL (casi siempre parámetros): la dirección de una página la introducimos nosotros y la interpreta el servidor. Habitualmente suelen dar más juego los parámetros (en PHP, lo que se obtiene con $_GET[]). Es el que usaremos en nuestro caso práctico.
  • Formularios: en los formularios de login, búsqueda, etc se nos piden datos que después el servidor interpreta. Por aquí se suelen colar las inyecciones SQL, otro ataque que veremos en futuros posts y que puede llegar a ser muuuuy peligroso, a la vez que sencillo.
  • Cabeceras: son manejadas por los servidores. No suelen dar problemas, porque los desarrolladores web no suelen tocar ahí ;-)
  • Cookies: más complejas y difíciles de atacar, aunque pueden llegar a ser también muy peligrosas (autenticación falsa, etc). Es un tema interesante para un futuro post.

Pues bien, entremos en materia. Imagina un formulario de búsqueda de una web cualquiera. Hay un cuadro de texto en el que tú introduces una palabra, por ejemplo, ‘normativas calidad’ porque quieres buscar las normativas de calidad de un organismo público. Cuando pulsas “buscar”, aparece una página de resultados en el que arriba dice “se han encontrado x resultados para ‘normativas calidad’”. Es decir, repite lo que le has dicho. Ha puesto en la página lo que tú habías introducido en el cajetín de búsqueda.

Vale, pero ¿y si en lugar de ‘normativas calidad’ pones ‘<img src=”http://bean.com/mister-bean.jpg”>’? En teoría la página debe repetir lo que has escrito. Si la web no está protegida, entonces verás una linda foto de Míster Bean en la página de resultados, ya que el navegador interpreta el HTML que has introducido y el buscador ha repetido. Acabas de inyectar HTML, pero no has manipulado el servidor. Esa foto de Míster Bean la ves tú en tu navegador y nadie más.

Si en lugar de HTML plano introduces un script (bien por invocación bien por introducción directa del código), ese script se ejecutará en el navegador, y podrás manipular los elementos de la página, hacer redirecciones… lo que quieras, pero siempre sabiendo que eso se está ocurriendo en tu navegador y sólo en tu navegador.

En los formularios de búsqueda —como ocurrió con eu2010.es— esto no constituye un peligro real para la página (aunque si el sitio es famoso o asociado a política no te dará muy buena imagen), pero si esta inyección la introduces en un cuadro de texto para publicación (un foro, un comentario en un blog, etc), entonces las manipulaciones que hagas las verán todos los que accedan a esa página, y eso ya empieza a ser peligroso.

Tutorial para ser hacker en 5 minutos

Vamos a ver este ataque en la práctica. La web del Centro de Investigaciones Sociológicas, construida sobre OpenCMS, tiene un lindo buscador. Si introducimos una palabra clave como “hola mundo”, la página de resultados repetirá nuestras palabras como un loro:

Página de resultados de búsqueda

Aquí tenemos el código fuente:

Primer fallo... no poner comillas para los valores, como exige el estándar HTML. Bueno, más fácil nos lo ponen.

Si en lugar de 'hola mundo' ponemos '>hola mundo <', entonces el código generado será

<input class="txt" type="text" size="15" name="query" value=>hola mundo <>

Lo que acabamos de hacer es inyectar código cerrando la tag input con el símbolo '>', y el resultado será que vemos el texto box de búsqueda vacío y nuestro mensaje, 'hola mundo' a su lado. Bien, vamos a divertirnos. Si introducimos '><script>alert("Hola mundo")</script>' inyectamos HTML con un JavaScript (la función alert() muestra un mensaje emergente), que se ejecuta con el siguiente resultado:

XSS con un alert() en JavaScript

Hala, ya somos super-hackers de la CIA —voy corriendo a publicarlo en Meneame :-P—. Ah, no, todavía nos falta el "golpe final", introducir la imagen de Mr. Bean. Ahora simplemente inyectamos una imagen (habiendo cerrado previamente la tag input con el signo '>'):

 ><img src="http://blog.tmcnet.com/blog/tom-keating/images/mr-bean.jpg"> 

Mr. Bean en la web del CIS

Si aparece repetida es porque en la página de resultados se muestra el mensaje de "buscar otra vez" en varios idiomas.

(Lo que viene ahora no es muy importante): hay una particularidad en este caso, y es que, nuestra inyección la podemos hacer tanto con la casilla de búsqueda (es decir, enviando el formulario por POST) como introduciendo el parámetro query —el nombre del campo de texto— en la propia URL (es decir, haciendo petición GET). Esto ocurre porque los servlets—eso que se utiliza en Java para hacer aplicaciones web— mete en el mismo "saco" (el método getParameter()) los parámetros introducidos por formularios y URL. Eso, combinado con la mala práctica de poner doPost en doGet y viceversa —o sea, decirle a Java que haga lo mismo para las peticiones GET y POST— hace que podamos inyectar código no sólo a través del campo de texto de búsqueda, sino también desde la URL. Así podremos distribuir nuestro super-hackeo a nuestros amigos, y al igual que pasó con eu2010.es, decir/tuitear...

¡Mira, mira! ¡Mr. Bean también se fue al CIS! http://is.gd/679lU

¿Cómo prevenir esta vulnerabilidad?

Si eres webmaster o desarrollador, ve y corre a comprobar si tus webs son vulnerables a este ataque. En caso de que hayas recibido una desagradable sorpresa, no tranquilizará saber que prevenirlo es tan sencillo como filtrar con una función la entrada del usuario. Imaginemos que tenemos un buscador en PHP que recibe las palabras clave por un formulario y un campo de texto llamado "busqueda". Si en nuestra página de resultados tenemos algo como

Se han encontrado 

sólo tendremos que sustituirlo por

Se han encontrado <?php echo $numero_de_resultados ?> resultados para <?php echo htmlentities($_POST['busqueda']) ?>

...y estaremos a salvo (más información sobre htmlentities()). Aunque hay métodos más sofisticados y completos, este funciona muy bien, ya que sustituye los caracteres especiales de HTML en sus entidades correspondientes, de tal forma que el atacante no verá su código interpretado, sino mostrado tal y como lo escribió.

Moraleja

¡No te fíes de los usuarios! Filtra todas las entradas (¡todas! formularios, URL, etc). Si esperas un valor numérico en un campo de texto, comprueba que es un número; si esperas una dirección de email, comprueba que es un email (pero con expresiones regulares ¿eh? nada de cosas raras).

Conclusión

Vivimos en la era del ladrillo, y en contra de lo que nuestros torpes políticos digan, el modelo de negocio del ladrillo no sólo no ha muerto, sino que se ha extendido como un cáncer hacia la industria informática. Nuestro negocio está lleno de gente con talento que cobra poco, y políticos corruptos que chupan generosas subvenciones y concursos públicos de una administración a la que no le importa malgastar un dinero que nos pertenece a todos.

Como dice un amigo... en España la ingeniería informática tiene tres principales salidas: por tierra, mar y aire.

Introducción visual a jQuery

Hace unos días impartí un breve taller de introducción a jQuery a mis amigos del SAW. El taller está pensado para durar unas cuatro o cinco horas, con ejemplos prácticos y casos reales, de forma que el asistente salga conociendo las posibilidades de jQuery y su aplicación en la vida real. Aquí tienes la presentación visual que complementa el taller (hecha con Prezi):

El taller está orientado a diseñadores web y programadores con conocimientos básicos de JavaScript y HTML. Así que si tú y tu equipo queréis aprender jQuery en un día, decidle a vuestro jefe que me contrate ;-)

5 razones para no usar Smarty (o similares)

Ejemplo de uso de SmartyEn la mayoría de los casos, Smarty no es la solución idónea para separar la lógica de las vistas, aunque hay que admitir que la versión 3 está siendo un salto de calidad impresionante. No obstante, estas son las cinco principales razones que he encontrado para no incluirlo más en mis proyectos:

  1. PHP es un lenguaje con sintaxis embebida, es decir, es de por sí un sistema de plantillas. Para mayor claridad puedes utilizar la tag de apertura corta (<?en lugar de<?php) y el operador de impresión (<?=$nombre ?> en lugar de <?php echo $nombre ?>). Además, Smarty no añade funcionalidad.
  2. Con Smarty debes aprender una nueva sintaxis para imprimir variables, hacer bucles, etc.
  3. En aplicaciones complejas tendrás que crear un montón de plug-ins para cargar widgets u otros fragmentos de código para las vistas que quieras reutilizar. O bien utilizar cada dos por tres la tag {php}.
  4. Smarty es difícil de integrar en gestores de contenido, frameworks y editores de código.
  5. Smarty reduce el rendimiento de tus aplicaciones web ya que debe parsear y convertir a PHP las plantillas. Incluso con caché el rendimiento se ve afectado (más accesos a disco).

Ya habíamos hablado de este tema en Plantillas PHP: cuestión de rendimiento, proponiendo el funcionamiento básico de un motor de plantillas basado en PHP puro.

ACTUALIZACIÓN (23 de noviembre de 2009):

Algunos expertos han escrito artículos en profundidad sobre este tema, por ejemplo “Template Engines” de Brian Lozer.

Con respecto a las alternativas, Savant y PHPTemplate (creado para Drupal) son buenas opciones. Además, los principales frameworks (CakePHP, etc) y gestores de contenido (Joomla!, WordPress) suelen incluir sus propios motores de plantillas en PHP.

«In short, the point of template engines should be to separate your business logic from your presentation logic, not separate your PHP code from your HTML code.»

ACTUALIZACIÓN (31 de enero de 2013):

En este artículo no defiendo que los sistemas de plantillas sean malos siempre. No son ni malos por naturaleza, y hay muchos tipos de proyectos en los que tiene sentido usarlos. Personalmente no los he necesitado nunca, aunque si tengo que elegir uno elijo Twig, sin duda.

Aún más rendimiento con CssDispatcher

En el artículo “Maneja las CSS como un profesional” veíamos algunas técnicas para insertar variables y funciones en las hojas de estilo, así como posibles mejoras del rendimiento en el cliente a la hora de servir las CSS, y para ello utilizábamos la biblioteca CssDispatcher.

Pues bien, en este post vamos a ver varias soluciones para aumentar el rendimiento en el servidor cuando servimos hojas de estilos, especialmente si son hojas de estilos tratadas con CssDispatcher.

Es importante diferenciar las mejoras de rendimiento en el cliente y el servidor. Mientras los benchmarks nos dicen que el 80% del tiempo de carga de una página lo causa el cliente (transferencias, renderizado, etc), para nosotros también es importante ese otro 20%, porque genera una carga en el servidor (acceso a disco duro, procesador, memoria…) que puede ponernos en aprietos si las visitas aumentan.

Accesos al disco duro

De por sí, servir una hoja de estilos simple no suele ser muy costoso para un servidor web. Si un cliente solicita /main.css, el servidor abre el archivo, lo lee y lo devuelve. Además, si el sistema operativo implementa caché de ficheros, 1000 visitas no generarán 1000 accesos al disco duro, lo cual aumentará la velocidad y reducirá la carga del servidor.

Si queremos ir más allá y acelerar aún más los accesos a disco duro podemos poner los archivos del servidor un sistema de archivos montado sobre la memoria RAM. Es decir, una porción del sistema de archivos que, en lugar de guardarse en el disco duro, lo hace sobre una porción de la memoria RAM. No es una técnica muy recomendable, porque aunque no es difícil montarlo, los datos desaparecen al apagar o reiniciar la máquina, por lo que habría que tener sincronizados los directorios en memoria con otros en el disco duro para que no se perdiesen.

Procesamiento de la petición

Como ya he dicho antes y todo el mundo sabe, para el servidor web devolver un archivo estático no le cuesta más que abrirlo e irlo enviando por el socket. Pero si la hoja de estilos pasa por el compilador de PHP (por introducir códigos PHP en la hoja de estilos) entonces la cosa se complica un poco. Para acelerar el procesado de scripts es recomendable usar:

  • Una caché de salida, que evita tener que ejecutar repetitivamente el mismo código para cada petición.
  • Un opcode cache, que evita tener que compilar el código para cada petición (si hemos implementado caché de salida, para la mayoría de las peticiones no será necesario ni compilar ni ejecutar el código).

Diagramas de flujo para el despacho de peticiones con caché

Para la opcode cache existen herramientas como APC, XCache o eAccelerator, que una vez instaladas y configuradas debidamente funcionan sin que tengamos que modificar el código de nuestra aplicación web. Puedes leer más sobre opcode cache en PHP Accelerators.

En cuanto a la caché de salida, hay principalmente dos caminos a seguir: gestionar la caché desde el servidor web o desde PHP. La ventaja del primero es que, al no ejecutar código PHP es un poco más rápido. Esto se puede lograr con el módulo Apache mod_cache. La otra posibilidad, gestionarlo con PHP, nos da más flexibilidad y control sobre la solución. En concreto, podremos guardar la caché en el disco duro o en memoria, con gestores de caché como Memcached (mi preferido).

Caché con URL propia

Existe una tercera solución, y es utilizar una caché en disco duro sin procesar las peticiones por PHP. El funcionamiento es el siguiente: si tenemos una hoja de estilos generada con CssDispatcher en/estilos.css.php, podemos guardar la salida en un archivo y ponerlo accesible en/estilos.css. Al ser un archivo estático, se servirá tan rápido como una hoja de estilos normal. Este sistema tiene un problema, y es que si tenemos hojas de estilos específicas para navegadores, no resultará fácil servir una u otra de forma estática. La única solución aparente es utilizar redirecciones y estructuras condicionales en los ficheros de configuración de Apache.

Algo de código, por favor

Vamos a implementar tres sistemas de caché: uno con Memcached, otro en disco y otro en disco accesible directamente.

Cache en Memcache

<?php

include 'class.Css.php';

//Nombre del elemento en Memcache
$cache_name = 'moduleName_' . Css::getUserAgent();
//Tiempo de vida. Para archivos que no cambien mucho, poner valores altos
$cache_time = 3600*24; 

//Conexión a Memcache
$mc = new Memcache();
$mc->connect('localhost');

//Si la salida no está en caché, se genera y se almacena
if (!$out = $mc->get($cache_name)) {

    //Incluye la biblioteca CssDispatcher.
    //No se incluye si no es necesaria
    include 'class.CssDispatcher.php';

    $estilos = new CssDispatcher;

    //Crea una nueva hoja de estilos
    $general = new Css('example.css.php');
    //Asigna variables
    $general->background = '#eee';
    $general->border_color = 'red';
    $general->header_size = 2.1;

    //Crea otra hoja de estilos para navegadores WebKit
    $another = new Css('example2.css.php', Css::UA_WEBKIT);
    $another->bold = 'font-weight: bold';

    //Añade las hojas de estilo al dispatcher
    $estilos->add($general);
    $estilos->add($another);

    //Añade un aviso
    $out = '/* Generated by CssDispatcher ' . strftime('%c') . " */n"
        . $estilos->render(false, true, false, true);

    //Guarda la salida en Memcache
    $mc->set($cache_name, $out, null, $cache_time);
}

header("Content-Type: text/css");
echo $out;
die();

?>

Tiempo de ejecución (renovando la caché): 3.6480ms
Tiempo de ejecución (utilizando la caché): 0.6439ms

Caché en disco duro

Este script tendrá será más lento y pesado para la CPU que el anterior, pero no necesita de servicios adicionales como Memcached, imposibles de instalar en alojamientos compartidos.

<?php

include 'class.Css.php';

//Archivo de caché
$cache_name = 'cssCache/moduleName_' . Css::getUserAgent();
//Tiempo de vida, poner valores altos para archivos que no suelen cambiar
$cache_time = 30; 

if (file_exists($cache_name)) {
    if (date('U') - fileatime($cache_name) > $cache_time) {
        $regenerate = true;
    } else {
        $out = file_get_contents($cache_name);
    }
} else {
    $regenerate = true;
}

//Si la salida no está en caché, se regenera
if ($regenerate) {

    //Carga la biblioteca CssDispatcher
    include 'class.CssDispatcher.php';

    $estilos = new CssDispatcher;

    //Crea una nueva hoja de estilos
    $general = new Css('example.css.php');
    //Asigna variables
    $general->background = '#eee';
    $general->border_color = 'red';
    $general->header_size = 2.1;

    //Añade otra hoja de estilos para navegadores WebKit
    $another = new Css('example2.css.php', Css::UA_WEBKIT);
    $another->bold = 'font-weight: bold';

    //Añade las plantillas al dispatcher
    $estilos->add($general);
    $estilos->add($another);

    $out = '/* Generated by CssDispatcher ' . strftime('%c') . " */n"
        . $estilos->render(false, true, false, true);

    //Graba la salida en el fichero
    file_put_contents($cache_name, $out);
}

header("Content-Type: text/css");
echo $out;
die();

?>

Tiempo de ejecución (renovando caché): 1.290ms
Tiempo de ejecución (utilizando caché): 0.242ms

El último caso, una caché en ficheros accesibles públicamente, es más rápida y más ligera que la anterior, pero nos añade la tarea de actualizar la caché cuando nos interese, bien con una tarea programada (cron) o mediante algún otro mecanismo.

<?php

include 'class.Css.php';
include 'class.CssDispatcher.php';

//Generamos hojas de estilos para todos los navegadores
$navegadores = array(Css::UA_IE6, Css::UA_IE, Css::UA_GECKO, Css::UA_WEBKIT);

//Para cada navegador generamos la hoja de estilos correspondiente
foreach ($navegadores as $navegador) {
    //Archivo de caché
    $cache_name = 'cssCache/estilos_' . $navegador . '.css';
    //Tiempo de vida. Para archivos que no cambien mucho, poner valores altos
    $cache_time = 30; 

    $estilos = new CssDispatcher;

    //Crea una nueva hoja de estilos
    $general = new Css('example.css.php');
    //Asigna variables
    $general->background = '#eee';
    $general->border_color = 'red';
    $general->header_size = 2.1;

    //Añade las plantilla al dispatcher
    $estilos->add($general);

    //Hoja de estilos para WebKit
    //No vale utilizar la detección de navegador de CssDispatcher
    if ($navegador == Css::UA_WEBKIT) {
        $another = new Css('example2.css.php');
        $another->bold = 'font-weight: bold';
        $estilos->add($another);
    }

    $out = '/* Generated by CssDispatcher ' . strftime('%c') . " */n"
        . $estilos->render(false, true, false, true);

    //Graba la salida en el fichero
    file_put_contents($cache_name, $out);
}

echo "Caché de CSS actualizada";

?>

 

Tiempo de ejecución del actualizador de caché: 4.009ms
Tiempo de obtención de la hoja de estilos: 86ms (incluye la transmisión del fichero)

Ya que con este método no tenemos un sólo punto de entrada para las diferentes hojas de estilos, será necesario cargar una u otra CSS en función del navegador, a través de los artificios clásicos: comentarios condicionales o la cabecera User-Agent. Pero tendremos que hacerlo manualmente, ya que al usar ficheros estáticos no se ejecuta CssDispatcher. Por ejemplo, según el ejemplo, si el navegador es Firefox tendremos que invocarcssCache/estilos_20.css.

Por mi parte, es todo por hoy. ¿Conoces alguna otra técnica para aumentar el rendimiento al servir CSS? ¿Has probado a cachear la salida de CssDispatcher?

Linux es un buen entorno de programación en PHP

Siempre he apostado por el software libre, pero hasta hace no mucho usaba Windows, un sistema privativo, para desarrollar. No me convencían las herramientas de programación en PHP para Linux, ya que estaba muy contento con Notepad++ para escribir scripts y TortoiseSVN para trabajar con Subversion. Usaba un servidor WAMP y phpMyAdmin para administrar la base de datos.

Ahora, sobre Debian, trabajo con unas herramientas que no tienen nada que envidiar a las anteriores. Vayamos por partes.

Un editor de PHP para Linux

Si algo me faltaba en Notepad++ era un autocompletado de código potente (el de Notepad++ se basa solamente en las palabras del archivo actual) y la autodocumentación en línea (es decir, soporte de phpDoc). Tras darle una oportunidad a Eclipse/PDT, me he decidido definitivamente por NetBeans. Tiene integración con Subversion, phpDoc, XDebug y Firefox, y me parece más productivo que Eclipse. Una de las características que más me gusta es que tiene en cuenta la cláusula @return de los comentarios phpDoc de una función para entender el tipo de dato que devuelve:

NetBeans, un buen IDE para programar con PHP

No obstante, no he dejado de usar Notepad++, gracias a WINE. Lo utilizo para manejar archivos de diferentes codificaciones, procesar textos con expresiones regulares y escribir PHP cuando no me apetece iniciar NetBeans ;-)

Un cliente Subversion para Linux

Como ya he dicho, NetBeans tiene soporte nativo de Subversion. En otros casos, utilizo kdesvn y RapidSVN. Además, he aprendido el valioso placer del cliente svn por línea de comandos ;-) Por cierto, existe un cliente privativo aunque muy potente llamado SyncroSVN, disponible para Windows, Mac y Linux.

Un depurador de PHP para Linux

De nuevo NetBeans me soluciona la papeleta, pero también utilizo KCacheGrind, un excelente visor de volcados XDebug:

KCacheGrind, herramienta para visualizar volcados de XDebug

Cross-browser en Linux

Si algo abunda en GNU/Linux son los navegadores. Los de siempre, Firefox (con el maravilloso Firebug), Opera, Chrome y lynx (o links o elinks), además de Konqueror (que nadie usa). Para el malvado Internet Explorer utilizo una máquina virtual con Windows XP en la que dispongo de muchas versiones de IE, gracias a Internet Explorer Collection. Además, existen renderizadores de Internet Explorer on-line.

Internet Explorer

Instalar extensiones es más fácil en Linux

Los paquetes Apache+PHP para Windows suelen traer algunas extensiones, pero a la hora de instalar otras nuevas (tanto PECL como PEAR) los métodos varían, y las utilidades de línea de comandos no siempre funcionan.

En Linux muchas extensiones PECL y bibliotecas PEAR están disponibles a través de los repositorios.

Repositorio Debian con paquetes PHP

Además, el paquete de desarrollo php5-dev permite instalar compilando las extensiones de la forma más sencilla:

# pecl install nombre_de_la_extension

Y lo mismo para librerías PEAR:

# pear install nombre_de_la_biblioteca

Conclusión

Si aún trabajas con Windows, anímate a probar GNU/Linux con Ubuntu o la distribución que más te guste. En una hora habrás podido instalar todas las aplicaciones que necesitas para programar de forma productiva y cómoda con PHP. Si ya lo has hecho, ¿qué experiencia tienes? ¿utilizas alguna de las herramientas que he citado? Comentar es gratis ;-)

ACTUALIZACIÓN 14/dic/2012: llevo unos meses usando PhpStorm en el trabajo y estoy encantado. Es mucho más rápido y potente que NetBeans, y las opciones de personalización son casi infinitas. Lo malo… que es privativo y caro.

ACTUALIZACIÓN 14/feb/2013: he dejado de usar Subversion para mis proyectos personales. Me gusta más git, por su velocidad y su flexibilidad. Utilizo GitHub para los repositorios públicos y BitBucket para los privados (ambos gratuitos), casi siempre desde la consola. Si quieres aprender Git, los cracks de GitHub han hecho un tutorial interactivo impresionante.