Mostrando entradas con la etiqueta programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta programación. Mostrar todas las entradas

jueves, 5 de febrero de 2015

La importancia de leer código fuente

Me pregunta mi amigo E que si le puedo dar algún consejo para seguir completando su formación en el campo de la programación. E es un informático que ha terminado hace poco sus estudios. Aparte de su formación académica, últimamente ha realizado varios cursos en los que ha conocido distintos paradigmas de desarrollo y diferentes tecnologías y lenguajes.

Hemos intercambiado varias ideas durante una fructífera conversación, y al final, como suele ser habitual, creo que el que supuestamente tenía que dar los consejos (yo) ha salido más enriquecido de la conversación que mi amigo, gracias a la cantidad de información e ideas que me ha dado él a mí.



Sin embargo, no quiero dejar que se pierda un consejo que, este sí, le he dado yo a él y sobre el que le he insistido mucho, y que considero muy importante. Lo voy a poner en letras gordas porque me parece muy importante:

LEE CÓDIGO FUENTE ESCRITO POR OTRAS PERSONAS

Muchas veces, debido a las limitaciones inherentes al tiempo disponible (cursos) o al espacio (libros), cuando nos explican una técnica, lenguaje o herramienta, los ejemplos que nos presentan son "de juguete". Esos ejemplos suelen ser suficientes para ilustrar alguna característica concreta que se quiere destacar, pero en la mayoría de las ocasiones no son realistas. Si pusiéramos ese código en producción, posiblemente nos despedirían al día siguiente (y de manera justificada, seguramente). Lo más probable es que tropezáramos con muchos problemas, ya que en esos ejemplos "de clase" o "de libro" no suelen incluirse cosas que sí se dan en el mundo real.

Por ejemplo, el control de entradas de usuario. En la vida real, un programa debe estar preparado por si en un cuadro de entrada de texto un usuario teclea cualquier dato con basura o, peor aún, con intenciones dañinas (como inyectar código SQL, provocar desbordamiento de buffers...). Los controles necesarios o más habituales en ese tipo de casos norlamente no se explican en los ejemplos de libro, ya que muchas veces lo que se quiere es ir rápidamente al meollo de alguna otra cuestión. Excepción hecha de aquellos temas del libro o curso en los que se quiere ilustrar, precisamente, cómo "sanear" las entradas del usuario antes de proceder a procesarlas.

Por todo ello, y ya que tenemos la suerte de tener chorropocientillones de líneas de código de casi cualquier lenguaje disponibles en Internet en cientos de proyectos grandes, medianos o pequeños (que también los hay interesantes), ¿por qué no sacarles partido? LEE CÓDIGO DE OTROS.

Quizás sea mejor comenzar con proyectos pequeños, pues te puedes hacer una idea global de todas las partes del proyecto con una o dos horas de lectura del código, y seguramente veas estilos y formas de hacer las cosas diferentes de tus costumbres. No quiere decir que sean mejores, pero a lo mejor te dan ideas que no se te hubieran ocurrido antes. A mí me ha pasado: viendo cómo otros han organizado datos o han creado estructuras o han hecho una clase o una función para llevar a cabo una tarea, me he inspirado para hacer algo parecido en mi código, refactorizándolo para dejarlo más eficiente, más legible, más mantenible o, simplemente, más bonito, lo que también tiene implicaciones en cuanto a mantenibilidad, legibilidad y, en última instancia, autosatisfacción (a ver si sólo los artistas van a poder buscar la belleza).

No es necesario tener controlado totalmente el hábito de leer proyectos pequeños para pasar a leer código de proyectos más grandes. Un buen recurso son los proyectos de código abierto (open source) más exitosos, donde su propio éxito es una garantía de que encontraremos código escrito de manera profesional, y seguramente bien estructurado y modularizado. Esa modularidad nos permitirá dedicarnos a leer una parte pequeña de ese proyecto y encontrarle utilidad "per se", sin tener que leer y mucho menos comprender el proyecto entero. Así que, insisto: lee (trozos de) código de proyectos grandes.

--

¿Y tú, has aprendido mucho leyendo código fuente de proyectos reales, o todo lo que sabes se lo debes a los ejemplos "de juguete"?

martes, 4 de febrero de 2014

Las mejores inyecciones son chorizos y jamones (parte 4, última)

4. Qué se puede hacer

Hasta aquí, hemos visto algunos ejemplos muy sencillos de cómo se puede intentar inyectar código SQL en una aplicación web (ojo: esto también sirve para una aplicación de escritorio). Pero, ¿hay algo que la madre de Caperucita pueda hacer para que el lobo no le putee la base de datos con una inyección SQL?

Bueno, no hay una solución mágica, pero se pueden hacer algunas cosas que sirven como pequeñas ayudas. Entre unas cosas y otras, se puede mitigar algo el poder de los ataques. Vamos allá. Seguro que a algunos se os ocurren más, pero estas son algunas de las actuaciones más básicas.

4.1 Instrucciones SQL precompiladas (prepared statements)

En primer lugar, está el uso de instrucciones SQL preparadas o precompiladas (prepared statements). Esto depende tanto del lenguaje (los más utilizados soportan todos formas más o menos parecidas de hacerlo) como de la BD. Estas instrucciones preparadas son como consultas precompiladas en las que lo único que falta es rellenar algunos datos que se han establecido como parámetros. Al estar precompiladas, estas instrucciones sólo se analizan (parsing) una vez, y se gana velocidad de ejecución, ya que el motor de la BD puede optimizar la consulta cuando se compila.

Pero lo mejor de ellas, para el caso que nos ocupa, es que los parámetros son gestionados por el motor de la BD; no tenemos que componer la sentencia SQL concatenando partes fijas con los parámetros. Bien es cierto que el motor también puede cometer un fallo, pero, seamos realistas, me fío más del equipo de programadores del motor de la BD y de su sistema de revisión que de mí, aunque nada más sea por el tamaño del equipo que tiene que haber detrás de un motor como SQL Server, Oracle o MySQL, por poner algunos. Y mira que yo soy buen programador (y además, guapo), pero aún y con esas, no creo que les gane ;-)

El caso es que los parámetros normalmente tienen tipos (integer, string...) que son comprobados por el motor y eso facilita mucho evitar que se cuelen cosas "raras".

En el caso de PHP, que es el que me pilla más cercano ahora, las instrucciones preparadas se pueden utilizar con los PDOs. Más info aquí:
http://php.net/manual/en/pdo.prepared-statements.php

4.2 Sanear la entrada

Otra cosa que se puede hacer es analizar la entrada que está metiendo el usuario para ver si contiene caracteres raros. Por ejemplo, si en el campo donde espero leer un NIF me encuentro algo que no son dígitos ni letras, ¡alarma! Como norma general, se pueden validar campos habituales como el NIF, los apellidos, el nombre, etc... para alertar si contienen comillas, exclamaciones, barras, etc... Se puede funcionar por lista negra (un conjunto de caracteres prohibidos) o, mejor aún (en mi opinión), si se sabe de antemano, funcionar por lista blanca (conjuntos de caracteres permitidos). En un apellido no permitiría ni siquiera un dígito. No obstante, a veces habrá que permitir la comilla (se me ocurren apellidos como O'hara) y en particular hay que tener cuidado con las codificaciones de los caracteres con tildes, eñes, etc...

4.3 Permisos en la BD

Bueno, si a pesar de nuestras precauciones el atacante logra ejecutar una instrucción contra la BD, intentemos que al menos el daño sea el menor posible. Siguiendo el principio de mínimo privilegio, un usuario nunca debería tener más permisos de los estrictamente necesarios para realizar su labor. Si sólo va a consultar datos en la BD, no tiene por qué tener permisos para cargarse una tabla. De esta forma, aun en el caso de que un atacante consiguiera inyectar una instrucción del tipo "drop table", si la ejecución se lleva a cabo con los permisos de un usuario con acceso de sólo lectura, ésta fallaría.

Así que ya sabéis, para configurar bien los permisos, a revisar aquello del GRANT SELECT ON TABLA... para el caso específico de vuestra BD.

Principio del mínimo privilegio:
http://sophosiberia.es/principio-de-minimo-privilegio-reducir-el-area-de-ataque/

4.4 Otras pequeñas ayudas

Por último, hay algunas otras cosas que se pueden hacer que, aunque no son la panacea, pueden aportar su granito de arena.

4.4.1) Limitar el tamaño de los campos de entrada. 

Por ejemplo, los login de los usuarios. En entornos empresariales a veces hay reglas cuadriculadas para asignar los login (así ocurre en uno de los entornos en los que suelo trabajar). Si los nombres de usuarios no superan los 10 caracteres, por ejemplo, limitar el tamaño de ese campo puede contribuir a que desde el formulario no se puedan insertar peticiones con instrucciones largas (por ejemplo, no se podría insertar el DROP TABLE...). Esto no evita que se lance una petición a través de un programa elaborado específicamente para ello, pero dificulta un poco la realización de ataques.

2) Registrar lo que se ejecute

También puede ser conveniente llevar un registro (tabla de log, fichero, log del sistema...) con las operaciones que se van realizando desde los programas. Esto no va a evitar que se produzcan los problemas, pero al menos puede ayudar para, una vez producidos y detectados los mismos, determinar el origen (usuario, dirección IP...) y por lo menos saber por dónde buscar para intentar cazar al lobo. Si no hemos podido evitar el ataque, al menos nos queda el consuelo de intentar pillar al responsable y darle un capón.

3) Filtros por palabras

Se pueden filtrar determinadas palabras clave en las entradas del usuario (como "drop", "delete"...) en determinados campos donde esto no tiene sentido. Por ejemplo, normalmente no encontraremos nombres o apellidos con esas palabras, así que si un usuario se llama "Drop..." lo mínimo que se puede hacer es disparar una alerta y llamar al leñador, a que venga con el hacha.

4) Codificaciones de cadenas

Esta solución no la he probado, pero la vi apuntada en un foro y quizás merezca dedicarle un poco de atención. La idea es codificar las entradas del usuario haciendo una conversión de las cadenas de entrada a codificaciones puras ASCII.

Lo que se argumenta es que con estas codificaciones desaparecen las comillas, los puntos y coma y otros caracteres "peligrosos". Pero no tengo muy claro cómo se harían las búsquedas y comparaciones. En fin, ahí os dejo apuntados los nombres de las funciones que habría que mirar, para aquellos que deseéis investigar: base64, hex2bin, unhex, pack...

Aquí lo dejo

Bueno, con esto doy por zanjado el tema. Como veréis, la metáfora del cuento de Caperucita ha ido perdiendo fuerza con cada nueva entrada; fue una chorrada que se me ocurrió un día y después me ha dado un poco de pereza mantenerla, aunque ahí la dejo por si alguien quiere elaborar un poco sobre ella y darle mejor forma.

Por otro lado, como ya he dicho otras veces, estas entradas las escribo, en primer lugar, para mí, por lo que a veces puede que dé por supuestas ciertas cosas y no me esfuerce en explicar las bases, aunque creo que cualquier programador con un nivel básico puede seguir las explicaciones sin complicaciones. En este caso, apenas he tocado la punta del iceberg del tema en cuestión, pero a mí me bastaba con tener unas pruebas de concepto que funcionaran realmente, y lo he conseguido, así que al menos a mí me ha valido. El hecho de haberme obligado a escribir de forma más o menos estructurada me ha generado, como otras veces, más preguntas que respuestas, y un montón de ideas y tareas a realizar, así que ahora me toca dedicarme a implementar muchas de las ideas que han ido apareciendo durante la escritura de esta serie de entradas, así como hacer pruebas reales en mis aplicaciones, pues me he dado cuenta de algunos fallos que tengo que debería corregir cuanto antes.

Para eso me sirve escribir ;-)

Referencias

Aquí tenéis el enlace a todos los artículos anteriores de la serie:

Parte 1
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y.html

Parte 2
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y_23.html

Parte 3
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y_27.html

lunes, 27 de enero de 2014

Las mejores inyecciones son chorizos y jamones (parte 3)

3.2 Haciendo daño (DROP TABLE)

Preguntémonos ahora si sería posible llevar al cabo el caso del DROP TABLE descrito en el apartado 1.

Imaginemos que el atacante no sabe el nombre de las tablas de nuestra BD. Para ilustrar qué pasaría en este caso, primero probaremos con una tabla que no exista introduciendo en el nombre de usuario la expresión

' or '1'='1'; drop table kk2--

Y al pulsar el botón "Entrar" obtenemos


Imagen 3.1 Intentando eliminar una tabla inexistente

Bueno, no sabemos si ha funcionado el DROP TABLE o ha fallado, pero sí sabemos que no se produce ningún mensaje de error. El atacante podría ir haciendo pruebas por fuerza bruta, hasta encontrar una tabla que sí exista. Para probarlo, he creado en la BD una tabla "kk" y quiero ver si se elimina y se me notifica de ello. He repetido la prueba

Imagen 3.2 Intentando eliminar una tabla que sí existe

Como véis, ningún mensaje de error o éxito. Pero al comprobar si  la tabla sigue viva me encuentro con (...redoble de tambores...)

¡Vaya, ha desaparecido! (bueno, aquí no tengo una imagen para ilustrar esto, pero tendréis que creerme, es que un pantallazo en este caso no aporta nada).

Hmmm... peligro, peligro...

En este caso, el atacante no sabe si su técnica ha funcionado, pero si lo que le interesa simplemente es hacer daño, no le sería demasiado complicado realizar un script que fuera generando por fuerza bruta todas las combinaciones de nombres entre 1 y 30 caracteres (pongamos por caso) y cargarse nuestra BD completa. Vale que esto llevaría un buen rato, pero el lobo tiene tiempo libre y ganas de fastidiar.

Según he leído (esto no lo he probado), la ejecución de dos sentencias SQL en una única llamada a la función correspondiente no funciona con todas las BBDD. Según dicen, en MySQL no funcionaría. Pero yo acabo de comprobar que en SQL Server sí funciona, así que ¡CUIDADÍN!

3.3 Meter basura en casa (INSERT INTO)

Continuando con nuestras pruebas, imaginemos por un momento que el atacante sabe el nombre de la tabla de usuarios y los campos, al menos el del nombre del usuario y el del hash de la clave (login y epass, respectivamente, en nuestro ejemplo). Pensar que un atacante sabe tanto no es descabellado, pues es relativamente sencillo forzar errores que, si no se gestionan bien (casi nunca se gestionan bien al 100%) revelan algunos de estos datos. Más adelante veremos algunos ejemplos sobre formas de obtener información acerca de la BD, como nombres de tablas, campos, etc...

En ese caso, podría intentar insertar un registro que le proporcione una cuenta de usuario. Algo así como:

' or '1'='1'; insert into usuarios(login, epass) values ('lobito', 'x')--

Donde 'x' sería el valor hash de la clave que él desea ponerse. Vale, aquí quizás estamos suponiendo ya demasiado, como que se utiliza un algoritmo u otro (MD5, SHA1...) para calcular el hash, pero tampoco esto es descabellado. Además, utilizar una "sal" (salt) para la contraseña dificultaría el ataque, pero de momento seguimos con ejemplos en entornos más o menos sencillos.

Bien, pues introducida la expresión que he puesto, pulsamos Entrar y...

Imagen 3.3 Nos la han colado doblada

¡Vaya, ahí está el registro insertado!

Que luego esa información le sea más o menos útil al lobo en su lucha contra la Caperumadre es otra cosa, pero el caso es que ya ha conseguido introducir datos ficticios, basura, en la BD. ¡Hmmm, malo, malísimo!

La semana que viene continuaremos.

Referencia:

Las mejores inyecciones son chorizos y jamones (un cuento de inyección SQL). 

Parte 1
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y.html

Parte 2
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y_23.html

jueves, 23 de enero de 2014

Las mejores inyecciones son chorizos y jamones (parte 2)

En la parte 1 hablé de la teoría; ahora vamos a montar un entorno básico y a hacer algunas pruebas, a ver si lo que allí se contaba es así realmente.

2. Un entorno de pruebas

Para tener un entorno de pruebas, crearé dos páginas PHP, una con el formulario de solicitud de datos y otra con el resultado del procesamiento. Probablemente, en una aplicación real esta página sea la misma, o no, pero de momento estamos haciendo algunas pruebas "de juguete".

 1 <html>
 2     <head>
 3         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 4         <title>Pruebas SQL Injection</title>
 5     </head>
 6 <body>
 7     <h1>Introduzca usuario y contraseña</h1>
 8     <form action="test2_res.php" method=POST target=_blank>
 9         Usuario <INPUT TYPE="text" NAME="usuario" SIZE=20 MAXLENGTH=20>
10         <br>
11         Contraseña <INPUT TYPE="password" NAME="pwd"   SIZE=20 MAXLENGTH=20>
12         <br>
13         <INPUT TYPE="submit" CLASS="boton" VALUE="Entrar">
14     </form>
15 </body>
16 </html>
Listado 2.1. La página de prueba

que se ve más o menos así (con variaciones mínimas entre navegadores):

Imagen 2.1 Como véis, todo un derroche de diseño y buen gusto.

Y la parte de procesamiento en la página 2 podría ser algo más o menos así:

 1 <?php
 2     //...otras cosas
 3     
 4     $conn = conectar_bd(); //falta control de errores
 5     $usuario = $_POST['usuario'];
 6     $pwd     = $_POST['pwd'];
 7     $epwd    = strtoupper(md5($pwd));
 8     echo "Datos leídos '$usuario' y '$pwd' -->encriptada: '$epwd'<br>";
 9     $sql = "select login, epass from usuarios where login = '$usuario' and epass = '$epwd'";
10     $rs=odbc_exec($conn, $sql);
11     if (!$rs) {exit("Error en SQL");}
12     
13     $encontrados = 0;
14     while ($row = odbc_fetch_array($rs)) {
15         $encontrados++;
16         echo "encontrado registro " . $row['login'] . " " . $row['epass'];
17     }
18     
19     if ($encontrados == 0) {
20         echo "no se encontró el usuario";
21     }
22     
23     //... otras cosas
24 ?>
Listado 2.2. La página que procesa el login y la clave introducidos

Como habréis visto, si habéis leído el código (si no lo has hecho, vuelve unas líneas arriba y léelo antes de continuar), la clave no se almacena en claro, sino que se almacena su hash MD5, pero esto no cambia en nada la prueba de concepto y lo que hemos dicho hasta ahora (sólo añade un poco de realismo a la situación, ya que esto es algo habitual en sistemas reales). También veréis que el procesamiento se limita a listar todos los registros que se encuentren que cumplan la condición, que en principio es que coincida el login con el que ha introducido el usuario, y el hash de la contraseña introducida con el hash almacenado en el campo "epass" (e de encriptada). En un sistema real en producción, lógicamente este listado de los registros recuperados no se haría, pero para nuestras pruebas es más ilustrativo. También voy a mostrar la sentencia SQL que se ejecuta, ya que a efectos de muestra va a ser de ayuda para entender lo que está pasando por dentro, y el hash de la clave introducida, esto es simplemente porque durante las pruebas lo hice así y ahora no me apetece repetir las capturas de pantalla quitando esto.

Por otra parte, en la BD, montada sobre un servidor SQL Server, he dado de alta un usuario con nombre 'caperu' y contraseña 'reparto1'.

Primero insertamos un par usuario / clave normal y vemos que todo funciona como se espera:



Si introducimos una clave incorrecta:




Bueno, en apariencia todo funciona más o menos bien. Con esto ya tenemos el entorno de prueba montado. Manos a la obra.

3. Haciendo algunas pruebas

Ahora intentaremos probar casos similares al descrito en el apartado 1 para ver si conseguimos obtener el mensaje de acierto: "encontrado registro..."

3.1 ¿Es uno igual a uno?

En primer lugar, supongamos que el lobo está engandulado, y se limita a meter en el campo "usuario" la siguiente expresión:

' or '1'='1'--

y deja la contraseña en blanco (¿para qué teclear sin necesidad?). Le da a ejecutar... et voilà! (La explicación a por qué usar esta expresión está en la parte 1, así que no me voy a repetir aquí).




Vaya, esto pinta mal, ¿eh? El lobo malo ha conseguido revelar todos los nombres de los usuarios, incluyendo el de Caperucita (usuario "caperu"). Además, puede decir que ha entrado en la BD. Por supuesto, en un sistema real se comprobarían otras cosas, pero estamos trabajando con una prueba de concepto y el ejemplo ya nos permite atisbar algunos de los peligros de esta técnica.

Bueno, creo que hasta aquí ya está bien por hoy. Hemos montado un entorno de pruebas y esta primera prueba ha sido suficiente para tomar conciencia del problema. 

¿Qué pruebas se te ocurre que podría hacer el lobo a continuación? ¿Cómo podría la Caperumadre prevenirse contra esto? 

La semana que viene seguiremos con el tema...

Referencia:

Las mejores inyecciones son chorizos y jamones (un cuento de inyección SQL). Parte 1
http://cosicasdeinformatica.blogspot.com.es/2014/01/las-mejores-inyecciones-son-chorizos-y.html

Related Posts Plugin for WordPress, Blogger...