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

jueves, 23 de junio de 2016

Sesión de galletitas y galletitas de sesiones

Ayer me surgió un problemilla relacionado con sesiones y cookies, y algún pequeño misterio (aún) sin resolver. A ver si consigo explicar un poco el tema de las cookies y las sesiones para mi yo del futuro, y a ver también si alguien me puede dar una pista acerca del misterio.

1. Sesiones

Una sesión es una forma de mantener el estado utilizando un protocolo como HTTP que, por construcción, no está pensado para mantener el estado. Normalmente, una sesión dura desde que el usuario abre el navegador hasta que lo cierra. El servidor web puede pasar información entre diferentes páginas durante la misma sesión. Cuando el navegador solicita la página (esa que va a trabajar con sesiones), el servidor web le asigna un identificador de sesión (SESSID), se lo envía al cliente y le pide que lo guarde "en algún sitio" (normalmente, en una cookie). Después, cada vez que el usuario haga una petición de página, el servidor web recibirá ese SESSID en una cookie (eso es lo habitual, aunque también puede enviarse en la propia URL de acceso). Conservando cierta información ligada al identificador de la sesión (en el servidor web), puede darse un servicio personalizado al usuario, como mantener un carrito de la compra, por ejemplo. Para mantener esta información, el desarrollador web puede almacenar determinadas variables (nombre:valor) en la sesión.

El servidor web guardará la información de la sesión en algún sitio, puede ser en ficheros de texto, binarios, en una base de datos... Normalmente, una configuración por defecto de Apache + PHP implica que se guarden en ficheros de texto plano.

Vale. Hasta aquí bien.

2. Cookies

Para guardar en el navegador del cliente el ID de sesión se utilizan cookies habitualmente. El servidor web le pide al navegador del usuario: "Anda, porfa, ¿puedes guardar por ahí en algún sitio que la variable <loquesea> almacene el valor <patatín-patatán>? Más adelante, cuando vuelvas, yo te pediré si tienes esta información guardada y, si la tienes, te reconoceré y sabré que eres tú, mi amor" (sí, los servidores web son así de ligones con los navegadores).

Los servidores pueden usar así esta información para "recordar" las preferencias del usuario, su login automático y un sinfín de cosas más útiles y beneficiosas para el usuario, y algunas otras que no son tan beneficiosas para el usuario (pero sí para ciertos sitios web, especialmente si venden publicidad o si tienen intenciones algo más siniestras), por ejemplo, para rastrear las preferencias y hábitos de navegación.

Los navegadores o, lo que es lo mismo, los usuarios, sabemos que si nos hacemos los estrechos la página no nos va a dar mucha funcionalidad, así que nos ponemos en modo facilón, nos dejamos querer y accedemos fácilmente a que el servidor web nos penetr meta esos datos sin excesivos reparos. (¿Cuántas veces has "Entendido" que un sitio web utiliza cookies y has consentido? Luego no vayamos con excusas, que así pasa lo que pasa).

El navegador guarda ese paquetito de información, esa cookie, en un fichero pequeño normalmente de texto (también pueden ser binarios) o, algo que se ve cada vez más en las últimas versiones de los navegadores, en bases de datos locales tipo SQLite o similares. En cualquier caso, ficheros en el ordenador del cliente web. O, al menos esto es así en teoría, ya que aquí me surge el primer misterio que mencionaba en la introducción. Pero antes de abordar el misterio, recapitulemos lo que llevamos dicho hasta ahora.


Resumiendo:

- Cookies: pequeños trozos de información que almacena el navegador en ficheros de texto, binarios o BD locales tipo SQLite. Se pueden usar para muchas cosas, una de ellas para almacenar ID de sesión (pero también para rastrear al usuario, conocer preferencias...)

- Sesiones: interacciones entre navegador y servidor web que recuerdan info (estado) del usuario, normalmente usando cookies. La información se almacena en el servidor, ya sea en ficheros de texto (pares nombre:valor), binarios, bases de datos...

3. Vale. ¿Y dónde se almacenan?

3.1 LAS COOKIES

Pues... depende del navegador. En el caso de Firefox, el directorio es el del perfil del usuario, como explican aquí

https://support.mozilla.org/es/kb/perfiles-el-lugar-donde-firefox-almacena-tus-contr

que se encuentra dentro de la carpeta %AppData%\Roaming\Mozilla\Firefox\Profiles

Si sólo tienes un perfil de usuario, éste incluye la palabra 'default' en el nombre de la carpeta. En mi caso:

%AppData%\Roaming\Mozilla\Firefox\Profiles\67bl3kpn.default-1443683694326

y, dentro de esta carpeta, hay un archivo cookies.sqlite


Fig. 1. Archivo SQLite de cookies en Firefox

El contenido de este archivo se puede ver con cualquier visualizador de ficheros SQLite. A mí me gusta mucho el SQLite Expert (edición Personal). Más adelante le echaremos un vistazo.

En el caso de Chrome, la ruta donde se almacena la BD de cookies es

%LOCALAPPDATA%\Google\Chrome\User Data\Default\Cookies

y el fichero se llama cookies a secas, sin extensión. También lo podemos abrir con SQLite Expert o cualquier otro visor.


Si echamos un vistazo a ambos ficheros, veremos cosas parecidas a esto en el caso de Firefox


Fig.2. Fichero de cookies en Firefox 47.0

y esto en el caso de Chrome



Fig.3. Fichero de cookies en Chrome

3.2 LAS SESIONES

La información de la sesión se almacena, como hemos dicho, en el servidor web. En el caso de Apache con PHP, que es el servidor que yo voy a utilizar en este caso, la ubicación de las variables de sesión se almacenan en el directorio que se especifique en php.ini, en el parámetro session.save_path. En mi caso, utilizando un paquete XAMPP integrado, la ruta es C:\xampp\tmp. 

 Fig.4. Configuración de la ruta donde guardar las sesiones

Si miramos en esa carpeta, veremos las sesiones que se han ido creando en los últimos días:




Fig.5. Contenido de la carpeta de almacenamiento de sesiones

4. ¿Me puedes poner un ejemplo?

Sí, será lo mejor. Todo esto puede ser un poco confuso, y la mejor forma de verlo claro es con un ejemplo. Haremos dos páginas PHP que se pasarán información de una a otra utilizando una sesión de usuario, e iremos viendo dónde, cómo y con qué contenido se van creando los ficheros.

Página 1


01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones - Página 1</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />'
15 . 'Nombre: ' . session_name() . '<br />';
16
17 //almacenamos algún dato en esa sesión
18 $_SESSION['mes'] = 'junio';
19 $_SESSION['usuario'] = 'pepe';
20 $_SESSION['flor'] = 'amapola';
21
22 echo '<pre>';
23 print_r($_SESSION);
24 echo '</pre>';
25 echo "<br>Ir a <a href='pagina2.php'>la página 2</a> a consultar las variables de sesión"
26
27 ?>
28
29 </body>
30 </html>



Y esta es la página 2


01 <?php
02 session_start();
03 ?>
04 <!DOCTYPE html>
05 <html lang="es">
06
07 <head>
08 <meta charset="utf-8" />
09 <title>Sesiones</title>
10 </head>
11
12 <body>
13 <?php
14 echo 'Estamos en la sesión [' . session_id() . ']<br />Nombre: ' . session_name() . '<br />';
15
16 //recuperamos los valores almacenados en la sesión
17 echo 'Recuperamos el valor mes=' . $_SESSION['mes'] . '<br />';
18 echo 'Recuperamos el valor usuario=' . $_SESSION['usuario'] . '<br />';
19 echo 'Recuperamos el valor flor=' . $_SESSION['flor'] . '<br />';
20
21 echo '<pre>';
22 print_r($_SESSION);
23 echo '</pre>';
24 ?>
25 </body>
26 </html>



Abramos en el navegador la página 1

 Fig. 6. Página 1, creando la sesión y almacenando algunas variables en ella.

Antes de pasar a la página 2, veamos qué ha pasado en los sistemas de archivos del servidor y del cliente.

En el servidor

Fig. 7. Fichero de sesión en el sistema de archivos del servidor web

como se ve, el nombre del archivo coincide con el session_id que hemos visto en la página. Si abrimos ese fichero, su contenido es:


Fig. 8. Contenido del fichero de sesión con las variables de la misma

Hmmm... Interesante. Vemos que junto a cada variable aparece un tipo de datos (s=string), su tamaño (5) y su valor ("junio"). Las distintas variables de sesión se separan con pipelines.


Pero es a la hora de ver qué ha pasado con las cookies en el cliente cuando aparece el primer "misterio". En teoría, las cookies se almacenan en la BD SQLite que hemos dicho. Pero, cuando muestro las cookies ordenadas por fecha de acceso, no encuentro la cookie de nombre PHPSESSID, que es el nombre que me devuelve la función session_name(). Esto es lo que consigo ver cuando accedo a la BD y filtro por el dominio 'localhost':

Fig. 9. Contenido del fichero de cookies para el dominio 'localhost' en Firefox. 
¿Y la cookie PHPSESSID?

Como tras muchos intentos no he conseguido encontrar esta información en la BD, al final he instalado el plugin Firebug en Firefox, que permite visualizar las cookies y ahí sí que puedo ver la cookie que me interesa, como se puede ver un poco más arriba en la figura 6. También si abro la página 2 con el Firebug activado (F12) puedo ver que la cookie de sesión PHPSESSID es la misma

Fig. 10. Página 2 mostrando la cookie de sesión y las variables asociadas, junto a sus valores

Aún no he conseguido averiguar dónde se almacena la cookie PHPSESSID, pero es obvio que el navegador la está guardando en algún sitio y a mí me gustaría saber dónde. Vale que con el Firebug puedo inspeccionar la cookie, lo cual me soluciona la papeleta por el momento, pero me quedo un poco mosca con no haber podido encontrar la ubicación exacta de la cookie en el disco.

¿Alguien tiene alguna idea?

viernes, 9 de octubre de 2015

Enviar mensajes de Telegram desde una aplicación web

Seguro que muchos conocéis Telegram, esa aplicación de mensajería instantánea que intenta hacerle la competencia a WhatsApp, aunque de momento, todo sea dicho, no le llega ni a la suela de los zapatos en cuanto a popularidad, y esto a pesar de tener algunas características bastante superiores. Pero bueno, la historia de la tecnología está llena de ejemplos en los que un producto técnicamente superior se ve desbancado por otro técnicamente inferior, y es que no sólo los detalles técnicos pesan. Sin embargo, leo que Telegram va creciendo gradualmente, así que espero que algún día la mayoría de mis contactos se hayan mudado de WhatsApp a Telegram. Mientras tanto, a seguir con las dos aplicaciones

No quiero entrar en los detalles técnicos ahora, y la evangelización sólo la hago los domingos, así que mejor os remito a esta entrada en hipertextual.com donde explican razonablemente bien las diferencias más importantes entre ambas aplicaciones. Lectura muy recomendable.

http://hipertextual.com/2015/06/telegram-vs-whastapp

¿Ya la habéis leído? Bien. Ahora, vamos al turrón.

1. Los bots de Telegram

El 24 de junio de este año Telegram lanzó una característica que no he podido investigar hasta hace tan solo unos días (muy por encima, eso sí, aunque suficiente para lo que yo quiero hacer): los bots. [https://telegram.org/blog/bot-revolution]

Fig1. Los bots de telegram: una innovación muy interesante

Estos bots son cuentas de Telegram detrás de las cuáles no hay una persona, sino un programa, y sin necesidad de un número de teléfono asociado. Y son ideales para muchas cosas. Entre ellas, para que tus programas te puedan mandar mensajes al móvil, por ejemplo, aunque según parece esta es la menor de las utilidades que tienen. Pero es la que hoy me interesa.

El proceso básicamente consiste en tres pasos: 1) crear el bot, 2) chatear (unirse) a él todos aquellos que quieran recibir los mensajes y 3) enviar y recibir mensajes como si no hubiera un mañana

Ok, pues manos a la obra.

2. Crear un bot


Para esto se utiliza el BotFather, el padrino-de-los-bots (confío en que no habrá que explicar el juego de palabras. Si no, dejad de leer y sacad de la biblioteca la serie de películas de "El Padrino").

Simplemente, hay que entrar en la dirección [https://telegram.me/botfather]
y seguir las instrucciones que nos va dando.

Veremos un mensaje tal que así


Fig. 2. El padrino nos recibe en su casa


Podemos interactuar con el BotFather "chateando" directamente, teniendo en cuenta que las órdenes comienzan con una barra /, o bien podemos usar el botón que os he señalado con una flecha en la figura 2.

Creamos el bot con /newbot y le damos un nombre


Fig. 3. El bot recién creado, de nombre "Cosicas"

A continuación le damos un nombre de usuario, que termine en "bot". Esto nos devolverá un token que necesitaremos pasar en las invocaciones al bot, para poder enviar mensajes. Lo copiamos y lo guardamos en un lugar seguro. Lo necesitaremos un poco más adelante.


Fig. 4. Al darle el nombre de usuario recibimos un token



El bot posee una serie de métodos que podemos invocar a través de la URL

https://api.telegram.org/bot<token>/METHOD_NAME

Donde METHOD_NAME puede ser uno de los métodos que podéis consultar en la referencia de la API (aquí [https://core.telegram.org/bots/api]).

Por ejemplo, invocando al método getMe podemos obtener información básica del bot, entre ella el ID que lo identifica:

https://api.telegram.org/bot<token>/getMe

En mi caso, esto:


Fig. 5. Uso del método getMe para extraer el ID de nuestro bot


3. Chatear con el bot


A continuación debemos unirnos a la "conversación" con nuestro bot. Para esto, pinchamos en el enlace que nos ha dicho el BotFather en la figura 4. Es un enlace del tipo telegram.me/CosicasBot (que a su vez redirige a https://web.telegram.org/#/im?p=@CosicasBot). Allí veremos un botón que nos servirá para iniciar nuestra conversación con el bot.


Fig. 6. Accediendo a la "conversación" con el bot

Pulsamos en INICIAR y



Fig. 7. Ya estamos conectados a nuestro bot. Listos para enviar mensajes.


Al iniciar el chat con el bot, ya podemos obtener nuestro identificador para que nuestro programa pueda enviarnos mensajes. El chat_id identifica a nuestro usuario en Telegram, y el bot necesita saber este chat_id para poder enviarnos mensajes. En una reciente actualización he leído que también se puede hacer con el nombre del usuario. Yo voy a explicarlo con el chat_id.

¿Y cómo obtenemos ese chat_id? Un pequeño truco que he visto es invocar al método getUpdates de nuestro bot, tal y como describimos en el apartado anterior. Eso debería producir una respuesta en formato JSON que, entre otras cosas, lleva nuestro chat_id. Es decir, abrimos un navegador y tecleamos la URL siguiente:

https://api.telegram.org/bot<token>/getUpdates

Y deberíamos ver algo así:

 
Fig. 8. Forma "chapucerilla" de obtener el chat_id


En el recuadro rojo tenéis el chat_id.

4. Enviar un mensaje

Bueno, pues parece que ya lo tenemos todo. Ahora ya podemos crear una página PHP sencilla que envíe mensajes al bot. Sería algo tan simple como:



Fig. 9. Código para enviar un mensaje al bot

Por supuesto, no es necesario que sea PHP, puede estar escrita en cualquier lenguaje. Lo necesario es simplemente invocar la URL que se construye en la línea 15.

Por último, abro ese fichero PHP en mi servidor web y me voy al Telegram en mi móvil, et voilà!


Fig. 10. Mi PHP enviándome mensajes al móvil, ¡qué tierno!


Bueno, pues ahí tenéis una forma rápida de enviar mensajes al móvil vía Telegram desde vuestros programas. Pero ojo a los bots, que parece que tienen un gran potencial mucho más allá de esto.

Por cierto, el bot que he usado de ejemplo voy a cargármelo ahora mismo, que no quiero que nadie empiece a mandarme basura. Quien quiera probarlo, que se cree el suyo propio.



Fig. 11. Borrando el bot de este ejemplo

Bueno, seguramente habré cometido varios errores en este artículo, pero acabo de empezar a pelearme con los bots (tampoco creo que les dedique mucho más, ya tengo lo que quería) y seguro que algunas cosas las iré aprendiendo mejor en los próximos días.

Y por hoy, basta de programación.

martes, 26 de agosto de 2014

Fotos públicas en Endomondo

Desde principios de año tenía por ahí aparcado y pendiente este tema del que voy a hablar hoy. Hace unas semanas, Chema Alonso publicó esta entrada hablando acerca de algunos problemas de privacidad en Endomondo (lectura muy recomendada), así que me he decidido a publicar lo que yo había experimentado, que viene a ser un dato más que añadir a lo que él cuenta allí. Como dicen los abogados: "...a mayor abundamiento..."

El caso es que cuando salgo a correr o a pedalear, suelo utilizar la aplicación Endomondo para registrar mis "proezas". Me gusta esta aplicación, ya que luego puedo entrar en la página web y revisar mis prácticas, exportar el fichero GPX con la ruta, ver estadísticas, etc...



Una de mis "proezas". Como véis, estoy hecho un espartano (¡aug!).

Pues bien, me encontraba yo revisando mis prácticas en el navegador cuando se me ocurrió hacer clic en mi imagen de perfil. Esto hizo que se me abriera una ventana aparte con la imagen



Sí, es la misma foto que tengo en mi perfil de Google+, y en varios sitios más. Original que es uno

Hmmm... Supongo que ya os habréis dado cuenta, ¿no? Aquí hay algo que pinta raro. ¿Las imágenes tienen una URL fija? No se accede a ellas a través de un script que valide la sesión del usuario. A ver, voy a cambiar algún dígito de la URL de mi foto de perfil, a ver qué sale...



Vaya, un vecino de URL

Parece ser que Endomondo no exige nada para que puedas ver la foto de perfil más allá de saberte la URL. Siendo esto así, a cualquier novato con un conocimiento mínimo de programación no le costaría nada hacer un pequeño script que con wget se fuera descargando imágenes, una tras otra, "a ver qué sale". Sería más o menos algo como esto:


 1 #!/bin/bash
 2 
 3 desde=1
 4 hasta=100
 5 ruta1=http://image.endomondo.com/resources/gfx/picture/
 6 ruta2=/big.jpg
 7 
 8 i=$desde
 9 while [ $i -le $hasta ]; do
10     echo "descargando $ruta1$i$ruta2"
11     wget $ruta1$i$ruta2 -O big.$i.jpg
12     let i=$i+1
13 done 
14 
El script para Linux. La adaptación a Windows es fácil, pero quien la quiera tendrá que hacérsela 

Como se puede ver, he elegido un rango pequeño de perfiles, del 1 al 100, por poner un ejemplo, pero se podrían modificar estos límites a voluntad y explorar qué fotos hay por ahí. Muchas fotos de esos perfiles no existen, y muchas otras contienen elementos donde la privacidad no es relevante (¿por qué a tanta gente le gusta poner la foto de su bici de montaña?), pero hay otras donde se ve gente acompañada de otra gente, con sus niños, etc...

¿Es esto un problema? Pues depende. Depende, sobre todo, de los términos y condiciones que uno acepta cuando se registra en Endomondo. Así, a priori, antes de leerlos (enseguida los veremos), me parece que no es lo ideal. A mí me parece bien que mis amigos en Endomondo vean mi foto de perfil, pero no que la pueda ver todo el mundo, incluso aquellos que no están registrados en la web.

Si leemos detenidamente la política de privacidad, nos encontramos que nuestra foto de perfil forma parte de nuestros datos personales

Tu foto de perfil forma parte de tu información personal

Y un poco más adelante


vemos que nuestra foto de perfil es pública por defecto, y que CONSENTIMOS en que así sea si no cambiamos la privacidad en nuestra cuenta. Hmmm, no me parece la mejor opción, pero así son las cosas.

¿Puedo cambiar esto en la configuración de la privacidad de mi cuenta? Bueno, más o menos

Tu perfil, o lo ve todo el mundo o los usuarios de Endomondo

En casi todas las opciones (la mayoría de las cuales vienen por defecto con el valor "Todos") se te da la opción de poner "Amigos", para limitar la visibilidad de tus datos, o incluso puedes poner "Sólo yo". Sin embargo, para la opción de mi perfil sólo puedo elegir "Todos" o "Usuarios Endomondo". Vale, lo cambio para que sólo lo puedan ver los usuarios de Endomondo.

Y una vez cambiado, intento ver si tengo acceso a mi foto de perfil poniendo la URL del principio... y el resultado es... ¡se sigue viendo! (he de reconocer que no me sorprende, me lo esperaba).

Conclusión: que tu foto de perfil sigue siendo visible para TODO EL MUNDO, no sólo para los usuarios de Endomondo.

Otra cosa: no soy ningún experto en políticas de privacidad, pero ya que han hecho el esfuerzo de traducir la interfaz al idioma español, ¿no deberían haber hecho lo mismo con la política de privacidad?

Mi consejo: no subas a Endomondo fotos que te interese tener con verdadera privacidad, ya que están accesibles públicamente mientras Endomondo no cambie esto.

¿Y tú, también tienes subida a Endomondo esa foto que no te gusta que todo el mundo vea?

Enlaces relacionados

Endomondo: www.endomondo.com

Róbame que estoy haciendo deporte: http://www.elladodelmal.com/2014/07/robame-que-estoy-haciendo-deporte-y.html

viernes, 25 de julio de 2014

Habilitar las librerías de cifrado de PHP en Ubuntu

Para cifrar (encriptar no existe para la RAE, aunque muchas veces utilicemos ambos verbos como sinónimos) un bloque de datos, en PHP podemos utilizar la función mcrypt_encrypt

Fig. 1. Parámetros de mcrypt_encrypt

El primer parámetro de esta función hace referencia al algoritmo que emplearemos. Pero, ¿qué algoritmos podemos utilizar? Para ver los que hay disponibles en nuestro sistema, podemos utilizar la función mcrypt_list_algorithms. Por ejemplo, así:

 1 <?php
 2 echo "<pre>" . print_r (mcrypt_list_algorithms (), TRUE) . "</pre>";
 3 ?>
Listado 1. ¿Qué algoritmos puedo utilizar para cifrar mis datos?

He probado esto en Windows y la salida ha sido la siguiente:

Figura 2. ¡Hala, qué variedad para elegir! Pito, pito, gorgorito...

Pero, al probar lo mismo en Ubuntu, la salida ha sido...


...




¡absolutamente nada!


Pero... ¿qué está pasando?????


Fig. 2. WTF!!!!!?????

Vale. Tras un poco de surfeo web, parece ser que hay que hacer una receta de 3 pasos:

1) Instalar php5-mcrypt
2) Reiniciar el servidor web
3) Habilitar la extensión mcrypt

Si sabéis hacer esto, podéis dejar de leer ya. Si eres como yo, que no sabía cómo se hacía, aquí están los detalles:

1) Instalar php5-mcrypt

$ sudo apt-get install php5-mcrypt

Como podemos deducir del nombre, hemos de disponer de la versión 5 o superior de PHP. ¿Que cómo sabemos cuál tenemos? Pues así:

$ php -v

Si no tienes la 5 o superior, actualízala (si puedes).


2) Reiniciar Apache

Tienes varias formas. Por ejemplo, así

$ sudo /etc/init.d/apache2 restart

o bien

$ sudo service apache2 restart

Tras esto, aún seguimos sin poder obtener los algoritmos de cifrado ("cifradores" los llama la documentación oficial de PHP) disponibles.


3) Habilitar la extensión en el fichero php.ini

Para ello, se incluye la línea

extension = mcrypt.so

en el fichero de configuración de PHP, el famoso php.ini.

Y ahora ya... ¡sí! Al ejecutar en Ubuntu el script de antes (Listado 1) obtenemos la salida

Figura 3. La misma lista de "cifradores" que en Windows, aunque cambie el orden de algunos elementos

Hala, ya podéis darle un buen uso a las funciones de cifrado.

jueves, 5 de junio de 2014

Recorrer un directorio en PHP procesando los ficheros en su interior

Vamos a ver un pequeño ejercicio en el que recorreremos un directorio contando cuántos ficheros hay colgando de él, incluyendo los de sus subdirectorios. Esto es un paso hacia una herramienta un poco más ambiciosa. Pero empecemos por el primer paso...


1. Funciones de Directorios en PHP

Hay tres funciones que son claves para recorrer un directorio con PHP.

La primera, opendir, toma el nombre del directorio a leer (sin la barra del final) y devuelve un identificador del directorio, que se podrá usar con las otras funciones. Llamada:

    $dir = opendir($path)

La segunda, readdir, va devolviendo en sucesivas llamadas los nombres de las entradas contenidas en el directorio. Normalmente, esto se utiliza en un bucle de la forma

    while ($elemento = readdir($dir)){
        ...procesar
    }

Hay que tener cuidado con los dos directorios especiales "punto" (actual) y "punto punto" (superior), más conocidos como "." y "..". Normalmente, al procesar los elementos del directorio se controla con un if si el elemento a tratar no es ninguno de estos dos. Algo así:

    $dir = opendir($path)
    while ($elemento = readdir($dir)){
        if( $elemento != "." && $elemento != ".."){
            ... procesar
        }   
    }

Por último, para diferenciar si el elemento a tratar es un fichero o es un subdirectorio, tenemos la función is_dir. En caso de que sea un subdirectorio al que queramos aplicar el mismo tratamiento que al actual, podemos hacer una llamada recursiva a la misma función (que aquí llamaremos analizar_directorio).


2. Todo Junto

Pongámoslo ahora todo junto, en una función que se limita a contar los ficheros contenidos en el directorio inicial y los subdirectorios que hay dentro, recorridos de manera recursiva (¡ojo, cuidado con la recursividad). Más adelante haremos cosas más interesantes que contar ficheros. Para probarlo todo, guardaremos el siguiente listado como index.php:

 1 <html>
 2 <head>
 3     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 4 </head>
 5 
 6 <body>
 7 <?php
 8     $path = "../../rips";
 9     $total_ficheros = analizar_directorio($path);
10     echo "Hay $total_ficheros ficheros en el directorio $path<br>";
11     
12 //------------------------------------------------------------- 
13 function analizar_directorio($path) {
14     $total_ficheros = 0;
15     $dir = opendir($path);
16     while ($elemento = readdir($dir)){
17         if( $elemento != "." && $elemento != ".."){
18             // Si es una carpeta
19             if( is_dir($path."/".$elemento) ){
20                 // Muestro la carpeta
21                 echo("Procesando subdirectorio: ". $elemento . "<br>");
22                 $total_ficheros += analizar_directorio($path."/".$elemento);
23             // Si es un fichero
24             } else {
25                 $total_ficheros++;
26             }
27         }
28     }
29     return $total_ficheros;
30 }
31 
32 ?>


El resultado en el navegador:


Referencias sobre las tres funciones

http://www.php.net/manual/es/function.opendir.php
https://php.net/manual/es/function.readdir.php
https://php.net/manual/es/function.is-dir.php

martes, 3 de junio de 2014

Configurar Apache con PHP en Ubuntu Linux

Guía breve, breve, brevísima, para recordar los pasos que he dado para la instalación, pero sin muchas explicaciones.

1. Instalar Apache

$ sudo apt-get install apache2

2. Instalar PHP, versión 5 (PHP5)

$ sudo apt-get install php5

$ sudo apt-get install libapache2-mod-php5


3. Habilitar el módulo de PHP5 en Apache

$ sudo a2enmod php5


Y ya podemos probarlo con un sencillo fichero de prueba, por ejemplo:

Fichero index.php:

 1     <HTML> 
 2     <H1>Probando PHP</H1>
 3     Salida del comando phpinfo:
 4     <?php
 5         phpinfo();
 6     ?>
 7     </HTML>
Listado 1. El HTML no es perfectamente ortodoxo, pero sólo es una prueba

¡Ojo! En la línea 4 del anterior código, la instrucción de apertura del código PHP, es decir
   
    <?php

    en teoría también debería funcionar si se omiten las letras "php", es decir
   
    <?
   
Bueno, pues resulta que esto sí que me ha funcionado en Apache2 sobre Windows XP, pero en Ubuntu me ha tenido un ratico dándole vueltas a la cabeza, pues no funcionaba, y me mostraba, en vez de la salida de la función phpinfo(), el texto hasta la línea 3, y el resto de la página en blanco. Es decir, como si no interpretara la función phpinfo(), e ignorara todo lo situado entre las etiquetas "<?" y "?>". Al final se ha arreglado cuando a la etiqueta de apertura le he añadido esas tres letras, dejándola así: "<?php" (sin las comillas, claro). Así que cuidado con este detalle, pues os puede hacer creer que PHP no está funcionando cuando en realidad sí que está bien instalado.

Directorios de interés
Fichero de configuración de Apache2
/etc/apache2/apache2.conf

Directorio por defecto para publicación de contenidos
/var/www/html

Más info
http://doc.ubuntu-es.org/HTTPD_Servidor_web_Apache2
Bien explicada, con muchísimos detalles y sin molestos banners publicitarios.

viernes, 30 de mayo de 2014

Leer ficheros, contar el número de líneas y procesarlas

¿Os gusta la lectura? A mí me encanta, uno de mis géneros preferidos es la ciencia ficción. Pero uno no siempre puede leer lo que quiere, y en ocasiones hay que leer cosas que no son tan entretenidas como una buena novela. Por ejemplo, programando, a veces tenemos que leer un fichero de texto línea a línea. (Vale, es un chiste muuuuyyyyy malo, pero al menos es corto ;-)

Bueno, ahora en serio, lo de leer el fichero lo podemos hacer como mínimo de dos formas:

1) Si no nos interesa todo el contenido del fichero, sino que únicamente estamos buscando las líneas que cumplan una determinada condición, podemos ir leyendo línea a línea y procesar esas líneas individualmente. Para ello, abrimos el fichero (fopen) y en un bucle vamos leyendo cada línea (fgets). Esto nos da control sobre cada línea individualmente, y no consume demasiada memoria, ya que una vez procesada la línea la podemos descartar (también la podemos almacenar si nos interesa, claro).

2) Si queremos todo el contenido del fichero de golpe, por ejemplo, en un array de líneas, con PHP podemos usar la función "file" que nos devuelve precisamente eso. Lógicamente, esta segunda opción ocupará más memoria, lo cual puede ser importante si el fichero leído tiene un tamaño grande, aunque me imagino que posiblemente sea una forma más óptima en tiempo, leerlo todo de un golpe. Ya sabéis, el eterno dilema de memoria frente al tiempo. Si después queremos analizar esas líneas leídas y almacenadas en el array, tendremos que hacer un bucle para recorrerlas.

Bien, ¿y qué técnica me conviene? Pues como siempre, depende de lo que quieras hacer. En un primer caso, para ilustrar ambas técnicas, mi objetivo va a ser contar el número de líneas del archivo, sin procesar cada línea individualmente. Os dejo aquí dos funciones en PHP (contar1 y contar2) que ilustran ambos métodos, aunque fácilmente se pueden traducir a cualquier otro lenguaje.

Según lo explicado en el caso 1)

 1 //-------------------------------------------------------------
 2 function contar1($filename) {
 3     $file = fopen($filename, "r");
 4     $num_lineas = 0;
 5     while (!feof($file)) {
 6         if ($line = fgets($file)){
 7            $num_lineas++;
 8         }
 9     }
10     fclose($file);
11     return $num_lineas;
12 }

Y para el caso 2), la función "count" nos da el número de elementos de un array, lo que en este caso coincide con el número de líneas del fichero

 1 function contar2($filename) {
 2     $num_lineas = count(file($filename));
 3     return $num_lineas;
 4 }



Rendimiento de cada opción

Para analizar el rendimiento de ambas opciones, he hecho pruebas contando el número de líneas con un grupo de unos 2.000 ficheros aproximadamente que tengo en un directorio, una aplicación web. He limitado la lectura a ficheros de código fuente en Javascript, PHP, CSS y HTML, con una longitud media de 316 líneas por fichero, aunque esta medida tiene una varianza alta, pues el fichero más largo tiene 30.000 líneas (no es un caso frecuente), y el fichero más pequeño está vacío (cero líneas).

Tras ocho o diez ejecuciones, contando las líneas por el método 1) tardo alrededor de 1.50 segundos, mientras que el método 2) está sobre los 1.25 segundos, es decir, un 17% más rápido. No he hecho pruebas de consumo de memoria RAM, pero supongo que cuando se cargó el fichero de 30.000 líneas en memoria (método 2), mi ordenador debió emitir una pequeña queja, aunque creo que nada preocupante para los estándares de memoria que puede tener un ordenador hoy en día. Claro, esto puede ser muy diferente si esta aplicación la va a ejecutar un único usuario o la van a utilizar varios usuarios a la vez sobre el mismo servidor web.

En mi caso, la diferencia entre ambas técnicas no es significativa, y sin embargo el método 1) me da mucha más flexibilidad para un objetivo que tengo en mente, pues me gustaría hacer algo de procesamiento con cada línea conforme las voy leyendo.

Pero esta entrada ya me está quedando demasiado larga, así que ya hablaré de ello en otra ocasión, y también pondré el código utilizado para el test, con las explicaciones pertinentes.

¿Y tú, sueles enfrentarte al problema de procesar ficheros línea a línea habitualmente? ¿Utilizas alguna de las técnicas de las que hablo aquí o tienes otra forma de hacerlo?

Referencias
La función file es quizás la más interesante de las aquí mencionadas.

martes, 8 de abril de 2014

Formatear números en PHP para un informe

Para mostrar los números con el separador de miles, el separador de la coma decimal y dos decimales (dinero en euros).

La función para esto se llama number_format.

Uso:


 1 $numero = 1002002.365;
 2 number_format($numero, 2, ",", ".");
 3 //devuelve 1.002.002,37 

El ejemplo está sacado directamente de desarrolloweb.com, donde hay una explicación mucho más detallada muy bien expuesta, para quien quiera conocer más detalles.

lunes, 7 de abril de 2014

Medir el tiempo de carga de una página en PHP

Insertar este código en la página:

 1 //al principio
 2 
 3 $hora_inicio = microtime(true);
 4 
 5 //... hacer cosas ...
 6 
 7 //al final
 8 
 9 $hora_fin = microtime(true);
10 $tiempo_total = round($hora_fin - $hora_inicio, 4);
11 echo "<div class=\"tiempocarga\">Página construida en " . $tiempo_total . " segundos </div>\n";

Como veréis, en el CSS he definido una clase para mostrar este mensaje con un tipo de letra más pequeño de lo habitual, pero vamos, esto va a gusto del consumidor

 1 .tiempocarga {
 2     font-family: Arial, Helvetica, sans-serif;
 3     color: #606680;
 4     font-size: 8pt;
 5     text-align: center
 6 }



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...