Ayer tuve el placer de participar en un webinar junto a Dabo @daboblog, Diego Mtnz Castañeda @n1mh, Oscar Reixa @oreixa. de Apachectl dentro de los webinars de desarrolloweb, en el que charlamos sobre servidores cloud y dedicados. Creo que es un webinar interesante para los que tengan dudas sobre que tecnología aplicar para sus desarrollos.

 

Os recomiendo echar un ojo al canal de desarrolloweb.com porque hay una cantidad de información alucinante sobre muchísimos temas tanto de desarrollo como de diseño.

Todos los que tenemos algún wordpress y revisamos los logs periódicamente vemos que continuamente hay accesos por POST al xmlrpc.php. Algunas veces son ataques y otras son simples consultas de bots. En cualquier caso son accesos que no me valen para nada y generan tráfico innecesario.

Hace poco revisando unos logs vi miles de accesos por POST desde una ip de Ucrania y con el user agent de GoogleBot. No se que quería pero seguro que nada bueno. 🙂

Lo que recomiendan en muchos sitios de borrar ese fichero a mi no me convence por que si actualizas el wordpress lo vuelves a tener ahí y hay veces que se hace un uso legítimo de ese fichero para, p.e. publicar post desde programas externos.

Una característica habitual de los bots es que hacen las peticiones por HTTP/1.0 en vez del habitual HTTP/1.1 así que aprovechando esta característica he preparado este pequeño código que bloquea las peticiones por POST y HTTP1.0 al xmlrpc.php. Obviamente si el bot trabaja con HTTP/1.1 esto no funcionará.

Lo más interesante de este código es que para evitar ifs anidados concateno dos valores en una variable. Si se cumple la primera condición el valor de la variable es “bot” y si se cumple la segunda añado a lo que tenga la variable el valor “post”, de esa forma sólo devuelve 403 si las dos variables cumplen la condición. Este método permite construir tantos ifs anidados como quieras, simplemente hay que añadir más valores a la cadena.

## Block xmlrpc.php POST http1.0
  location ^~ /xmlrpc.php {
    set $mata_bot 0;
    if ($server_protocol ~* "HTTP/1.0") {
        set $mata_bot "bot";
        }
    if ($request_method = POST ) {
        set $mata_bot "${mata_bot}-post";
        }
    if ($mata_bot = "bot-post"){
       return 403;
       }
   }

Espero que le sea útil a alguien más.

Información sobre ataques de fuerza bruta con xmlrpc.php


80legs es un webcrawler que tiene la molesta costumbre de visitar todas las páginas de tu sitio web con cientos de visitas simultáneas desde diferentes direcciones ip con el resultado de que pueden llegar a tirarte abajo tu servidor, algo muy parecido a un ataque DDOS pero con barniz de legalidad.

La forma de identificarlos es revisando los logs del servidor donde podrás ver que el useragent de la visita es:
Mozilla/5.0 (compatible; 008/0.83; http://www.80legs.com/webcrawler.html) Gecko/2008032620

En su página te dicen que si no deseas que rastreen tu sitio web debes de añadir en el robots.txt lo siguiente:

User-agent: 008
Disallow: /

Mi experiencia es que muchas veces pasan olímpicamente del robots.txt o también puede darse el caso de que no puedas cambiar nada en ese fichero por lo que lo único que te queda para luchar con ellos es a través del servidor.

En mi caso el problema se me presentó en un servidor donde hay mucho tráfico y en el que utilizo cherokee como servidor web. Para solucionarlo lo que hice fue filtrar las visitas que me llegan con el useragent de 80legs.

Para hacerlo sólo hay que añadir una regla de tipo “header” con la cadena 008 ó 80legs.

Y después hacer lo que quieras con esas visitas: redireccionarlas a una página estática, mostrar un error (en el ejemplo un error 509 – Bandwidth Limit Exceeded – ó un 404 – página no encontrada – ) o simplemente mandarlos al carajo (un DROP).


Actualización
Si quieres bloquearlos desde un servidor con apache lo único que tienes que hacer es añadir estas líneas al fichero .htaccess de tu sitio web:

RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} (80legs)
RewriteRule ^(.*)$ – [F]

Si por cualquier razón necesitáis bloquear el tráfico de un servidor desde algún país en concreto en http://www.countryipblocks.net/ mantienen una lista actualizada de los rangos de ips de cada país.

Puedes descargar varios formatos, incluso uno preparado para insertar directamente en un fichero .htaccess, aunque a mi no me parece el mejor formato ya que obligas a que el servidor web revise una lista de ips que puede llegar a ser muy grande antes de que muestre la página y eso podría hacer que la navegación se enlenteciera demasiado, además un servidor web no es el más adecuado para realizar esa función.

Os pongo varios ejemplo de como realizar el bloqueo desde la línea de comandos. Para los ejemplos he utilizado korea, no se os ocurra utilizar rangos de ips de el país en el que estáis por que os podéis bloquear el acceso. Como diría un amiguete, “no seáis gañanes

Con iptables:

.
/usr/bin/wget -O /tmp/pais.txt https://www.countryipblocks.net/e_country_data/KR_cidr.txt; for i in $(/bin/cat /tmp/pais.txt | /bin/grep -v '#'); do sbin/iptables -A INPUT -s $i -j DROP; done; /bin/rm /tmp/pais.txt

Con apf:

.
/usr/bin/wget -O /tmp/pais.txt https://www.countryipblocks.net/e_country_data/KR_cidr.txt; for i in $(/bin/cat /tmp/pais.txt | /bin/grep -v '#'); do /etc/apf/apf -d $i; done; /bin/rm /tmp/pais.txt

ACTUALIZACIÓN
countryipblocks.net ya no permite descargar esos ficheros así que ahora utilizo el listado de maxmind.com

Como lo dan en otro formato he tenido que cambiar la rutina que lo proceso, ahora en una sola línea, aunque tiene el inconveniente de que necesitas tener instalada la aplicación ipcalc (sudo apt-get install ipcalc) para calcular el cidr.
Tened en cuenta que sólo permite descargar la base de datos unas pocas veces al día.

En esta rutina bloquea todo el tráfico que viene de china (CN)

curl http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip | funzip | grep CN | tr -d '"' | awk '{split($0,a,","); print "ipcalc -r "a[1]" "a[2]" | head -2 | tail -1"}' | sh | awk '{print "iptables -A INPUT -s "$0" -j DROP"}' | sh

Ayer detecté un error poco frecuente en un servidor web con apache.
Después de una caida del servidor, cuando intentaba iniciar el apache este no arrancaba y en el log aparecía el error.
(28)No space left on device: Couldn’t create accept lock (/var/lock/apache2/accept.lock.xxxxx) (5)

Espacio en disco duro no falta, así que el problema era otro por lo que revolviendo por internet vi que, por suerte, no era el primero que le pasaba algo así y que ese error se achacaba a un problema con el array de semáforos.

Al hacer un

ipcs -s

se veía que tenía un montón de semáforos abiertos por el usuario del apache cuando este no está funcionando.
La solución pasa por eliminar todos los semáforos abiertos por el usuario del apache y ya se puede reiniciar sin problemas.

Si los liberas a mano puedes utilizar:

ipcrm -s proceso

O si lo prefieres automatizar un poco:

for i in `/usr/bin/ipcs -s | /bin/grep www-data | /usr/bin/awk '{print $2}'` ; do /usr/bin/ipcrm -s $i; done

Ahora sólo me queda averiguar por que se llena el array de semáforos y no se libera.

Muchas veces cuando instalas en un servidor algún plugin o algún cms, como prestashop, te crean permisos de lectura, escritura y ejecución que no son los más adecuados para la seguridad del servidor. Personalmente me molesta especialmente encontrarme con directorios y ficheros con 777.

Modificar a mano una cantidad muy grande de ficheros y directorios puede requerir mucho tiempo así que he preparado un pequeño script en python que hace esas modificaciones por mi. Sólo hay que cambiar el string ruta_a_explorar por el path y el sólo modifica los ficheros y directorios dejando los permisos 644 para los ficheros y 755 para los directorios.

Espero que os sea útil.

#! /usr/bin/env python
# -*- coding: utf-8-*-
import os, sys, stat
 
 
ruta_a_explorar="/ruta/a/explorar/"
 
for root,dirs,files in os.walk(ruta_a_explorar):
        for file in [f for f in files]:
                pp=os.path.join(root, file).replace("""\\""",'/')
                if stat.S_ISDIR(os.stat(pp)[stat.ST_MODE]):
                        os.chmod(pp,stat.S_IRWXU|stat.S_IWUSR|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH)
                elif stat.S_ISREG(os.stat(pp)[stat.ST_MODE]):
                        os.chmod(pp,stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
 
        for dir in [f for f in dirs]:
                pp=os.path.join(root, dir).replace("""\\""",'/')
                print pp
                if stat.S_ISDIR(os.stat(pp)[stat.ST_MODE]):
                        os.chmod(pp,stat.S_IRWXU|stat.S_IWUSR|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH)
                elif stat.S_ISREG(os.stat(pp)[stat.ST_MODE]):
                        os.chmod(pp,stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)