apache2 htacces

Mejorando la seguridad en Apache2 .htaccess

En una entrada anterior enseñamos como configurar mod_rewrite en nuestro .htaccess para poner a “punto de caramelo” nuestro Apache2 pero ahora es necesario preveniros de “haber abierto la caja de Pandora”: nuestro servidor web está expuesto a tácticas de intromisión y hasta toma de control por atacantes con malas intenciones. Sirva pues la presente para complementar y ayudaros a proteger vuestros equipos.

Apache mod_rewrite
Apache mod_rewrite

Prerequisitos.

Es bueno que leaís nuestro tutorial para configurar mod_rewrite pero si soís más avanzado os decimos de una vez que tenemos configurado ya un servidor Apache2 sobre Ubuntu 16 en una red de área local con la dirección 192.168.1.47

Para ser más exactos, corremos Apache 2.4 y podemos conocer cuál versión tenemos instalada abriendo una ventana terminal y ganando acceso como “root” o administrador:

root@KEVIN:/home/jimmy# apache2 -v
Server version: Apache/2.4.18 (Ubuntu)
Server built: 2016-07-14T12:32:26

Otra cosa que tenemos activado es el tratamiento de archivos .html o .htm como .php lo cual abre la posibilidad a que nuestra seguridad quede expuesta si alguien logra acceder a escribir en nuestro .htaccess (de nuevo os recordamos que todo está explicado en nuestra entrada anterior sobre como configurar mod_rewrite). Eso quiere decir que tenemos la siguiente instrucción en el archivo .htacces:

AddType application/x-httpd-php .html .htm

Al estar así configurado pasamos a estudiar las posibles brechas en seguridad.

apache2 htacces
apache2 htacces

Lo bueno, lo malo y lo feo.

Recordando la famosa película de vaqueros así clasificaremos las tretas y estrategias, poned atención que cada una de ellas es un caso particular a estudiar, pausar y seguir adelante para, al final, tener un panorama completo de la estrategia a seguir que más convenga a nuestro entorno de trabajo.

Empezaremos por lo malo, luego lo feo y de último lo bueno que podemos hacer.

Lo malo.

php_value auto_append_file.

Vale decir que este comando php_value bien puede estar ubicado ya sea en “PHP config”, “virtualhost settings” o en un archivo .htaccess que es el caso que nos ocupa y más específicamente el valor auto_append_file porque coloca al final de nuestra página web (en este ejemplo didáctico el archivo index.html que trae por defecto Apache al ser instalado) los valores que tengamos en nuestro archivo hosts con la siguiente instrucción:

php_value auto_append_file /etc/hosts

El resultado sería el siguiente:

php_value auto_append_file etc hosts
php_value auto_append_file etc hosts

Que para nuestro servidor de pruebas nos aporta muy poca información pero imaginad si lo tenéis en un servidor en producción y de paso sea compartido por otros sitios web, dicha información sería muy útil a los atacantes.

Otra variante de esta treta es incluir al archivo .htacces en sí mismo y adicionalmente algún código php. Por ejemplo podemos agregar el comando phpinfo(); con el cual podemos visualizar el entorno de variables con que trabajamos y buscar así software específico para nuestra configuración y poder de esta manera tomar control completo, si fueramos atacantes.


1
2
php_value auto_append_file .htaccess
#<?php phpinfo();
php_value auto_append_file htaccess phpinfo
php_value auto_append_file htaccess phpinfo

php_flag display_errors 1

Hasta ahora hemos visto inyecciones de código que se ejecutan cada vez que es cargado y mostrado un archivo .html ¿pero que tal colocar una bomba eventual? Si, como habéis leido, “eventual” es diferente a una bomba de tiempo, una bomba eventual se ejecuta cuando sucede algún disparador. Para este caso el disparador del evento es cuando nos hayamos equivocado programando algún código php, con la salvedad de que el atacante a nuestra web haya colocado un “script” que se lanza en este suceso, pero veamos el código:

php_flag display_errors 1
php_flag html_errors 1
php_value docref_root "'><script>alert('¡Programador perdedor!);</script>"

En la primera y segunda línea lo que se hace es activar la rutina de aviso de errores (que bien en vez del parámetro “1” puede tomar el valor “on”) y en la tercera línea es donde se inserta el código malicioso en sí (acá simplemente mostramos un aviso recriminando el error). Esto sería el equivalente a “castigar” al programador al cometer un error y el script que desencadene podría ser peor aún todavía. Por extraño comportamiento, esto produciría un muy extraño regocijo a nuestro atacante y esperad también ver algún código de aviso para el atacante que nos pudiera servir para dar con el paradero del delincuente digital.

Otra variante sería utilizar php_value docref_ext lo cual duplicaría las ejecuciones del script malicioso.

php_flag display_errors 1
php_flag html_errors 1
php_value docref_root "x"
php_value docref_ext "<script>alert(1);</script>"

QUERY_STRING

En lenguaje HTML y PHP es bien sabido que se puede pasar valores directamente por la barra de direcciones con simplemente agregar al final de la dirección web un signo de cierre de interrogación acompañado del nombre de la variable, un signo de igualdad y el valor de la variable. Si se desean pasar más valores se utiliza un et “&” y se repite el procedimiento. Esto es especialmente útil para iniciar sesión en una página web pero también abre la puerta a posibles robots que intenten, a lo largo de mucho tiempo, ir probando valores automáticamente hasta poder ingresar. Para cerrar esta posibilidad podemos hacer lo siguiente, consideremos el siguiente archivo llamado “bienvenido.hmtl“:

<?php
 echo '<html><body>Bienvenido ';
 if (isset($_GET['nombre'])) {
   $test = $_GET['nombre'];
 } else {
   $test = ' Invitado';
 }
 echo $test;
 echo '</body></html';
?>

Como podéis observar utilizamos el método $_GET a modo didáctico para poder hacer las pruebas sin necesitar de otro archivo con formulario que lo refiera. Si introducimos una consulta como la siguiente:

http://192.168.1.47/bienvenido.html?nombre=Pedro

Obtendremos la siguiente respuesta:

bienvenido html con pase de consulta
bienvenido html con pase de consulta

Pero como buscamos bloquear dichos “accesos directos” debemos modificar nuestro .htaccess de la siguiente manera:

RewriteEngine On
RewriteCond %{QUERY_STRING} .
RewriteRule ^bienvenido\.html /bienvenido.html? [L]

La primera línea sabemos que activa la maquinaría de reescritura de direcciones y la segunda es una condición que se cumple siempre: compara la consulta %{QUERY_STRING} (toda la subcadena de texto a la derecha del signo de interrogación) y lo compara con el metacaracter “.” que significa una cadena de texto con cualquier (cualesquiera) caracter(es) de cualquier longitud (1 ó más) para que lo ejecute el siguiente comando RewriteRule. Este comando simplemente pasa la dirección con el nombre del archivo en cuestión acompañado de la bandera [L] que le indica que no procese las siguientes líneas en el fichero .htaccess . El resultado sería que el fichero bienvenido.html (que contiene un guion de comandos escritos en lenguaje PHP) muestre el mensaje “Bienvenido Invitado” como si no hubiera pasado consulta alguna al servidor web.

bienvenido html con consulta bloqueada
bienvenido html con consulta bloqueada

Para suplementar lo anterior, son bien famosos los robots que hacen publicidad en los comentarios de las entradas de WordPress y por supuesto lo hacen con pases de consultas, las podemos filtrar con palabras claves y prohibirles el acceso. Famosos por ser bien insidiosos son los “spam” de medicamentos para la disfunción eréctil tales como “compre viagra en oferta”, vamos a realizar una regla adicional a lo anterior -pase de consultas-:

RewriteCond %{QUERY_STRING} \b(spill|cialis|ejaculation|erectile)\b [NC,OR]
RewriteCond %{QUERY_STRING} \b(erections|impotence|levitra|libido)\b [NC,OR]
RewriteCond %{QUERY_STRING} \b(fitex|sildenafil|tadalafilo?)\b [NC,OR]
RewriteCond %{QUERY_STRING} \b(viagra|duroval|viasek)\b [NC]
RewriteRule .* - [F]

Como apreciamos agregamos las marcas más conocidas pero aún faltan muchas más, como la lista es larga “encadenamos” cada RewriteCond con la bandera [OR] pero la última línea antes de RewriteRule NO debe contener [OR]. La otra bandera utilizada es [NC] para que coincida en la búsqueda en mayúsculas y/o minúsculas y para finalizar la bandera [F] para prohibir el acceso, que devuelva un código 403.

Poned atención en la palabra clave “tadalafilo?” ya que bloquea “tadalafil” o “tadalafilo” y en idioma castellano los nombres de principios químicos activos siempre la última letra produce sinonimías (otro ejemplo: “ciprofloxacino” y “ciprofloxacina” significan exactamente lo mismo). Esto es así debido que en inglés el género no toma mayor importancia (y en el lenguaje cotidiano da pie a muchas situaciones graciosas que aprovechan guiones de series de televisión “sitcom”) y cuando se traduce al castellano puede tomar valores ambivalentes y el algunos casos hasta extraños. Al contrario, en este campo, en el idiom inglés son pocos los casos que esto sucede (de hecho ellos GUSTAN de palabras cortas, no hablemos ya si gustan de sinonimias, además ellos utilizan marcas comerciales como nombres propios para los principios químicos activos), un caso muy particular es el medicamento contra la depresión llamado “Prozac” y por algunos es escrito como “Prosac”: en nuestras reglas de expresiones regulares lo podemos marcar como “pro[sz]ac” para que sea detectado -ambas escrituras- y bloqueado en los comentarios de cada entrada pública de nuestro blog.

Lo feo 🙁

php_flag engine 0

Con esta única línea en nuestro archivo .htaccess hará que automáticamente podamos descargar completamente un archivo .html o .php aunque igual se ejecute el guión php obtenemos el código fuente completo, incluidas contraseñas, comentarios, los diversos include hacia otros ficheros, etc. que hayamos colocado en dichos archivos. Nos parece que es la más maliciosa inyección de código en nuestro servidor.

LO BUENO 😎

IndexIgnore *

De manera predeterminada un servidor web Apache siempre buscará mostrar un archivo llamado “index.html” o “index.php” pero si dicho archivo -por alguna u otra razón- no se encuentra entonces se le mostrará al visitante -o atacante- los archivos y subdirectorios presentes, mirad un ejemplo:

lista directorios index of
lista directorios index of

Para evitar dicho comportamiento debemos agregar a nuestro .htaccess las siguientes líneas:

IndexIgnore *
Options +FollowSymLinks All -Indexes

Al guardar y revisitar mostrará lo siguiente:

Listado negado de directorio
Listado negado de directorio

De tener algún problema con la opción “+FollowSymLinks” (mirad imagen siguiente) simplemente comentarla y guardar de nuevo el archivo .htaccess.

Internal serve error
Internal serve error

DirectoryIndex

Mencionamos en el punto anterior que siempre buscará servir un fichero “index” pero ¿qué tal si tenemos dos ficheros llamados “index.php” e “index.html” en el mismo directorio?

Pues con la orden DirectoryIndex podemos ordenar cual se ejecuta primero -y de no conseguirse ejecuta el segundo-, esto puede ser de utilidad si queremos “forzar” cuál clase de archivos queremos siempre mostrar.

DirectoryIndex index.html index.php

En este ejemplo -recordad la configuración de servidor- obligamos que sea mostrado “index.html” primero (y dentro tenemos el código php necesario que será ejecutado por nuestra configuración especial .htaccess) y de no conseguirse buscaría mostrar “index.php”. Un atacante podría colocar su propio “index.php” y modificar .htaccess para camiar este orden. Nosotros preferimos utilizar solamente lo siguiente:

DirectoryIndex index.html

Así al faltar “index.html” no muestra ninguna página e intentará mostrar los archivos y subdirectorios opción la cual le eliminamos la visualización en el punto anterior.

FileETag MTime Size

Habilitar los ETag en un servidor web trae doble beneficio: por una parte ahorramos en tráfico de bytes lo cual redunda en rapidez al cargar nuestro sitio y el beneficio de la seguridad adicional.

ETags funciona identificando en un archivo TAGS las características que identifican unívocamente para fichero que pondemos a disposición del público. Es transmitido y guardado por el cliente en un caché, luego si recargan la página el servidor envia de nuevo el ETag y si el recurso no ha cambiado pues se ahorra el envio.

Por parte de la seguridad es útil si utilizamos rsync desde un servidor que origina datos (y que no está expuesto a la internet) y sirve para “alimentar” uno o varios servidores espejos -o nodos-. Hay un interesante artículo titulado “Rsync: Sincroniza tus carpetas o respalda tus archivos en Linux – Debian – Ubuntu – Derivados” escrito por el experimentado usuario Twitter @Xombra donde encontraréis mayores detalles acerca del uso de rsync como estrategia de respaldo.

 

Si utilizamos un servidor único podemos agregar INode, cuando hay varios servidores web para una misma página los ETags se vuelven inútiles ya que en cada servidor cada INode es diferente con lo cual incluso incrementa la carga de trabajo en nuestro sitio web. Es debido a esto que se les activa solamente dos atributos: tiempo (MTime) y tamaño (Size).

Para habilitarlo debemos colocar lo siguiente en nuestro archivo .htaccess (pendientes con detalle del INode):

FileETag INode MTime Size

SetEnvIf user-agent

Otra manera de proteger en alguna manera nuestro Apache es denegando a ciertos agentes que visiten nuestro sitio. Cada navegador web envia su identificación al solicitar una página web y a nuestro servidor podemos denegar el acceso (esto no quiere decir que a su vez los atacantes “disfrazen” su software de ataque como algún navegador web popular). La sintaxis que debemos usar es la siguiente:

SetEnvIf user-agent "Wget" stayout=1
SetEnvIf user-agent "Indy Library" stayout=1
SetEnvIf user-agent "libwww-perl" stayout=1
SetEnvIf user-agent "Download Demon" stayout=1
SetEnvIf user-agent "GetRight" stayout=1
SetEnvIf user-agent "GetWeb!" stayout=1
SetEnvIf user-agent "Go!Zilla" stayout=1
SetEnvIf user-agent "Go-Ahead-Got-It" stayout=1
SetEnvIf user-agent "GrabNet" stayout=1
SetEnvIf user-agent "TurnitinBot" stayout=1
deny from env=stayout

En realidad la última línea es la que bloquea los agentes, fijaros bien que la primera línea bloquea al famoso wget que si bien no es un programa malicioso en sí mismo si que se utiliza en scripts que pueden ser mal utilizados. Si tenéis algún script propio que acceda a vuestro servidor web tened cuidado en marcar como comentario dicha línea.

wget bloqueado por htaccess
wget bloqueado por htaccess

Fuentes consultadas.

En idioma inglés.

Lo malo:

Lo bueno:

Lo feo:

Apache mod_rewrite

Tutorial para configurar mod_rewrite en Apache sobre Ubuntu

Para los que “montamos” páginas web siempre es útil ocultar las extensiones de nuestros archivos a ser servidos y así despistar a posibles atacantes pero es mejor aún para que nuestros usuarios les sea más fácil recordar nuestras secciones e incluso promociones publicitarias, ¿ya tenemos vuestra atención?

Introducción.

En un mundo donde ya prácticamente todo el mundo introduce una búsqueda en Google o DuckDuckGo para llegar a nuestra página web -exponiéndose a redirecciones hacia páginas falsas por los buscadores web- pues nosotros nos empeñamos en utilizar la barra de direcciones de nuestro navegador web recomendado, Mozilla Firefox.

Es así entonces que podemos publicar alguna promoción en algún medio de comunicación como televisión o prensa y queremos que se pongan en contacto con nosotros por medio de una página web que a tal efecto hayamos programado para ello. Dicha dirección podría ser:

nuestrodominio.com/contacto

Como véis no incluimos “www” ni tampoco las dobles barras (de hecho Tim Berners-Lee se disculpó públicamente por haber hecho la redundancia y hacernos escribir billones de veces ese caracter adicional) ni “http:” -la mayoría de los navegadores web completan automáticamente la dirección- pero mejor aún hemos obviado la extensión del archivo .html, algo que consideramos podría confundir a cualquier usuario común (sí, que hasta los “nativos digitales” también se pueden equivocar).

La intención de este tutorial es CONFIGURAR nuestro servidor web para que acepte dicha dirección y redirija a nuestros posibles clientes de manera transparente y así, a futuro, trabajemos y aumentemos nuestros ingresos 😉 .

Prefacio.

Aunque no estamos escribiendo un libro, estas páginas web algún día las podríamos recopilar en un ejemplar, por eso el uso de la palabra “prefacio“. Lo que necesitaremos para nuestra práctica didáctica es un ordenador con Ubuntu 16 instalado (que también vale este tutorial para Ubuntu 14) al cual le instalaremos Apache2. Para nuestro caso nuestra máquina tiene una dirección IP fija en nuestra red área de local 192.168.1.47 CAMBIAD lo que veaís aquí por vuestra propia dirección IP local. Lo que acá escribimos es válido también para cuando queráis montar vuestro servidor de producción en la internet y solo está limitado por las restricciones que os imponga vuestro proveedor de hospedaje. Este tutorial es completo y el escenario sería o bien una máquina virtual o real que alquileís a un proveedor de alojamiento (es más costoso que alojamiento compartido) o vuestro ordenador con una dirección IP fija que os venda vuestro ISP y que os conécteis bien sea por “ADSL” o “frame relay“.

Instalando Apache Web Server:

Abrimos una ventana de comandos (si no sabéis cómo, leed nuestro tutorial) e introducimos el siguiente comando:

sudo apt-get update
sudo apt-get install apache2

La primera línea actualiza los paquetes disponibles para nuestra versión GNU/Linux que tengamos instalado (Ubuntu 16 en este ejemplo) y la segunda línea instala el servidor web Apache2, software que ha evolucionado mucho desde los años 90. Nótese que usamos “sudo” para tener derechos de administrador o “root” y deberemos colocar la contraseña respectiva (luego el sistema obvia esta solicitud si corremos varios comandos seguidos en corto período de tiempo). Podremos ver algo muy parecido a esto, si todo sale bien:

apt-get install apache2
apt-get install apache2

Para probar que tenemos nuestro servidor instalado podemos navegar hasta nuestra propia dirección IP o en su defecto podemos utilizar la palabra clave “localhost” y eso sí, en la barra de direcciones de nuestro navegador (los buscadores web NO mostrarán enlace hacia nuestro servidor).

Apache2 Ubuntu Default Page
Apache2 Ubuntu Default Page

Habilitando mod_rewrite.

Antes de meternos de lleno con mod_rewrite cumplimos con avisaros que también existe la función mod_alias que es muchísimo más sencilla para resolver rápidamente ciertas situaciones -e incluso cuenta con los comando más avanzados llamados “<Location>” y “<LocationMatch>“- .

Pero si queremos evolucionar y disponer de una herramienta poderosa (y compleja) estudiaremos mod_rewrite. Para activar mod_rewrite debemos ejecutar las siguiente líneas:

sudo a2enmod rewrite
sudo service apache2 restart

La segunda línea sirve para que el servicio o “daemon” reinicie con la configuración deseada. En el ambiente GNU/Linux son muy parcos y estoicos, si todo va bien simplemente veréis de nuevo el indicador de la línea de comando “prompt” -pero al finalizar el primer comando nos indica que falta alguna instrucción que sea importante para finalizar el comando-. ES DECIR no espéreis a cada rato mensaje de confirmación de comando ejecutado -otra cosa, en raras y contadas ocasiones deberemos hacer un reinicio completo de nuestro ordenador, con reiniciar los servicios respectivos tiene para seguir funcionando luego de nuestros cambios-.

Como ya sabéis los comandos en GNU/Linux diferencian letras mayúsculas de minúsculas y por supuesto debemos escribir muy bien los comandos, no como nosotros que nos equivocamos pero luego correjimos, mirad:

sudo a2enmod rewrite
sudo a2enmod rewrite

Configurando .htaccess

Ahora debemos crear un archivo (oculto, mirad el puntito que precede el nombre del fichero) muy importante en el funcionamiento de Apache. Si lo tenemos ubicado en el directorio raíz de nuestro servidor web se aplicará a todas las subcarpetas que tengamos pero cada subcarpeta podrá tener su propio “.htaccess” que regirá para la carpeta donde esté ubicado (y a su vez las subcarpetas que contenga) obviando las instrucciones que hayamos colocado en nuestro “.htacces raíz”.

Pero antes, por razones de seguridad, debemos tener acceso (desde Apache) para crear y escribir dicho fichero, usamos nano -o vuestro editor de texto favorito- para modificar el siguiente archivo:

sudo nano /etc/apache2/sites-enabled/000-default.conf

Tras dentro del cual debemos insertar lo siguiente (notad los comentarios que insertamos en castellano en la imagen más abajo:)

  <Directory /var/www/html>
     Options Indexes FollowSymLinks MultiViews
     AllowOverride All
     Order allow,deny
     allow from all
  </Directory>
000-default.conf
000-default.conf

Es sumamente importante indentar las líneas (en nano lo podéis hacer rápidamente pulsando la tecla TAB para cada sangría) y respetar mayúsculas y minúsculas. Luego debemos reiniciar el servicio web Apache con el consabido comando:

sudo service apache2 restart

Como información adicional os contamos que el archivo “000-default.conf” es el archivo por defecto del primer dominio que sea servido por nuestro ordenador, es decir, podremos a futuro alojar varios dominios en una sola máquina. De este modo el segundo dominio utilizará el archivo “001-default.conf” y así sucesivamente, pero ¿cómo reconoce nuestro servidor web cual dominio entregar? Estad atentos más adelante en este tutorial os daremos noción de ello -y a futuro publicaremos una entrada totalmente dedicada a configurar un servidor Apache con varios dominios virtuales-.

Creando .htaccess

Ya que tenemos configurado a Apache para que acepte nuestro .htaccess procedemos a crearlo. Para ello debemos utilizar nuestro editor de archivos de texto favorito (usaremos nano en este caso) y escribiremos lo siguiente:


1
sudo nano /var/www/html/.htaccess

Y le agregamos una sola línea:


1
RewriteEngine on
RewriteEngine on
RewriteEngine on

Guardamos y luego debemos garantizar su acceso para los demás usuarios, nosotros o cualquiera otro usuario que edite nuestra página web (recordad que lo creamos con la credencial de administrador o “root”):


1
sudo chmod 644 /var/www/html/.htaccess

¡Y listo! Ya tenemos configurado nuestro .htaccess pero como decía Santo Tomás “hasta no ver, no creer” así que vamos a probar si es verdad que funciona.

Creando nuestra pequeña página web.

Como dijimos al principio haremos una página de contacto sin mayores pretensiones, en este ejemplo indicando nuestro correo electrónico de manera tal que no sea capturada por los robots que esparcen “spam” a diestra y siniestra (para una explicación más a fondo visitad nuestro tutorial sobre HTML5):

<html>
        <head>
                <title>Contacto.</title>
        </head>
        <body>
                <h1>Correo electr&oacutenico:</h1>
                <p> contacto EN 192.168.1.47 </p>
        </body>
</html>
contacto.html
contacto.html

Y al navegar desde nuestra barra de dirección de nuestro navegador web observamos que tranquilamente podemos obviar la extensión del archivo y nuestro Apache dará por bien servida a nuestros propósitos sin mayor configuración, pero…

contacto
contacto

… si escribimos “Contacto” (o cualquiera de sus variaciones en mayúsculas o minúsculas) veremos algo que se asemeja a esto:

Contacto
Contacto

Para solucionar esto hemos de echar mano en el archivo .htaccess y hacer uso de las expresiones regulares.

Usando expresiones regulares o racionales.

De antemano: Apache utiliza los conceptos de expresiones regulares o racionales desde el punto de vista del lenguaje Perl 5, ciertas diferencias hay que tenerlas muy en cuenta si usamos otros lenguajes que soportan o tienen librerías que permiten el uso de expresiones regulares (a saber hoy en día, sin que sea tajante la lista: AWK, C++, Java, JavaScript, PCRE, PHP, Python y .NET framework ).


1
sudo nano /var/www/html/.htaccess

Volvemos a modificar nuestro archivo .htaccess y agregamos la siguiente línea:


1
RewriteRule ^contacto.html$ contacto.html [NC]

Guardamos en inmediatamente la regla es puesta en funcionamiento, lo podemos comprobar al buscar nuestra página (recordad poner vuestra propia direción IP o dominio web) /Contacto.html y todas sus variantes, probad y volved.


Explicamos entonces la expresión regular -o racional- utilizada, el significado de cada metacaracter:

  • “contacto.html”: es el nombre del fichero al cual queremos “redirigir” de manera “transparente” al usuario, es decir, mostrará la página a pesar de como escriba el visitante el enlace web (mayúsculas o minúsculas).
  • “^”: representa todo el enlace a la izquierda de nuestra palabra clave (en nuestro caso el nombre de un fichero pero esto no es necesariamente así todo el tiempo, ya veremos más adelante).
  • “$”: representa todo el enlace a la derecha de nuestra palabra clave, éste y el anterior metacaracter actúan a modo de delimitadores, lo podemos ver de esa manera para simplificar su uso.
  • “[NC]”: es una bandera o banderín que indica que use indistintamente minúsculas y/o mayúsculas en nuestra palabra clave.

En conclusión que nuestros usuarios podrán escribir “contacto” con o sin extensión, en mayúsculas y/o minúsculas (todas sus combinaciones) -probad- más sin embargo las siguientes expresiones devolverán una respuesta 404 “página no encontrada”:

  1. 192.168.1.47/contacto.html/
  2. 192.168.1.47/contacto.htm
  3. 192.168.1.47/contact

En el primero se trata de abrir un subdirectorio, en el segundo y el tercer caso se trata de abrir un archivo totalmente diferente (casos que al final también analizaremos y trataremos).

Actualizado el sábado 5 de agosto de 2017.

Que este tema de las expresiones regulares da para una entrada completa en nuestro blog, por la red social Twitter econtramos una excelente imagen que lo explica muy rápidamente y a continuación leímos las opiniones de varios programadores al respecto. Os debemos ese tutorial y en cuanto hallemos tiempo libre de nuestro trabajo de ganarnos el pan de cada día lo haremos.

 

Reglas de “Rewrite”.

De las expresiones regulares o racionales, ese tema solo, solito él, da para una entrada completa, sin embargo ya tenéis la práctica (si algo no tenéis claro subid y practicad de nuevo) y es por ello que acá estudiaremos unas cuantas reglas más avanzadas del comando “RewriteRule” -valga la redundancia multilingüe, si es que eso existe- utilizando las expresiones regulares o racionales para usos más avanzados, ¡vamos!

RewriteRule patrón sustitución bandera
  • Cada regla ha de colocarse en cada archivo .htaccess al principio de la línea con la palabra clave “RewriteRule“.
  • A continuación escribiremos el patrón a buscar en el enlace recibido por nuestro servidor web (ya comentamos de entrada mera que los navegadores web modernos agregan “http://www”, colocan todo el dominio en minúsculas -NO los subdominios y archivos- y hasta completan “.com” si se escribe dominio y se presiona CTRL+INTRO).
  • Sustitución, osea, lo que queremos enviar a nuestro servidor web y que se comporte como si el mismo usuario lo hubiera escrito tal cual (si queréis, pensad en este proceso como un corrector ortográfico automático a toda petición web que llegue).
  • Opcional: bandera o banderín para modificar la regla.

Ejemplo sencillo: imaginad que ahora en nuestra cuenta Twitter, nuestro perfil, colocamos un enlace web para que los interesados sepan nuestro correo electrónico pero queremos reutilizar la paginita web “contacto.html” para ello, pues lo que debemos hacer es anteceder en nuestras reglas lo siguiente:

RewriteRule ^correoe$ contacto.html [NC]
RewriteRule ^contacto.html$ contacto.html [NC]

Como podeís observar en la primera línea, simplemente si un visitante escribe:

http://www.nuestrodominio.com/correoe

pues simplemente nuestro servidor web recibirá la orden de mostrar nuestro archivo “contacto.html” y listo, sin mayor trabajo hemos solucionado otro “problema” pero la cosa va más allá, vamos a preguntarnos ¿Qué tal si escribimos “Contacto.Html” en vez de “contacto.html”? Como bien sabéis en GNU/Linux (y los servidores web e internet en general) las mayúsculas se distinguen de las minúsculas, no son lo mismo a pesar que nuestro cerebro las siga tratando como iguales, ¿qué sucedería?

RewriteRule ^correoe$ Contacto.htmL
RewriteRule ^contacto.html$ contacto.html [NC]

Advertencia imagen pequeña

No, no estaís equivocados, en efecto, el archivo “Contacto.htmL” NO existe en nuestro servidor web pero igual nos “devuelve” el contenido de “contacto.hmtl”, resulta y acontece que cada regla se ejecuta una a una y la siguiente regla corrige lo que entrega la regla anterior -muchas otras personas lo ven como si se “sobreescribe” la una a la otra, pero en realidad nosotros lo vemos de manera muy similar al comando tubería “|” que utiliza el shell de GNU/Linux.

Anda, probad a gusto tu archivo .htaccess, quitad la segunda línea y ensayad, luego volved y seguiremos practicando otras cosillas.


Utilizando patrones alternos.

En este punto continuaremos con el ejemplo anterior: ¿qué tal si queremos escribir ambas reglas en una sola línea?

Para ello haremos uso del los paréntesis normales “()” y el metacaracter “|” que en este caso viene a significar “o” (“or” en idioma inglés) -no confundir con el comando grep que mencionamos párrafos arriba, son contextos diferentes debido a que están encerrados entre paréntesis-:

RewriteRule ^(contacto.html|correoe.html)$ contacto.html [NC]

Ya sabemos como funciona la regla, aquí lo nuevo es como expresamos el patrón y le estamos indicando que ya sea que recibamos “contacto.html” o “correoe.html” -en mayúsculas y/o minúsculas mirad la bandera– siempre “devolverá” la cadena de texto “contacto.html”, precisamente el nombre del fichero que mostrará nuestro servidor web. Incluso podemos ir más allá y quitar las extensiones “.html” del patrón e igual funcionará, probad esto:

RewriteRule ^(contacto|correoe)$ contacto.html [NC]

Utilizando el pase de parámetros.

De nuevo seguimos el hilo con el ejemplo anterior, ¿qué tal si quisiéramos saber cómo llegó nuestro usuario hasta nuestro servidor -preguntando por “correoe.html” o “contacto.html”?

Esto lo podemos lograr insertandole un poco de lenguaje PHP, pero vayamos por partes y configuremos nuestro ordenador:


En nuestro servidor web Apache debemos instalar las librerías que soportan PHP versión 7 (en Ubuntu 16):

apt-get install libapache2-mod-php
sudo a2enmod php7.0
sudo service apache2 restart
  1. En la primera línea instalamos las librerías php.
  2. En la segunda línea activamos php 7 para Apache.
  3. En la tercera línea reiniciamos el servicio Aapache.

Si todo va bien podremos examinar la configuración PHP con un archivo que podemos crear nosotros mismos con una línea: “<? phpinfo() ?>” y lo guardamos con el nombre que más nos guste. Al “llamarlo” por el navegador web nos mostrará algo parecido a lo siguiente:

PHP 7 en Apache 2 sobre Ubuntu 16
PHP 7 en Apache 2 sobre Ubuntu 16

NOTA: en un servidor “en producción” por razones de seguridad NUNCA debemos dejar un archivo con la función “phpinfo()” : haría que cualquier atacante conozca nuestro “entorno” y le sea más fácil utilizar herramientas específicas contra las versiones de software que tengamos instalado en nuestro ordenador configurado para trabajo público.


Ha llegado la hora, pues, de modificar nuestro .htaccess para que trabaje con lenguaje PHP:

AddType application/x-httpd-php .html .htm
RewriteRule ^(contacto|contacto.html|correoe)$ contacto.html?origen=$1 [NC,QSA]
  • En nuestro archivo .htaccess la primera línea indica a nuestro servidor Apache que trate los archivos .html y .htm como .php .
  • En la segunda línea añadimos la bandera “QSA” (“Query String Append”) la cual permite pasar de manera intacta la palabra clave recibida, tal cual fue escrita, por medio de un método GET al “interior” del archivo HTML (“?origen=$1”).
  • Por último el comodín $1 indica por cual de las opciones “entra” la consulta (si tuviéramos otro paréntesis con opciones “|” recuperaríamos dicha coincidencia o elección con $2 y así sucesivamente).
RewriteRule y variables seleccionadas
RewriteRule y variables seleccionadas

En nuestro archivo “contacto.html” modificamos para que reconozca las palabras claves “contacto” y “correoe”: con la primera escribiremos “Gracias por conocer nuestra promoción en TELEVISION” y a la segunda “Gracias por conocer nuestra promoción en PERIÓDICO”.

  • “contacto” -> “TELEVISION”.
  • “correoe” -> “PRENSA”.
<html>
  <head><title>Contacto.</title></head>
  <body>
    <?php
      $parametro = strtolower($_GET['origen']);
      $contacto = strpos($parametro,'contacto');
      if ($contacto !== false) {
        echo(' ¡Gracias por conocer nuestra promoción en TELEVISION!');
      }else{
        echo(' ¡Gracias por conocer nuestra promoción en PRENSA!');
      }
    ?>
    <h1>Correo electr&oacutenico:</h1>
    <p> contacto EN 192.168.1.47 </p>
  </body>
</html>

Como veís con el lenguaje PHP buscamos la palabra clave “contacto” para notificar que el cliente vio la promoción por televisión, el otro único valor posible es “correoe” ya que la regla mod_rewrite filtra lo que escribe el usuario.

  • Con el comando $_GET[‘origen’] obtenemos la cadena $1 del comando RewriteRule.
  • Con el comando strtolower() lo convertimos todo a minúsculas.
  • Con el comando strpos() obtenemos FALSO si no se haya la palabra clave, de lo contrario devuelve un valor mayor o igual a cero.
  • Teniendo en cuenta lo anterior se debe utilizar la comparación ($contacto !== false) por negación para obtener el resultado correcto.

Hasta acá hemos considerado que el usuario o visitante escriba ya sea “correoe” o “contacto” -con todas sus variantes, con o sin extensión (.html o .htm) pero ¿Qué ocurriría si, dado el caso, el navegante escribiera “correo”, es decir, obviara la última letra? En este caso nuestro servidor simplemente devolvería un mensaje de error 404 como este:

Ejemplo de página no encontrada.
Ejemplo de página no encontrada.

Podemos, con propósitos didácticos, ampliar nuestro estudio de Rewrite con un comando adicional: RewriteCond.

Reglas de “RewriteCond”.

El comando “RewriteCond” nos permite evaluar una condición y de ser cierta ejecutará la siguiente e inmediata línea “Rewrite” de lo contrario la saltará y seguira evaluando el resto de las líneas de nuestro fichero .htaccess (si es que hubieran más líneas). Otro punto a tener en cuenta es que se pueden tener varias líneas RewriteCond contínuas una después de la otra y si alguna se cumple se ejecutará la siguiente e inmediata línea “Rewrite”.

Veamos la sintaxis de RewriteCond:

RewriteCond cadena condicion banderas
  • RewriteCond: el comando en sí mismo que debe ser sucedido en la siguiente línea con un comando “RewriteRule”.
  • Cadena: lo que vamos a comprobar, la entrada de datos.
  • Condicion: lo que le vamos a aplicar a la cadena, con la que vamos a comparar y que nos devolverá un valor verdadero o falso.
  • Banderas: modifican el comportamiento de la condicion y va entre corchetes y separadas por comas, si son varias y tal como lo vimos con el comando “RewriteRule”.

Como vemos ya tenemos la teoría, ahora vayamos a la práctica. Seguiremos con nuestro ejemplo anterior: ¿qué sucede si el visitante de nuestra página web escribiera mal la dirección web (URI) y obtuviera un mensaje con error 404?

Podríamos “redirigir” la dirección solicitada hacia la página principal de nuestro servidor web de manera transparente con nuestro archivo .htaccess pero primero una advertencia:


Advertencia imagen pequeña

Esta NO es la manera correcta de tratar una visita a nuestra servidor web que no contenga, por alguna razón (página borrada, error del usuario, etc) una URI solicitada. Lo “legal” es manejar el error 404 con una página web personalizada (de hecho la que vemos es la que trae Apache por defecto) y recomendamos que transcurrido un tiempo necesario para su lectura sea redirigida automáticamente hacia nuestra página web principal (esto escapa a este tutorial pero podéis hacerlo con JavaScript o incluso PHP).

Para configurar nuestra propia página web personalizada para el error 404 podemos hacerla a nuestro gusto/diseño y nombrarla, por ejemplo, “404_personalizado.html” y ubicarla en el directorio raíz para luego agregar a nuestro archivo .htaccess la siguiente línea:

ErrorDocument 404 /404_personalizado.html

Retomando nuestro ejemplo podemos agregar a nuestro .htaccess las siguientes líneas que evaluarán y tratarán la entrada de la URL recibida y la ubicaremos al final del fichero para que sea ejecutada en último lugar (si la colocamos antes al llegar una consulta por “/correoe” automáticamente será redirigida a la página principal):

AddType application/x-httpd-php .html .htm
RewriteEngine on
RewriteRule ^(contacto|contacto.html|correoe)$ contacto.html?origen=$1 [NC,QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ %1

Las 3 primeras líneas ya las hemos explicado y aprendido ahora expliquemos las dos líneas agregadas:

  • “RewriteCond”: El comando que nos permite evaluar una condición.
  • “%{REQUEST_FILENAME}”: El archivo solicitado, luego de haber recibido la URI (con confundir con {REQUEST_URI} ).
  • “!”: es un operador lógico que invierte un valor, por ejemplo de falso a verdadero.
  • “-f”: Le indica a nuestro servidor que compruebe si es un archivo es “regular”, esto es, que esté bien escrito y que exista en nuestro ordenador -y devuelve verdadero si esto se cumple-.
  • Si no se cumple el punto anterior y devuelve falso con el operador “!” lo convertimos en verdadero y tenga la respuesta positiva para ejecutar la siguiente línea “RewriteRule”.
  • “RewriteRule”: con la respuesta positiva recibida comienza a trabajar comparando con el patrón siguiente.
  • “^(.*)$”: los metacaracteres “^” y “$” ya los conocemos, los nuevos son “.” y “*” que indican que compare -y acepte- cualquier caracter y cualquier cantidad de los mismos, es decir, acepte toda entrada posible (el punto indica que puede ser cualquier caracter y el asterisco indica que dicho caracter se puede repetir cualquier cantidad de veces). Como cumple con el patrón procede a aplicar la regla.
  • ¿Recuerdan la variable “$1” que nos permitía elegir que opción de un patrón recibíamos? Ahora con la variable “%1” nos permite pasar intacta la referencia recibida de la “RewriteCond” (la parte que corresponde a nuestro dominio) que desencadenó la ejecución de la “RewriteRule”.

En este punto consideramos prudente mostrar de forma un tanto gráfica la nomenclatura de

Un último ejemplo de “RewriteCond”.

Para finalizar este tutorial vamos a proponernos el bloquear el acceso a una dirección IP4 en particular. Para nuestros propósitos didácticos tenemos una red de área local con nuestro servidor web en 192.168.1.47 (la misma a lo largo de toda esta entrada) y una máquina virtual con la dirección IP4 192.168.1.78 la cual queremos bloquear y para ello agregaremos lo siguiente:


1
2
RewriteCond %{REMOTE_ADDR} ^(192\.168\.1\.78)$
RewriteRule (.*) - [F,L]

Pasamos a explicar línea por línea y comando por comando:

  • “RewriteCond”: comando para evaluar una condición.
  • “%{REMOTE_ADDR}”: una variable que contiene la dirección IP del visitante.
  • “^” y “$”: delimitadores izquierdo y derecho de la dirección IP recibida.
  • “(” y “)”: cadena a evaluar con expresion regular.
  • “\”: la barra invertida sirve para indicarle a nuestro servidor web que el punto a la derecha lo considere como símbolo y NO como metacaracter (ver ejemplo anterior de error 404).
  • Los números pues son la dirección IP a bloquear.
  • “RewriteRule”: es la línea que sirve a la(s) última(s) “RewriteCond”.
  • “(.*)”: indica que acepte cualquier cadena de texto -ver ejemplo arriba-.
  • “-“: un guión para que NO sustituya nada ya que la bandera hará el trabajo por nosotros -ver siguiente punto-.
  • “[F,L]”: son dos banderas con dos instrucciones diferentes, la letra “F” le indica que envie el “mensaje” de prohibido (en realidad devuelve un “error 403” el cual también podemos personalizar con un fichero .html a nuestro gusto y conveniencia tal y como lo hicimos con el “No encontrado 404”). La letra “L” es muy importante e indica que es la última (“last”) regla a aplicar y que no revise las siguientes (si las hubiere) líneas en .htaccess . Esto es así porque si de plano le prohibimos el acceso ¿para qué vamos a revisar reglas adicionales?

La probamos y veremos algo muy similar a esto:

403 Forbidden
403 Forbidden

Si por el contrario queremos que solamente esa dirección IP tenga acceso al servidor web (por ejemplo queremos”administrar” nuestro servidor desde esa única y exclusiva dirección IP, sin que nadie nos moleste) simplemente antecedemos el caracter lógico “!” que inverte la respuesta de la comparación.

En idioma inglés:

Enlaces hacia expresiones regulares:

En idioma japonés:

 

CAPTCHA con PHP.

Un CAPTCHA fácil y sencillo de implementar.

CAPTCHA

Introducción:

Siempre me ha llamado la atención los CAPTCHA (“Completely Automated Public Turing test to tell Computers and Humans Apart“) que hay en muchas páginas web y que permiten diferenciar consultas hechas por humanos de las hechas por “arañas” que pululan por internet (son también conocidas como “robots”). La diferencia esencial en este caso con respecto a la prueba de Turing es que se trata de convencer a una máquina que es un humano, osea el inverso de la prueba.

En el idioma inglés existe la filosofía K.I.S.S. y la practico frecuentemente ya que los sistemas complejos tienden al caos de manera natural. Es por ello que buscando por internet (precisamente ayudado por las “arañas” que pretendo combatir, ¡qué ironía!) he encontrado el siguiente artículo que me deslumbró por su simplicidad más al cabo de una hora de estudio observo los elementos implícitos sin los cuales NO funciona. Recomiendo vayan y lean dicho artículo y regresen cuando lo hayan asimilado, yo por aquí les espero. 😎


El artículo data del año 2007 pero considero que no ha perdido vigencia alguna, si administran una página web bancaria o son partidarios del último grito de la moda, recomiendo desistan de seguir leyendo esto. 😉

Pues bien manos a la obra.

Requisitos previos.

Utilizo una máquina virtual con Debian Wheezy con la cual estudio el programa para base de datos PostgreSQL a la cual le he instalado también un servidor Apache con PHP: este tipo de configuración es llamado LAMP por sus siglas en inglés, sólo que en aquí en realidad es un LAPP: Linux, Apache, PostgreSQL y PHP. Hay muchísimos tutoriales para instalar este tipo de servidores por ello partimos de la base que ustedes cuentan con uno en funcionamiento y para pruebas (no utilicen un servidor “en producción” hasta tanto no estén completamente seguros de este código). Importante recalcar que no utilizaremos funciones de bases de datos en esta entrada, pero muy frecuentemente las CAPTCHA se utilizan en este tipo de ambiente, por ejemplo, registrar usuarios en una lista de correo electrónico (tremenda tentación para los “robots spam”), así que allí encaja perfectamente.

Configuración adicional.

Al servidor LAPP anteriormente descrito debemos instalarle unas librerías para el trabajo gráfico bajo el lenguaje PHP. Aquí está muy bien descrito a lo que me refiero, haciendo la salvedad que dichas librerías NO SIEMPRE están instaladas por defecto.

En todo caso debemos crear una carpeta bajo la raíz web del servidor Apache con el nombre “captcha” y donde alojaremos el siguiente archivo “info.php”:


1
2
3
4
5
&lt;?php
// Archivo "info.php":
// Muestra toda la información, por defecto INFO_ALL
phpinfo();
?&gt;
y luego iremos a nuestro navegador web preferido a la dirección IP que le hayamos asignado en nuestra red de área local. En mi caso dicha dirección es 192.168.1.27 en la carpeta “captcha”:
captcha01En dicha página debemos buscar si están instalados los siguientes valores:
captcha02Dado el caso que no se encuentren instaladas dichas librerías debemos recurrir a la línea de comandos y escribir lo siguiente:
sudo apt-get install php5-gd
y verán algo más o menos parecido a esto (en mi caso no necesité el comando “sudo” porque ya había ganado acceso como root con el comando “su”):
captcha_003
Dichas librerías son necesarias para el trabajo con imágenes jpg, png y gif, entre otros formatos y funciones.

Imagen de fondo para el CAPTCHA:

Necesitaremos un imagen tipo gif con unas dimensiones de 100×30 píxeles con colores adecuados teniendo en cuenta que las letras serán negras buscaremos colores como azul o verde o el que gusten pero no de color negro. Yo elegí la siguiente imagen (aunque al final agregaremos varios elementos para dificutarles la tarea de lectura a los “robots” con técnicas OCR ): bgcaptcha.gifComo ven es una imagen con degradado que busca confundir al OCR pero con legibilidad al ser humano; no obstante usar este mismo fondo siempre y con la ayuda de la “fuerza bruta” se puede descifrar al poco tiempo -pero de eso nos encargaremos luego, de “complicarlo” para tratar de eludir la lectura-. La imagen la guardaremos en la carpeta de trabajo en el servidor web bajo el nombre “bgcaptcha.gif” (nemotécnico ‘BackGroundCaptcha.gif’=’bgcaptcha.gif’).

Para bajar archivos por líneas de comando

recomiendo usar “wget”, por ejemplo:

“wget    http://www.e.com/imagen_a_bajar.gif      nombre_imagen.gif”

Creación del archivo “captcha.php”.

Dicho archivo se encargará de tomar la imagen anterior y, aleatoriamente, “escribirle” letras encimas, almacenarlas en una variable (que leeremos con luego con el método POST) y mostrarla al navegador; para ello utilizaremos el siguiente código que explicaremos línea por línea:

<?php
  //Archivo 'captcha.php'
  session_start();
  $_SESSION['tmptxt'] = randomText(7);
  $captcha = imagecreatefromgif("bgcaptcha.gif");
  $colText = imagecolorallocate($captcha, 0, 0, 0);
  imagestring($captcha, 5, 16, 7, $_SESSION['tmptxt'], $colText);
  header("Content-type: image/gif");
  imagegif($captcha);
  imagedestroy($captcha);
?>
  • Para que el servidor sepa dónde comienza y dónde termina el lenguaje PHP debemos encerrarlo todo con los siguientes comandos: “<?php (…) ?>” y donde van los paréntesis y puntos suspensivos ubicaremos nuestro código a ejecutar. Todo lo que esté fuera de estos demarcadores será considerado lenguaje HTML e interpretado como tal.
  • Cada final de sentencia le será indicado al servidor con un punto y coma “;”.
  • Los comentarios, que son importantísimos para nosotros los seres humanos, para las máquinas carecen de importancia y se identifican con “//” y la derecha el texto explicativo.
  • La función session_start()permite crear o recuperar un identificador único que servirá para que el servidor pueda atender varios clientes al mismo tiempo sin confundir las respuestas de cada usuario. No debemos preocuparnos mucho ya que todo es automatizado, si acaso dedicaremos una sentencia “if-then-else” por si acaso devuelve el valor “falso”, es decir, no se pudo iniciar sesión (todo el mundo da por sentado que devuelve “verdadero”). Importante, muy importante, el comprender sobre el cómo PHP compara dos variables y/o valores para devolver “verdadero” o “falso”, merece su estudio de 10 minutos.
  • La función “$_SESSION[]” permite asignar un valor a una variable en la sesión iniciada en el punto anterior y es un variable de arreglo global. El texto, que será aleatorio, lo proporcionará la función “randomText()” que escribiremos luego.
  • La función “imagecreatefromgif()” nos permitirá “cargar en memoria” la imagen que destinamos como fondo del CAPTCHA y devolverá un “identificador de imagen” representado en una variable que usaremos para agregar el texto que identificará e introducirá el usuario, ser humano. De nuevo digo que deberíamos destinar un “if-then-else” dado el caso la función devuelva “falso”.
  • El comando anterior nos permitió establecer el “lienzo” donde vamos a escribir las letras aleatorias; pues el comando “imagecolorallocate()” nos permite fijar el color con que las “pintaremos”: “0, 0, 0” corresponde al color negro en la codificación de valores “RGB”, “Red Green Blue” y cuyos valores van del 0 al 255 cada uno y nos permite usar +16 millones de colores. Luego echaremos mano de esta función para confundir aún más a los “robots”, por ahora nos conformaremos con el color negro. Es de hacer notar que para esta función CERO es “falso” y cualquier otro valor es “verdadero”, si usted considera esto una tontería le invito a leer la disertación sobre el tema, está avisado o avisada.
  • La funcíon “imagestring()” dibuja una cadena de texto en nuestro “lienzo”, la imagen gif seleccionada. Los parámetros de esta función son: (image, font, x, y, text, color)  y los detallo a continuación:
    1. image: la que cargamos en memoria con la función imagecreatefromgif() y que llamamos $captcha.
    2. font: numeradas del 1 al 5 y es la fuente nativa predeterminada en la librería GD e incluso nos permite cargar nuestras propias fuentes pero debemos cargarlas y compilarlas de acuerdo a la arquitectura de nuestro servidor. Si se entusiasman a realizar esto último deberán cargar dicha fuente “compilada” con la función imageloadfont() de acuerdo a unos valores binarios. Más interesante hallo utilizar la función imagettftext() NO SIN ANTES VERIFICAR el phpinfo() devuelva que el ambiente del servidor lo soporte (si quieren saber más sobre fuentes True Type en GD visiten este enlace): captcha_004
    3. Coordenadas X e Y: tomadas a partir de la esquina superior izquierda, pónganse de cabeza para que las entiendan (ahhh me recuerdo de la materia Geometría Analítica, ¡qué belleza para Autocad! ¡Y dibujábamos por comandos escritos en papel fuera del laboratorio de computación!).
    4. text: en este caso lo que ya tenemos almacenado en la variable global de sesión “$_SESSION[‘tmptxt’]”.
    5. color: el que establecimos a negro con la función “imagecolorallocate()”.
  • La función “header()” permite que nuestro servidor se ciña a las normas del lenguaje HTML que consiste en “notificarle” al navegador web (en formato NO html) que le será enviado un flujo de datos, por defecto “application/octet-stream”, pero que nosotros utilizaremos para indicarle que es una imagen gif “Content-type: image/gif”. Si desean conocer más acerca del nacimiento de la web y sus normas de funcionamiento de la mano del mismísimo Tim Berners-Lee hagan click en este enlace.
  • Por último la función “imagegif()”  instruye a nuestro servidor que le envie la imagen al navegador del usuario, el CAPTCHA que queremos interprete el ser humano.
  • Una función que considero importante y que yo agrego al código mostrado originalmente -y del que desconozco la autoría- es “imagedestroy()” a fin de liberar la memoria utilizada por la variable “$captcha”. Aunque toda la memoria se libera cuando el usuario cierra su navegador o cuando nosotros mismos invocamos “session_destroy()” nunca está demás liberar “trabajo” apenas sea posible.

Creación de la función “randomText()”.

Ahora explicaré la función que devuelve una cadena de texto de manera aleatoria pero con dos mejoras al código fuente original, el cual es el siguiente:

function randomText($length) {
  $pattern = "23456789abcdefghijkmnpqrstuvwxyz";
  for($i=0;$i<$length;$i++) {
    $key .= $pattern{mt_rand(0,32)};
  }
  return $key;
}
  • Las funciones en PHP se declaran con la palabra clave reservada “function” y los argumentos se pasan entre paréntesis separados por comas.
  • Todo el cuerpo de la función esta demarcado por inicio y fin de corchetes “{ … }”.
  • La función toma como parámetro la variable “$lenght” la cual toma el valor de “7” al hacer el llamado de la siguiente manera: “randomText(7)”. Por ende nuestro CAPTCHA tendrá 7 caracteres, recuerden que estamos limitados al espacio del “bgcaptcha.gif”.
  • En la primera variable “$pattern” establecemos los caracteres que queremos mostrar al usuario, no repetidos, y aquí va la primera mejora que les dije antes: NO incluyo el número uno (“1”), ni la letra ele (“l”) ni tampoco el número cero (“0”) ni la letra o (“O”) porque se trata de ponersela difícil al “robot”, no al ser humano, quien puede confundir dichos signos.
  • Utilizamos un ciclo “for” similar al usado en lenguaje C, es decir, un valor de inicio, una condición que se evalúa en cada ciclo y un incremento en cada ciclo. Así establecemos que al escribir “for ( $i=0; $i<$length; $i++ ) {…}” estamos ordenando ejecutar un ciclo basado en la variable “$i” que comienza desde cero y que mientras sea menor que la longitud de caracteres de nuestro CAPTCHA se incremente en una unidad para cada ciclo, “$i++”.
  • Dentro de los corchetes del ciclo “for” anterior colocamos la variable “$key” que se autoconcatena en cada rizo “.=” con una letra aleatoria del patrón elegido y que llevamos asignado en la variable $pattern.
  • En el lenguaje PHP (e igualmente en Python) cada cadena de caracteres es considerada de manera implícita una matriz de una sola fila, esto nos permite “imprimir” en el navegador del usuario una letra del patrón, por ejemplo “echo $pattern{10}” devolverá “b”.
  • ¿Cómo escogemos una letra cualquiera del patrón? Aquí es donde viene la segunda mejora que les comenté: en vez de usar la función “rand()” utilizamos la función “mt_rand()” la cual, aparte de ser más rápida nos permite utilizar números más grandes (de ser necesario). Dicha mejora la propone un usuario llamado “caos30” en los comentarios de la entrada del blog en la cual se inspira este tema. Esta función tiene dos argumentos que nos permiten acotar el número devuelto: para este caso es (0, 32) que es precisamente el largo del patrón que escogimos (el abecedario inglés 26 caracteres más los diez dígitos numéricos son 36 menos 4 que eliminé para evitar confusiones a los humanos).
  • Es necesario el comando “return” acompañado de la variable que debe devolver la función, en nuestro caso es un tipo “cadena de texto”.

Guardado del archivo “captcha.php”.

Ya suficientemente explicado línea por línea el código unimos ambos algoritmos y con el editor de texto nano (o el que ustedes prefieran) guardamos en la carpeta “captcha” que hicimos en “\var\www” (donde generalmente Apache guarda el sitio web):

<?php
  //Archivo 'captcha.php'
  function randomText($length) {
    $pattern = "23456789abcdefghijkmnpqrstuvwxyz";
    for($i=0;$i<$length;$i++) {
      $key .= $pattern{mt_rand(0,32)};
    }
    return $key;
  }
  session_start();
  $_SESSION['tmptxt'] = randomText(7);
  $captcha = imagecreatefromgif("bgcaptcha.gif");
  $colText = imagecolorallocate($captcha, 0, 0, 0);
  imagestring($captcha, 5, 16, 7, $_SESSION['tmptxt'], $colText);
  header("Content-type: image/gif");
  imagegif($captcha);
  imagedestroy($captcha);
?>

Antes de continuar nos aseguramos que la CAPTCHA se ejecute y muestre correctamente en nuestro navegador conectado a nuestro servidor LAPP de pruebas:

captcha_005

Cada vez que pulsamos CTRL+F5 nos mostrará una CAPTCHA diferente cada vez y asigna a la variable “$_SESSION[‘tmptxt’]” el valor deberá comparar con el valor introducido por el usuario humano. El siguiente paso es hacer la interfaz para la presentación e introducción de los datos.

Formulario web “captchademo.php”.

El uso básico de sesiones bajo PHP está explicado en detalle en este enlace. No obstante buscando la simplicidad para la comprensión de todos ustedes, amables y pacientes lectores, haremos un archivo php para mostrar la CAPTCHA e introducir la respuesta del usuario y otro archivo php donde compararemos la respuesta; a este archivo lo llamaremos “captchaanswer.php” y esta explicado más adelante.

Sin más pretensiones de seguridad ni estética escribiremos el siguiente código:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>CAPTCHA con PHP</title>
<meta name="description" content="CAPTCHA con PHP: ejemplo para demostrar la creacion de Captcha con PHP." />
</head>
<body>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
 <tr>
   <td align="center">
     <strong>CAPTCHA con PHP </strong><br>
     Ingresar el texto mostrado en la imagen: <br>
     <form action="captchaanswer.php" method="post">
       <img src="captcha.php" width="100" height="30" vspace="3"><br>
       <input name="tmptxt" type="text" size="30"><br>
       <input name="btget" type="submit" class="boton" value="Enviar">
       <input name="action" type="hidden" value="checkdata">
     </form>
   </td>
 </tr>
</table>
</body>
</html>

Al navegar nuestro servidor de prueba podrán ver algo parecido a esto:

captcha_006Todo el código anterior es puro lenguaje HTML y mencionaré brevemente línea por línea y su cierre correspondiente (nota: los términos que usaré para describir lo que sucede probablemente hiera la sensibilidad académica de algún lector, no obstante recuerdenme y escríbanme si estoy equivocado) :

  • Etiquetas “<html>…</html>”: le indica a nuestro servidor dónde comienza y dónde termina el lenguaje HTML, veremos en una entrada futura que esto es esencialmente útil en nuestros “scripts php”.
  • Etiquetas “<head>…</head>”: contiene información prioritaria que nuestro servidor tendrá que darle tratamiento especial, leer próximo punto.
  • Etiquetas “<meta (…) />”: estas etiquetas contienen datos que describen datos y están categorizados. La categoría “Content-Type” la describimos anteriormente pero en esta ocasión la usaremos para decirle al navegador del cliente que la vamos a enviar lenguaje HTML y que lo interprete como tal. Envuelve mayor complejidad una solicitud HTTP pues incluye códigos numéricos de respuesta y aceptación pero con saber que esta información es una de las primeras que se envían es más que suficiente.
  • También en los metadatos podemos incluir la codificación de caracteres utilizados en la página web (algún día cuando usemos todo en 128 bits esto caerá en desuso) por medio del comando “UTF-8”.
  • Hay ciertas palabras claves reservadas en los metadatos y es lenguaje de alto nivel, el que usamos usted y yo, seres humanos. Uno de ellos es la palabra name=”description” que nos permite especificar a manera general el propósito principal de la página.
  • En este punto es útil preguntarnos ¿para qué es todo esto del encabezado? Para ello debemos recordar que hace 20 años el ancho de banda en internet era “oro en polvo” y el protocolo se diseñó para no cargar la página completa de un solo golpe sino que echaramos “un ojo” antes de descargar completo. Hoy en día se ha vuelto obsoleto (yo escucho una radio por internet, mientras corro una máquina virtual que actualiza su software desde varios repositorios y otra máquina virtual “baja” parches de seguridad mientras escribo estas líneas ‘en línea’ y además de tanto en tanto superviso servidores de datos remotos; el tráfico de datos es asombroso) pero aún sigue siendo útil para las “arañas” o buscadores como https://duckduckgo.com/  a fin de categorizar las páginas web visitadas y de las cuales hasta guardan una copia espejo, datos, datos y más datos; discos duros a reventar (alguien dijo alguna vez “lo que se sube a internet allí se queda”, yo lo creo fehacientemente 😯 ).
  • Con las etiquetas “<title>…</title>”, valga la redundancia, titulamos nuestra página (el “caption” de la ventana) y algunos navegadores agregan su propio nombre como pueden ver el ejemplo mostrado.
  • Dentro de las etiquetas “<body>…</body>” insertaremos la página web en sí, sólo que en este caso es un formulario para mostrar e introducir los datos.
  • Con “<table><tr><td>…</td></tr></table>” dibujaremos una tabla de un solo cuadro ayudados por el concepto de anidación de etiquetas ¿recuerdan el método de la “doble C” para dividir fracciones? Pues bueno es algo parecido a eso.
  • Con “<strong>…</strong>” podremos poner en negritas lo escrito entre las etiquetas.
  • Con “<br>” le decimos al navegador que termina una línea y comience una nueva.
  • He aquí lo más importante: “<form>…</form>”. Allí declaramos y establecemos los elementos del formulario en sí.
  • Con form <form action=”captchaanswer.php” method=”POST”> indicamos que las variables siguientes sean pasadas a otro “script php” con el método “POST” para que el usuario no visualize las variables(a diferencia del método “GET”).
  • He aquí la “magia”: al pasar la orden de visualizar la imagen CAPTCHA realmente le indicamos es que ejecute el “script”: <img src=”captcha.php” width=”100″ height=”30″ vspace=”3″> y que lo muestre como tal (con ayuda de “header()”).
  • Los demás elementos son para dibujar el cuadro de texto y el botón enviar, creo no merecen mayor explicación.
  • Las normas estéticas las agregaremos en una próxima entrada pero de manera normalizada, como el tema es largo y quiero escribirlo desde hace tiempo le dedicaré una entrada aparte.

Actualizado el lunes 22 de febrero de 2016.

Para prevenir que los servidores proxy instalados en redes de área local NO guarden en caché la imagen de nuestro CAPTCHA podemos hacer uso de generar diferentes nombres al “archivo” de imagen misma con, por ejemplo, la función uniquid de la siguiente manera:

<?php
  echo "<img src='";
  echo uniqid("captcha", true);
  echo ".php' width='100' height='30' vspace='3'><br>";
?>

Formulario web “captchaanswer.php”

Como ustedes pueden ver loq ue yo llamo “formularios web” o “páginas web” en este servidor LAPP son en realidad unos “scripts” en lenguaje PHP. El que nos ocupa ahora es el que se invoca cuando el usuario pulsa el botón “Enviar”:

<?php
    session_start();
    if ($_POST['action'] == "checkdata") {
        if ($_SESSION['tmptxt'] == $_POST['tmptxt']) {
            echo "Bienvenido.";
        } else {
            echo "Intentalo nuevamente.";
        }
        exit;
    }
?>

De nuevo lo analizamos línea por línea obviando, por supuesto, las funciones y comandos que de nuevo encontremos y hayamos explicado antes:

  • Es necesario llamar a la función “session_start()”, recordemos dicha función también nos permite recuperar los datos de la sesión almacenados en la variable global.
  • La próxima acción es verificar que la variable ‘action’ esté establecida a ‘checkdata’  de no ser así pues simplemente finaliza y sale sin aviso alguno.
  • El punto decisivo es la comparación de la cadena aleatoria creada por el script “captcha.php” y compararla con la enviada por el usuario. De ser iguales emite el mensaje “BIENVENIDO” y aquí es donde podemos ubicar el código que nos interesa: la publicación de un comentario a una entrada de blog, la confirmación a una lista de correos, etc.

Conclusiones.

Esto es sólo un abreboca de lo que podemos realizar con PHP en cuanto a gráficos se refiere, estoy consciente que para las normas actuales es un CAPTCHA bastante débil pero lo podemos mejorar hasta llevarlo a un nivel básico. Para mi caso particular lo voy a implementar de una vez en un “servidor de producción” y que trabaje de una vez.

<Eso es todo, por ahora>.