systemadmin.es > LAMP y web > Configuración segura de Apache para WordPress

Configuración segura de Apache para WordPress

A raíz del post “Listar los plugins de WordPress” en “Un informático en el lado del mal”, vamos a ver una configuración de Apache que evite revelar la presencia de los plugins y otras opciones.

La configuración del VirtualHost debería ser similar a la siguiente, recortando algunas partes irrelevantes:

<VirtualHost *:80>
    ServerAdmin webmaster@dummy-host.example.com
    DocumentRoot "/var/www/blog/htdocs/"
    ServerName systemadmin.es
    DirectoryIndex index.php

        <Directory /var/www/blog/htdocs/>
            Options FollowSymLinks
            AllowOverride None
            Order deny,allow
            Allow from all
        </Directory>

  	<Files wp-login.php>
		Order Deny,Allow
		Allow from 1.2.3.4
		Deny from All
	</Files>

        <Directory /var/www/blog/htdocs/wp-admin>
            Options FollowSymLinks
            AllowOverride None
            Order Deny,Allow
            Allow from 1.2.3.4
            Deny from All
        </Directory>

	<Filesmatch  ^wp-config.php$>
		Deny from all
	</Filesmatch>

	<Directory /var/www/blog/htdocs/wp-content/plugins>
		DirectorySlash Off
	</Directory>

RewriteEngine On

RewriteCond %{REQUEST_URI} !/(about|wp-includes|wp-content|images|wp-admin|wp-login)/
RewriteCond %{REQUEST_URI} !/(about|google|sitemap|wp-comments-post.php|wp-login.php|favicon.ico|xmlrpc.php|buscador.xml)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</VirtualHost>

Las partes importantes son:

  • Deshabilitar mod_autoindex: Se trata de un modulo de Apache para hacer listados del contenido de los directorios.

    Personalmente prefiero eliminar dicho modulo de apache al instalarlo mediante la opción de configure –disable-autoindex. Podemos comprobar si esta cargado el modulo mediante httpd -M:

    # /usr/local/apache22/bin/httpd -M | grep index
    Syntax OK
    
  • Deshabilitar el .htaccess: Independientemente por la perdida de rendimiento que supone el htaccess, supone un riesgo permitir que un fichero pueda modificar la configuración de apache, por esto lo deshabilitamos mediante:
    AllowOverride None
    

    La configuración que tengamos en el .htaccess la deberemos mover a la configuración del VirtualHost. Típicamente los rewrites, a los que deberemos añadir la siguiente regla antes de cualquier RewriteCond o RewriteRule:

    RewriteEngine On
    
  • Restricciones por IP en wp-login.php y wp-admin: En el caso que sea un blog personal lo podemos limitar a nuestra IP o si la tenemos dinámica siempre podemos usar OpenVPN para restringir el acceso, además de cifrar las comunicaciones. En la configuración de Apache indicamos mediante el Allow que IPs permitimos:
    Order Deny,Allow
    Allow from 1.2.3.4
    Deny from All
    
  • Proteger archivos sensibles como por ejemplo el wp-config.php: Los ficheros con configuración del blog, por ejemplo los datos de conexión al MySQL no son nunca servidos, sino que se leen desde otro PHP. Así, podemos denegar que sean servidos.

    En condiciones normales, aunque sean servidos por el Apache, sería una página en blanco pero en el caso que falle el modulo de PHP (o exista un error en al configuración del Apache referente al PHP) se podría servir el contenido del fichero sin ejecutar. Esto pasó, ya hace años, a un proveedor de alojamiento gratuito muy conocido en España publicándose un montón de contraseñas.

    La configuración que evita que dicho fichero sea servido es:

    <Filesmatch  ^wp-config.php$>
    	Deny from all
    </Filesmatch>
    
  • Evitar la detección de plugins de WordPress mediante el script http-wp-plugins para nmap. Sin añadir ninguna opción adicional no detecta los plugins ya que en el script vemos lo siguiente:
        local target
        if wp_root then
          -- Give user-supplied argument the priority
          target = wp_root .. "/wp-content/plugins/" .. line .. "/"
        elseif wp_autoroot then
          -- Maybe the script has discovered another WordPress content directory
          target = wp_autoroot .. "wp-content/plugins/" .. line .. "/"
        else
          -- Default WP directory is root
          target = "/wp-content/plugins/" .. line .. "/"
        end
    

    Realiza una búsqueda del nombre del plugin seguido de una barra, dando siempre 404. Pero el modulo mod_dir si que añade una diferencia que permite detectar si un directorio existe. Por defecto, si el directorio existe pero no le hemos añadido una barra final hace un redirect al directorio con la barra:

    # curl -I systemadmin.es/wp-content/plugins/akismet
    HTTP/1.1 301 Moved Permanently
    Date: Mon, 04 Jul 2011 19:55:28 GMT
    Server: Apache
    Location: http://systemadmin.es/wp-content/plugins/akismet/
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    
    # curl -I systemadmin.es/wp-content/plugins/akismet/
    HTTP/1.1 404 Not Found
    Date: Mon, 04 Jul 2011 19:55:30 GMT
    Server: Apache
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    
    

    Por el contrario, si el directorio no existe no realiza este redirect:

    # curl -I systemadmin.es/wp-content/plugins/noexisto_luego_existo
    HTTP/1.1 404 Not Found
    Date: Mon, 04 Jul 2011 19:55:34 GMT
    Server: Apache
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    
    # curl -I systemadmin.es/wp-content/plugins/noexisto_luego_existo/
    HTTP/1.1 404 Not Found
    Date: Mon, 04 Jul 2011 19:55:36 GMT
    Server: Apache
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    

    Lo que evita que para esta configuración sean detectados los plugins. Pero si modificamos el plugin de nmap para que no incluya la barra final si que los podremos detectar con el mismo diccionario:

        local target
        if wp_root then
          -- Give user-supplied argument the priority
          target = wp_root .. "/wp-content/plugins/" .. line
        elseif wp_autoroot then
          -- Maybe the script has discovered another WordPress content directory
          target = wp_autoroot .. "wp-content/plugins/" .. line
        else
          -- Default WP directory is root
          target = "/wp-content/plugins/" .. line
        end
    

    Con esta modificación podremos ver como sí que encuentra los plugins instalados dentro del directorio plugins (que no significa que esten habilitados):

    $ nmap -p80 --script=http-wp-plugins --script-arg 'http-wp-plugins.root="/",http-wp-plugins.search=all' systemadmin.es
    
    Starting Nmap 5.59BETA1 ( http://nmap.org ) at 2011-07-04 19:41 CEST
    Nmap scan report for systemadmin.es (87.98.227.154)
    Host is up (0.044s latency).
    rDNS record for 87.98.227.154: jordi.prats.systemadmin.es
    PORT   STATE SERVICE
    80/tcp open  http
    | http-wp-plugins: 
    | search amongst the 14170 most popular plugins
    (...)
    |   akismet
    (...)
    
    Nmap done: 1 IP address (1 host up) scanned in 62.73 seconds
    
    

    Esta característica que hace detectables los plugins la podemos deshabilitar mediante la opción DirectorySlash. Por lo que podemos añadirla exclusivamente para el directorio de plugins con:

    <Directory /var/www/systemadmin.es/htdocs/wp-content/plugins>
    	DirectorySlash Off
    </Directory>
    

    Una vez aplicados los cambios en el Apache podremos comprobar como el redirect ha desaparecido, comportándose igual con un directorio que existe:

    # curl -I systemadmin.es/wp-content/plugins/akismet
    HTTP/1.1 404 Not Found
    Date: Mon, 04 Jul 2011 19:59:46 GMT
    Server: Apache
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    
    # curl -I systemadmin.es/wp-content/plugins/akismet/
    HTTP/1.1 404 Not Found
    Date: Mon, 04 Jul 2011 19:59:49 GMT
    Server: Apache
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    

    Que con uno que no existe:

    # curl -I systemadmin.es/wp-content/plugins/noexisto_luego_existo 
    HTTP/1.1 404 Not Found
    Date: Mon, 04 Jul 2011 19:59:53 GMT
    Server: Apache
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    
    # curl -I systemadmin.es/wp-content/plugins/noexisto_luego_existo/
    HTTP/1.1 404 Not Found
    Date: Mon, 04 Jul 2011 19:59:54 GMT
    Server: Apache
    Vary: Accept-Encoding
    Content-Type: text/html; charset=iso-8859-1
    

    Por lo que dicho script ya no detecta que plugins de WordPress están instalados:

    $ nmap -p80 --script=http-wp-plugins --script-arg 'http-wp-plugins.root="/",http-wp-plugins.search=all' systemadmin.es
    
    Starting Nmap 5.59BETA1 ( http://nmap.org ) at 2011-07-04 20:03 CEST
    Nmap scan report for systemadmin.es (87.98.227.154)
    Host is up (0.038s latency).
    rDNS record for 87.98.227.154: jordi.prats.systemadmin.es
    PORT   STATE SERVICE
    80/tcp open  http
    |_http-wp-plugins: nothing found amongst the 14170 most popular plugins, use --script-arg http-wp-plugins.search=<number|all> for deeper analysis)
    
    Nmap done: 1 IP address (1 host up) scanned in 62.83 seconds
    
  • Evidentemente, si el diccionario se modificara para buscar ficheros que deben estar presentes en el caso que el plugin este instalado (ficheros de estilo, imagenes…) esto no serviría de nada.

Gracias a estas modificaciones en la configuración del VirtualHost de Apache complicaremos un poco más la identificación y explotación de vulnerabilidades, con la esperanza que se busquen un objetivo más fácil.

11 comments to “Configuración segura de Apache para WordPress”

  1. Muy interesante, también leí y soy fan de la web que comentas. Gracias.

  2. Muy buen artículo Jordi. Felicidades.

  3. Hola. Magnífico post, felicidades. Para evitar mostrar el listado de directorios, tambien existe la opcion chapuza pero válida de crear un index.php vacío en las raices de los directorios que no queremos listar; (no recuerdo si habia que hacer algo más…).
    Para la protección de archivos sensibles, también vale el uso de chattr en servidores bajo linux.

    Salu2 desde Wasesores.com

  4. Hombre, en lugar de crear un index.php vacío se puede usar IndexIgnore

  5. Muy interesante, gracias Jordi

  6. Muchas gracias, uno que se lo pasa bien jugueteando con este tipo de herramientas 🙂

  7. Hola

    pues yo probando con un blog, le añadi en su configuración de virtual host el trozo de código para el tema de los plugins y paso a devolverme un 200 :S Es decir, sin tocar nada, hago

    curl -I etetc/akismet

    HTTP/1.1 301 Moved Permanently

    edito el fichero de VirtualHost, añado el DirectorySlash OFF y me devuelve un 200

    :S ¿alguien entiende porque?

  8. Tendria que ver toda la config, seguramente por algún directoryindex?

  9. Estaba pensando que según que plugins podríamos adicionalmente prohibir cualquier acceso HTTP al directorio de plugins de una forma total o permitiendo el acceso a contenidos estáticos (CSS, JS, HTML / XML)

    Saludos

  10. Sería para aplicar en conjunto con lo que mencionas en el post. Por un lado dificultar la identificación de los plugins y por otro fortificar un software que puede ser más proclive a fallos de programación que la rama principal de wordpress.

  11. El problema es se debería afinar dependiendo de los plugins instalados, aquí hablo en genérico

Deja un comentario:

XHTML - Tags permitidos:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>