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

viernes, 12 de junio de 2015

Segundo portlet con Liferay: listar una tabla (parte 2 de 2)

Bueno, por diversos motivos he tenido que estar parado un tiempo sin poder actualizar el blog, lo siento mucho. Así que voy a ver si ahora puedo poco a poco volver a la normalidad y hacer las entradas de forma más frecuente.

Continuamos con la creación de un portlet que consulta a una BD Oracle y nos muestra los datos en una simple tabla HTML.

Enlace a la primera parte.

Recordemos que lo último que habíamos hecho era incluir el driver de Oracle en nuestro proyecto, así que ya podemos pasar a establecer la conexión con la BD.

5. Establecer la conexión

Para crear la conexión, necesitamos un objeto de la clase Connection. Esta clase se encuentra en el paquete java.sql, así que añadiremos estas líneas al código:

    import java.sql.Connection;
    ...
   
    //conectar
    Connection conOracle = null;
    conOracle = this.getConexionOracle();


También necesitaremos otras clases de este paquete, así que las añadiremos:

    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.sql.ResultSet;


Una opción más cómoda sería haber importado todo el paquete java.sql con una única instrucción, así:

    import java.sql.*;
   
Pero yo prefiero especificar individualmente cada clase (si fueran muchísimas no me complicaría la vida e importaría todo el paquete, pero en este caso son pocas). De esta manera, hacemos explícito el conocimiento de las clases que estamos importando.

Para conectar con un servidor Oracle, necesitamos los siguientes datos: servidor (nombre o dirección IP), puerto (por defecto, 1521), nombre del esquema, usuario y la clave. Si tenéis claro todo esto, el método para obtener la conexión quedaría algo así (he borrado los datos específicos de mi instalación, naturalmente):

    private Connection getConexionOracle() {
        String
            servidor = "xx",
            puerto   = "1521",
            esquema  = "xx",
            passBD = "xx",
            usuario = "xx";
        String cadenaBD = "jdbc:oracle:thin:@" + servidor + ":" + puerto + ":" + esquema;
        Connection conOracle = null;
        try {
            DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver());
                conOracle = java.sql.DriverManager.getConnection(cadenaBD, usuario, passBD);           
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("Error en getConexionOracle() : " + e.getMessage());
        }
        return conOracle;
    }


OJO: esto de poner en el propio código Java los datos de la conexión probablemente no es la forma más adecuada de hacerlo. De momento lo dejaremos así por simplicidad de comprensión, pero no es lo más recomendable. Sería mejor configurar la conexión a nivel del contenedor de portlets (Tomcat en nuestro caso, Javaboss... ) y desde el código Java obtener la conexión.

6. Realizar la consulta

Una vez obtenida la conexión, ya está hecho lo más difícil. Ahora tan sólo nos queda realizar la consulta, lo cual podemos hacer con el código que se ve a continuación

    //recuperar datos
    Statement orclStmt = conOracle.createStatement();
    String sql = "select campo1, campo2 from tabla order by campo1 ";
    orclResult = orclStmt.executeQuery(sql);


Como vemos, se utiliza el método createStatement del objeto conOracle y el objeto Statement devuelto nos permite ejecutar la consulta correspondiente.

El objeto oraclResult es de tipo java.sql.ResultSet. Como véis, no lo he declarado en estas líneas, y eso se debe a que voy a poner todo este código dentro de un bucle try/catch, ya que los métodos createStatement y executeQuery pueden lanzar algunas excepciones y debemos atraparlas. Por ello, declararé las variables orclResult y la variable conOracle antes de este bloque, así:

public String recuperarTablaHTML() {
    String res = "<table border=1>";
   
    Connection conOracle = null;
    ResultSet orclResult = null;
    try {
        //conectar
        conOracle = this.getConexionOracle();

        //recuperar datos
        Statement orclStmt = conOracle.createStatement();
        String sql = "select campo1, campo2 from tabla1 order by campo1 ";
        orclResult = orclStmt.executeQuery(sql);


Nuestro código va tomando forma.

7. El bucle de procesamiento

Una vez obtenido el resultado, ya sólo nos queda ir recorriendo el conjunto de registros del resultado. Algo que podemos hacer así:

    while (orclResult.next()) {
        String campo1 = orclResult.getString("campo1");
        String campo2 = orclResult.getString("campo2");
        res += "<tr>";
        res += "<td>" + campo1 + "</td>";
        res += "<td>" + campo2 + "</td>";
        res += "</tr>";
        this.numResultados++;
    }


También hemos introducido en el bucle un contador del número de registros procesados, como puede verse en la última línea dentro del bucle.


8. Mostrar los resultados

Bueno, pues hecho todo lo anterior, sólo nos queda regresar al fichero view.jsp y añadir el código para mostrar el resultado.

Fig. 17. Mostrando los resultados en la vista del portlet

Ahora nos vamos al portal y comprobamos si todo funciona como debería:


Fig. 18. Ejecutando el portlet en nuestro portal


Bueno, parece que sí. Así que ya podemos dar por terminada esta entrada.

miércoles, 22 de abril de 2015

Segundo portlet con Liferay: listar una tabla (parte 1 de 2)

Hasta ahora hemos visto cómo crear un portlet muy básico que básicamente mostraba un mensaje en la página y alguna información del usuario que ha iniciado la sesión en el portal.

Vamos ahora a crear un nuevo portlet con un comportamiento un pelín más interesante, que acceda a una base de datos (Oracle, en este caso) y que muestre los datos de una consulta sobre una tabla. Lo llamaremos "ListarTabla" (olé ahí, derroche de imaginación eligiendo nombres ;-). La tabla que utilizaremos tiene esta estructura:

CREATE TABLE TABLA1
(
   ID varchar2(10) PRIMARY KEY NOT NULL,
   CAMPO1 varchar2(100),
   CAMPO2 varchar2(100)
);

Y estos datos:

INSERT INTO tabla1 VALUES(1, 'ENCARGADO DEL BOCADILLO', 'PEPE GOTERA')
INSERT INTO tabla1 VALUES(2, 'PROFESORA DE BAILE', 'JESSICA ALBA')
INSERT INTO tabla1 VALUES(3, 'ASESOR DE IMAGEN', 'LA PANTOJA DE PUERTO RICO')
INSERT INTO tabla1 VALUES(4, 'CONFESOR', 'COMISARIO TORRENTE')

Comencemos, pues.

1. Crear el portlet

Si no sabes cómo crear un portlet, a lo mejor te es útil echar antes un vistazo a esta entrada: Primer portlet con Eclipse y Liferay.

Utilizaremos el botón de Liferay de la barra de herramientas, y dentro de él la opción "New Liferay Plugin Project"
Fig 1. Creando el portlet "Listar Tabla"

Dejamos las opciones por defecto

Fig. 2. Trabajaremos con el framework MVC

Y tras pulsar Finish se nos debería haber creado la siguiente estructura de carpetas y ficheros

Fig. 3. Estructura de ficheros para el portlet "Listar Tabla"

2. Mostrar datos del usuario y preparar el sitio donde mostrar los resultados

Aunque esto no es necesario, quiero mostrar la información del usuario, como describimos en una entrada anterior, Objetos implícitos en portlets Liferay, así que añadiré el código correspondiente al fichero view.jsp. También añadiremos el sitio donde se mostrarán los datos procedentes de la consulta.

Fig. 4. Preparando el espacio donde se mostrará el resultado de la consulta (línea 30)

3. Crear una clase para acceder a los datos


Ahora viene lo interesante. Para facilitar la legibilidad del código de view.jsp, delegaremos la responsabilidad de acceder a los datos y convertirlos en una tabla HTML a una clase Java que posteriormente invocaremos desde esta página. Así, el fichero view.jsp se limitará a invocar las funciones de esta clase y su código será bastante reducido y limpio.

Esta clase, por cuestiones de organización, será conveniente que esté en un paquete específico. Si no se especifica un paquete concreto, la clase pertenece al paquete por defecto (default), cosa que no se recomienda.


Fig. 5. El uso del paquete 'default' no se recomienda

Así que lo mejor será que creemos antes el paquete que la contendrá. Le pondremos de nombre "accesoDatos" (Eclipse recomienda que los paquetes empiecen con minúscula, aunque esto es más bien una convención Java, no un requisito imprescindible). Para crear el paquete, nos vamos al menú File / New / Package


Fig. 6. Nuevo paquete

Le damos el nombre que hemos dicho, y le indicamos a Eclipse que la carpeta que contendrá los fuentes colgará de WEB-INF/src

Fig. 7. Creando el paquete "accesoDatos"

Tras esto, ya podemos ver en el Package Explorer que el nuevo paquete se encuentra en nuestro proyecto.

Fig. 8. El paquete recién creado

Ahora ya podemos crear la nueva clase dentro de ese paquete. Para ello, hacemos clic con el botón derecho sobre el paquete en el Package Explorer y seleccionamos New / Class


Fig. 9. Creando la nueva clase

Le pondremos de nombre "GestorDatos"

Fig. 10. La clase GestorDatos dentro del paquete accesoDatos

Para este ejemplo, utilizaremos una técnica que podríamos denominar "artesanal". Básicamente, consistirá en que el Gestor de Datos realizará una conexión a la base de datos, recuperará los registros que nos interesan, y a continuación los irá recorriendo en un bucle, construyendo una tabla HTML que irá almacenando en una cadena de texto. Esta cadena será la que devuelva al código invocante (el de view.jsp), que lo único que hará será mostrar esta tabla sin más.

Como digo, este método es muy primitivo, no se utilizan plantillas ni controles de interfaz de usuario, no se paginan los resultados, ni se pueden ordenar, ni ná de ná... Simplemente, se recuperan datos y se muestran. Pero esto es suficiente para ilustrar sin mayores complicaciones el proceso básico de conectarse a una base de datos y hacer 'algo' con los datos recuperados. Es mejor ir dando pequeños pasos, así nos concentramos en un aspecto cada vez.

Bueno, pues manos a la obra. La clase GestorDatos va a contener un único método público que será el que devuelva la tabla HTML. Su esquema será algo así:


Fig. 11. Estructura del método que devolverá la tabla HTML

Las acciones de conectar y recuperar datos se codificarán en dos métodos privados de esta clase, que servirán de apoyo al método público. Además, definiremos también un método público para devolver el número de registros recuperados (con la correspondiente variable privada para almacenar este dato).

4. El driver de Oracle

Como dije, voy a sacar los datos de una BD Oracle. Por eso, necesito el driver de Oracle, que descargaremos de la web oficial (http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html). Imagino que tendréis que registraros con un email válido, en su momento yo tuve que hacerlo. Este driver es un fichero de nombre ojdbcXX.jar. El XX depende de la versión que os bajéis, lo cual, a su vez, depende de la versión del sistema operativo y de la versión del motor de la base de datos. Si no tenéis clara la versión del motor de Oracle, aquí podéis ver cómo se averigua. En mi caso, el fichero se llama ojdbc141.jar.

Para que este driver esté disponible en nuestro proyecto, hay que incluir la referencia en el subdirectorio WEB-INF/lib. Para ello, podemos hacer clic con el botón derecho del ratón sobre la carpeta "lib" que cuelga de WEB-INF y luego elegir "Import..." del menú contextual.


Fig. 12. Vamos a añadir el fichero JAR con el driver de Oracle

Para elegir el origen de la importanción desplegamos "General" y dentro de ella elegimos "File System" y pulsamos Next.

Fig. 13. Vamos a seleccionar un fichero del disco

Y en el siguiente diálogo, le indicamos la ruta donde tenemos ese fichero. Nos dará la opción de elegir qué ficheros de la carpeta seleccionada queremos importar. En este caso, sólo he marcado la biblioteca con el driver de acceso a Oracle, como se ve en la figura.

Fig. 14. Importamos solamente el fichero con el driver de Oracle

Tras pulsar Finish, podemos comprobar que ya debe aparecer en el árbol de nuestro proyecto esa biblioteca colgando del subdirectorio WEB-INF/lib

Fig. 15. La biblioteca con el driver de Oracle ya está referenciada en nuestro proyecto

Lo que ha hecho Eclipse internamente ha sido simplemente copiar el fichero indicado dentro del subdirectorio correspondiente, lo que podemos ver si con un explorador de archivos del sistema operativo echamos un vistazo a la carpeta WEB-INF/lib. Allí aparece el fichero que hace un momento no estaba. Por eso, otra forma más rudimentaria de importar el fichero en nuestro proyecto podría haber sido copiar manualmente este fichero.


Fig. 16. La biblioteca importada se ha copiado a la carpeta lib de nuestro proyecto


Bien, hasta aquí la primera parte de esta entrada. En la siguiente entrada completaremos la tarea.

lunes, 9 de marzo de 2015

Buscando desesperadamente a Susan... digoooo... un gestor de BD Oracle

Resulta que últimamente en el curro tengo que lidiar con bases de datos Oracle, cosa que había hecho hasta ahora muy poco (algún contacto puntual sin mayor trascendencia, algún cursillo básico enseñando SQL básico sobre Oracle y... poco más).

Pero ahora se trata de trabajar a diario con algunas BD Oracle. Para ello, el primer paso es proveerme de una buena herramienta de administración de Oracle. Hasta hace unas semanas, la única herramienta que conocía era TOAD que, a pesar de haberla utilizado de forma esporádica, hay que reconocerle que es muuuuuy, pero que muuuuy completa. Tiene una cantidad de características impresionante. Sólo tiene una pega... es de pago. No obstante, si alguien quiere probarla, podéis descargar una versión de evaluación por 30 días.
http://software.dell.com/products/toad-for-oracle/software-downloads.aspx

Si tu organización puede permitírselo, mi recomendación es comprarla. Me parece muy bien pagar por un buen software (ahora bien, no he mirado el precio, así que no sé si se pasan o no).

Sin embargo, en mi caso esto no era una opción. Así que yo busco una aplicación gratuita (si ya fuera libre, miel sobre hojuelas, pero tampoco me obsesiona esto). Tras el habitual proceso de googleo, investigación, descargas, instalaciones, configuración y desinstalaciones, me he quedado con tres candidatas. Bueno, quedado no es la palabra, ya que aún no me he decantado por una:

  • TORA [http://torasql.com/]: tiene buena pinta "la publi", pero, lamentablemente, no he conseguido hacerla funcionar. Al parecer, no soy el único, parece que no es tan fácil hacerla funcionar [http://sourceforge.net/p/tora/discussion/52737/thread/bc088bb4/]. Será cuestión de dedicarle más tiempo. Pero cuando le has dedicado un rato y no le has sacado punta, la verdad es que te aburres y te apetece dejarla y probar otra. Probablemente vuelva a dedicarle algún rato más, ya veremos. Si alguien ha conseguido hacerla funcionar en un Windows7 64 bits, por favor, que me diga cómo.



- SQuirreL SQL [http://squirrel-sql.sourceforge.net/]. Esta he conseguido hacerla funcionar rápidamente, y tiene buena pinta. Aún no la he probado mucho, pero ya veremos. Está hecha en Java, lo que se le nota un poco en rendimiento (algunas exportaciones de tablas gordas han sido un poco lentas), pero tampoco pienso andar moviendo grandes bloques de datos frecuentemente, así que probablemente me valga. Una ventaja es que sólo ocupa 56MB, que no es una barbaridad para los tiempos que corren. Como podéis ver en el menú contextual de la imagen, un montón de opciones para exportar a CSV, Excel, HTML... y otras cosas útiles.


- Y por último, SQL Developer. Esta herramienta, recomendada por un compañero, la proporciona Oracle gratuitamente [http://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html] y también va sobre la JVM, así que (los prejuicios pesan mucho, pero es que cuando son fundados...) será cuestión de evaluarla y ver qué tal. Un poco mamotreto sí que es (casi 500MB una vez descomprimida), pero mi intención es probarla.



Bueno, ¿hay alguna herramienta que conozcáis y que me recomendariais en lugar de estas? Cualquier información será muy bienvenida.

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

martes, 14 de enero de 2014

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

Bien es verdad que hace casi 4 meses que no escribo en este blog, pero este año me he propuesto escribir un poco más que el pasado. Cosa que no será muy difícil, ya que el año pasado sólo escribí cinco entradas. De momento, he empezado con una serie que me dará para varias entradas.

martes, 19 de febrero de 2013

Harry Potter y el increíble poder de las "no transacciones" o Cómo hacer inserciones masivas en SQLite3 sin pérdida de rendimiento

Andaba yo preocupado porque necesitaba hacer una copia local de unas tablas desde un servidor Informix a una BD local SQLite3. Todo esto desde una aplicación web hecha en PHP.

Ni corto ni perezoso, me lancé a la tarea. Mi primera idea fue hacer una consulta a la BD Informix y luego procesar cada registro, componer la correspondiente instrucción SQL INSERT y ejecutarla. Algo así


 1     $sql="SELECT * FROM $tabla where " . $where ;
 2     $rs=odbc_exec($conn, $sql); if (!$rs) {exit("$modname: Error en SQL [$sql]");}
 3  ...
 4  while (odbc_fetch_row($rs)) {
 5   ...
 6   $sql="insert into " . $tabla . "(" ...
 7   $res = $db_sqlite3->exec($sql);
 8   ...
 9  }//while

Pero me decepcioné cuando vi lo que se tardaba en hacer la copia

 Como véis, el primer bloque son 2.628 registros, y tarda 34"
El segundo bloque, 50.446 registros, tardó 11' 13", o sea, 673"

Fatal. Y eso que sólo estaba sacando una parte de los registros de la tabla. La tabla original tenía casi 90.000 registros, pero es que además necesitaba copiar otras tablas más grandes, incluso una de ellas con 8 millones de registros. Y a este paso, la copia iba a terminar para mi fecha de jubilación, aproximadamente.

La cosa quedó ahí aparcada un tiempo, y me desvié hacia otras cuestiones. El problema lo resolví de otra manera, pero me quedó la espinita clavada. ¿Por qué se tardaba tanto en hacer una copia, si yo había leído que SQLite3 era rapidísima? Estaba claro que no era cosa del Informix, ya que hacer una copia a un MDB de Access era muchísimo más rápido.

Y ahora llega el momento en el que entra mi gran amigo Miguel en acción. El otro día vino a verme al curro, y, mientras nos tomábamos un café, surgió el tema de SQLite3 y le conté mi problema.

Un par de horas después tenía un correo suyo en mi buzón, con enlaces a estas dos estupendas entradas:

Hola Jose

Tu problema de lentitud no parece que sea SOLO de indices. Como las BD de SQLite son ficheros hay otro problema añadido de bloqueo de ficheros.

Mira estos enlaces:

http://tech.vg.no/2011/04/04/speeding-up-sqlite-insert-operations/
http://blog.quibb.org/2010/08/fast-bulk-inserts-into-sqlite/

Después de leerlos, he comprendido la causa del problema. Básicamente, la cosa es que cada INSERT que se ejecuta dentro del bucle consiste en una transacción. SQLite3 se asegura de que se consolida en disco antes de dar por terminada la operación. Lo que se necesita en casos como este es ejecutar TODAS las instrucciones en una única transacción, y no cada una por separado. Esto es lo principal. Aparte, se pueden parametrizar ciertos valores en la BD para ayudar a mejorar el rendimiento:


1) Desactivar el modo de funcionamiento síncrono de SQLite3
 1 $db_sqlite3->exec("PRAGMA synchronous=OFF");

2) Iniciar una transacción justo antes del bucle, y cerrarla al salir

 1     $sql="SELECT * FROM $tabla where " . $where ;
 2     $rs=odbc_exec($conn, $sql); if (!$rs) {exit("$modname: Error en SQL [$sql]");}
 3  ...
 4  $db_sqlite3->exec("BEGIN TRANSACTION");
 5  while (odbc_fetch_row($rs)) {
 6   ...
 7   $sql="insert into " . $tabla . "(" ...
 8   $res = $db_sqlite3->exec($sql);
 9   ...
10  }//while
11  $db_sqlite3->exec("COMMIT TRANSACTION");

3) Otros PRAGMA para mejorar el rendimiento

 1  $db_sqlite3->exec("PRAGMA count_changes=OFF");
 2  $db_sqlite3->exec("PRAGMA journal_mode=MEMORY");
 3  $db_sqlite3->exec("PRAGMA temp_store=MEMORY");

4) Utilizar sentencias precompiladas (prepared statements)

Yo empleé en primer lugar la técnica 1) y el resultado en ese caso fue prometedor:


En este caso, el primer bloque tarda 19", frente a los 34" del primer caso. O sea, una mejora del 44%. Vamos bien.
El segundo bloque tardó 5' 41", o sea, 341", frente a los 673" del primer caso. O sea, una mejora del 49%. Bien, muy bien.

Pero la gran mejora vino al hacer lo segundo, y que he mencionado antes: abrir una transacción antes de entrar en el bucle, y cerrarla al salir. Resultados:



Primer bloque: 1" ¡Impresionante! Una mejora del 97%
Segundo bloque: 7". Una mejora del 99%. ¿Es para flipar o no lo es?

La opción 3) (otros PRAGMAS) también la probé, pero no hay variaciones significativas.

Y como no os lo voy a dar todo hecho, el tema de probar con las sentencias preparadas os lo dejo para que lo probéis vosotros.

Por supuesto, me encantaría oír vuestros comentarios.
Related Posts Plugin for WordPress, Blogger...