Archivo por meses: mayo 2015

DNSSEC. Asegurando las respuestas de nuestro dominio. La práctica (II)

Validar tu implementación de DNSSEC

Si seguiste las indicaciones de mi post anterior, ahora tendrás tu dominio bajo DNSSEC. En este momento es vital que te asegures de que todo está correcto, ya que si tu dominio «anuncia» (mediante la publicación de un registro DS en el registrador) que soportas DNSSEC, pero la implementación es errónea (claves no corresponden al DS, firmas caducadas, …) tu dominio dejará de ser visible para aquellos que usen DNSSEC (que cada día serán más). Es decir, adiós a tu correo, tu web, etc…

Para comprobar que todo está OK podemos usar servicios web como:

  • http://dnssec-analyzer.verisignlabs.com/ Que lista todos los pasos para validar el soporte DNSSEC de tu dominio.
  • http://dnsviz.net/ Que visualiza la relación (firmas) entre las claves desde el dominio raíz hasta el tuyo. Además de comprobar que está correcto, ayuda a entender la relación entre los diferentes (sub-)dominios y sus claves.

Si somos más de consola, los siguientes comandos nos permitirán comprobar que nuestro dominio valida correctamente:

## Primero guardamos las claves (KSK y ZSK) de la zona raíz (.)
$ dig . DNSKEY | grep -Ev '^($|;)' > root.keys

## Luego hacemos la validación de la cadena completa (desde la raíz
## hasta nuestro dominio)
$ dig +sigchase +trusted-key=./root.keys inittab.net. SOA

Autopsia de un registro DNSSEC

Una vez seguros de que nuestra configuración de DNSSEC es correcta, nos queda un asunto que tratar… el periodo de validez de la zona firmada! Como comenté en el post anterior, la zona se firma para un periodo de tiempo limitado, por defecto 30 días. Es necesario por tanto volver a firmar antes de que caduque, ya que en caso contrario… adiós web, correo, etc… Veamos un ejemplo de registro firmado:

inittab.net. 21569 IN A 91.121.65.176 
inittab.net. 21569 IN RRSIG A 14 2 21600 20150521144640 20150421144640 34840 inittab.net. uY8......

Primero vemos el RR de tipo A para inittab.net (la IP a la que apunta). Y luego tenemos el RR de tipo RRSIG (Resource Record SIGnature) que le acompaña permitiendo verificar la validez del RR de tipo A. Analicemos los campos del RRSIG:

Después de IN RRSIG, encontramos el tipo de registro al que se refiere. En este caso: A. Luego sigue el algoritmo con el que está firmado 14 (ECDSAP384SHA384). El 2 indica el número de etiquetas (net e inittab). Si fuera para un nombre tipo foo.bar.inittab.net. el valor sería 4. Detrás tenemos el TTL original, 21600. El siguiente campo es el más relevante para tema que nos preocupa, la fecha de caducidad de la firma 2015052114464021 de mayo de 2015, a las 9 de la noche (UTC). Antes de esa fecha deberemos volver a firmar la zona o las respuestas de nuestro DNS serán interpretadas como inválidas. La siguiente fecha indica cuando se realizó la firma del registro 21 de abril de 2015, y con que clave (ZSK en este caso) se hizo, 34840 de inittab.net. Por último estará la firma propiamente dicha.

Automatizando el proceso de firma

Ya sabemos cuando caducan las firmas de nuestra zona, 2015-05-21-14:46:40 en este ejemplo. Sólo queda automatizar el proceso de mantener las firmas vigentes. Para ello podemos usar los comandos del paquete dnssec-tools, o un par de sencillos shell scripts. El primero (check_rrsig), de Hauke Lampe, comprueba la fecha de caducidad de las firmas y el segundo, de un servidor, firma la zona en caso de que sea necesario:

#!/bin/bash
## (c) 2015 Alberto González Iniesta. GPLv2
## Depends: /usr/local/bin/check_rrsig

## Configuración
#DOMINIOS="foo.com bar.net baz.org"
DOMINIOS="inittab.net"

SERVIDOR_DNS=localhost
DIR_ZONAS="/etc/bind"
DIR_CLAVES="${DIR_ZONAS}/keys"
##

SALIDA=0

renueva_firma() {
  echo -n "Renovando $1 .."
  cd "$DIR_ZONAS"
  SERIE_ACTUAL=$( dig +short @$SERVIDOR_DNS $1 soa | awk '{print $3}' )
  (( SERIE_NUEVO = SERIE_ACTUAL + 1 ))
  rndc freeze # por si la zona es dinámica
  SERIE_EN_FICHERO=$( cut -d';' -f1 $1 | awk 'BEGIN {ORS=" "} /SOA.*\(/,/\)/ ' | tr '\t' ' ' | tr -s ' ' | cut -d'(' -f2 | awk '{print $1}' )
  sed -i "s/$SERIE_EN_FICHERO/$SERIE_NUEVO/" $1
  dnssec-signzone -d "$DIR_ZONAS" -K "$DIR_CLAVES" -S -t -u -3 $( dd if=/dev/random bs=16 count=1 2>/dev/null | hexdump -e \"%08x\" ) -o $1 "${DIR_ZONAS}/$1"
  rndc thaw # por si la zona es dinámica
  if rndc reload > /dev/null 2>&1 ; then
    echo "OK"
  else
    echo "ERROR"
    SALIDA=1
  fi
}

for DOMINIO in $DOMINIOS ; do
  OUTPUT=$( /usr/local/bin/check_rrsig -n $SERVIDOR_DNS -z $DOMINIO -w +10days )
  [ $? -eq 0 ] || renueva_firma $DOMINIO
done

exit $SALIDA

Una entrada en cron para que se ejecute todos los días y listo!. El script supone que los ficheros de zona se llaman como el ORIGIN (dominio), es decir para el dominio inittab.net el fichero se llama inittab.net, y supone que está en /etc/bind. Así como que las claves se encuentran en /etc/bind/keys. Sin duda es mejorable y se aceptan parches. Pero si lo hago de esta manera es porque para aprender siempre es mejor usar el mínimo de magia. Un día migraré a dnssec-tools, que entre otras cosas gestiona el cambio (rotación) de claves (KSK y ZSK), proceso que habría que hacer con cierta periodicidad. Pero si has llegado hasta aquí ya tienes una base de DNSSEC para seguir solito 🙂

NSEC vs NSEC3. Evitando la enumeración de zona

Para tener un sistema seguro de DNS es necesario firmar respuestas negativas, evitando que alguien pueda negar la existencia de un registro por ti. Es decir, con DNSSEC una respuesta que indique que un registro no existe, también tiene que venir firmada.

No se puede firmar siempre lo mismo, es decir no se puede firmar un texto que diga «eso no existe«, ya que una respuesta negativa podría ser usada por un atacante para negar la existencia de registros que si existen (enviando la respuesta firmada «eso no existe» a un cliente). Véase Replay Attack

Tampoco podemos generar una firma por cada registro no existente, por ejemplo firmar «estonoexiste.inittab.net y estotampoco.inittab.net y …. ya que el coste computacional (firmar miles de preguntas sobre registros no existentes) sería enorme, además de requerir la clave (ZSK normalmente) en todos los servidores de la zona (el primario y todos los secundarios). Mucha exposición para la clave.

NSEC al rescate. Se ordenan los registros de la zona en orden alfabético y se crear un registro NSEC por cada dos registros consecutivos. Esto permite devolver un registro NSEC (con su correspondiente RRSIG) por cualquier pregunta al DNS de algo que no exista entre los dos registros que cubre el NSEC. Es decir, si tenemos bar.inittab.net, foo.inittab.net y nada entre ellos, habrá un registro NSEC con esta forma:

bar.inittab.net. NSEC foo.inittab.net. A RRSIG NSEC

Que se devolverá cuando se pregunte por cochino.inittab.net o demente.inittab.net o eclectico.inittab.net. El problema de NSEC es que permite enumerar la zona completa. Es decir, preguntando por entradas no existentes podemos ir obteniendo los registros existentes mediantes las respuestas NSEC. Y podrían desvelarse registros «no públicos», tipo desarrollo.inittab.net, que aunque el DNS no garantiza que sean secretos, con este sistema sería trivial descubrirlos.

Para evitar este problema se crea NSEC3. La idea es más o menos la misma, pero con un cambio importante. Primero se hace un hash de todas las entradas de la zona y son estos hash los que se ordenan y los que componen una respuesta NSEC3:

LCFQO0AOA1QL3Q7A8E0ONQ9T2PTRLFS3.inittab.net. 86400 IN NSEC3 1 0 10 874201500AC7C3CFDAB802EDD07EEFFD ( VPE6NRVJK9AI2SUFEJG8I0G36C1ANBGS CNAME RRSIG )

NSEC3 no evita, mediante ataques por fuerza bruta/diccionarios, que se termine por enumerar la zona. Pero lo hace bastante más complejo, sobre todo si se usan salts aleatorias (en el ejemplo anterior el campo que empieza por 87420….). De ahí las opción -3 y los dd/hexdump en el comando dnssec-signzone recomendado en éste y el anterior post. De hecho ya hay planes de un NSEC5 que solvente la posible enumeración con NSEC3

$ exit

DNSSEC. Asegurando las respuestas de nuestro dominio. La práctica (I)

Ingredientes

Para ofrecer DNSSEC en las respuestas de nuestro dominio, garantizando a los usuarios/aplicaciones la validez de éstas, necesitamos:

– Acceso al panel de control del registrador del dominio. Donde informaremos el registro DS (Delegation Signer) que indicará cual es nuestra clave (KSK) en la zona padre de nuestro dominio (normalmente .com.net.org, etc…). Aunque en los dominios .es ya hay soporte para DNSSEC, en el registrador que yo uso en la actualidad (abril 2015) no hay posibilidad de informar el DS, y por tanto no hay DNSSEC para mis .es.

– Control de tu DNS primario. Supongo que los que usen su registrador, o su empresa de hosting, para gestionar su dominio tendrán la posibilidad de usar DNSSEC. Este artículo está dirigido a aquellos que administran su servidor DNS (bajo GNU/Linux claro, y usando Bind).

Pasos a seguir

Aunque existen utilidades (dnssec-tools) que facilitan todos los pasos que seguiremos, he optado (como suelo hacer) por usar las herramientas más «básicas», ya que mi objetivo siempre es aprender como funcionan todas las piezas y no depender de «mágia» que lo haga todo por mi. Así que lo único que necesitaremos es tener instalado Bind en nuestro servidor DNS, lo que posiblemente ya tenemos salvo que usemos otro software. En ese caso este tutorial sólo será orientativo.

Antes de nada, deberemos asegurarnos que nuestro Bind está configurado para soportar y validar DNSSEC.

Después crearemos las claves KSK y ZSK. Compuestas, respectivamente, de una clave privada que usaremos para firmar la clave ZSK y la zona, y de una clave pública que irá informada en el fichero de zona de nuestro dominio.

Posteriormente firmaremos la zona de nuestro dominio con las claves creadas y publicaremos, mediante el panel de control de nuestro registrador, el registro DS correspondiente a nuestra KSK. Ésto permitirá a otros servidores DNS validar la información proveniente de nuestros servidores DNS.

En el siguiente post veremos como comprobar que todo está correcto y nuestra zona está siendo servida con DNSSEC además de establecer un mecanismo de firmado periódico de la zona, ya que ésta sólo se firma con una validez finita (normalmente un mes).

Configuración de Bind para soportar DNSSEC

Dos son las opciones principales que controlan el soporte DNSSEC en Bind:

// dnssec-enable, indica soporte DNSSEC como servidor autoritativo
// de un dominio (en los DNS primario/secundario)
dnssec-enable yes;
// dnssec-validation, que activará la validación de respuestas DNSSEC
// como servidor recursivo (en el DNS que usan nuestras máquinas como resolver)
dnssec-validation yes;

Si bien se supone que el valor por defecto de estas opciones es «yes» desde la versión 9.5 de Bind, en versiones 9.7, como la que viene en Debian Squeeze, he tenido que añadir explicitamente dichas opciones a la sección «options» en /etc/bind/named.conf.options, junto con un «include» del fichero /etc/bind/bind.keys. Resultando en un named.conf.options como el siguiente:

options {
          // opciones que vienen de serie...
          .....
          // añadido lo siguiente:
          dnssec-enable yes;
          dnssec-validation yes;
};
// fuera de "options" también añadido:
include "/etc/bind/bind.keys";

En Debian Wheezy y posteriores no es necesario tocar nada de la configuración de Bind, funciona «out-of-the-box».

Para comprobar que nuestro servidor DNS recursivo (el que usa nuestro portátil/servidor/… para resolver nombres) tiene soporte DNSSEC podemos hacer alguna de las siguientes pruebas:

  •  Usar dig para obtener una respuesta clara y sencilla:
$ dig +short test.dnssec-or-not.net TXT | tail -1
"No, you are not using DNSSEC"
## ^^ En este caso no tenemos soporte DNSSEC
  • Usar dig para comprobar los flags en la respuesta del DNS recursivo:
## La opción +dnssec hace que dig incluya en la query el flag
## 'do' (DNSSEC OK)
## El flag 'ad' (Authenticated Answer) debe estar presente en la respuesta
$ dig +dnssec . SOA | grep flags
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 25
##        ^^^^^^^^^ En este caso no hay soporte DNSSEC

## En el siguiente si lo hay:
$ dig +dnssec . SOA | grep flags
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 13, ADDITIONAL: 1
  •  Preguntar por el dominio www.dnssec-failed.org. Que es un dominio con una configuración DNSSEC inválida. Este es un ejemplo interesante, ya que con un DNS con soporte DNSSEC correcto, no deberíamos obtener una respuesta válida, y por tanto no ser capaces de ver la web. Mientras que si nuestro DNS no está validando correctamente, obtendremos una IP y podremos ver la web.
# Si nuestro DNS recursivo no usa (correctamente) DNSSEC:
$ host www.dnssec-failed.org
www.dnssec-failed.org has address 69.252.193.191
www.dnssec-failed.org has address 68.87.109.242

# Si nuestro DNS usa correctamente DNSSEC:
$ host www.dnssec-failed.org
Host www.dnssec-failed.org not found: 2(SERVFAIL)
  • Existen plugins/add-ons/extensiones/como-lo-quieran-llamar-este-año para los navegadores que te indican si las webs a las que llegas están «verificadas» desde el punto de vista DNSSEC. Por ejemplo DNSSEC/TLSA Validator para Firefox.

Una vez estamos seguros de que tenemos soporte DNSSEC en nuestro DNS recursivo, y habiendo activado la opción ‘dnssec-enable‘ en los servidores autoritativos de nuestros dominios si son más antiguos que la versión 9.5 de Bind, podemos continuar.

Creación de las claves de firmado

Antes de crear las claves, vamos a poner unos permisos en el directorio /etc/bind que permitan que el propio demonio firme las zonas en caso de necesidad (por actualización dinámica de la zona, o caducidad de la misma), y a crear un directorio para guardar las claves:

# chmod 2775 /etc/bind
# mkdir /etc/bind/keys
# chmod 2770 /etc/bind/keys

Como ya comenté en el post anterior, crearemos dos claves. La primera, KSK (Key Signing Key), será usada para firmar la ZSK (Zone Signing Key), permitiendo cambiar esta última de forma periódica de forma sencilla y sin realizar cambios en el registrador del dominio:

$ dnssec-keygen -K /etc/bind/keys/ -a RSASHA256 -b 4096 \
              -n ZONE -3 -f KSK inittab.net

Este comando creará un par de claves en el directorio (-K/etc/bind/keys. El nombre de los ficheros será algo como Kinittab.net.+008+51772.private o .key. Donde el fichero .private contiene la clave privada y el .key la pública. El «+008» indica el algoritmo elegido (-a RSASHA256) y el «+51772» el identificador de la clave (lo usaremos luego en el registrador). Con «-b 4096» elegimos el tamaño de la clave a generar, 4096 es una buena opción para la KSK. «-n ZONE» indica que la clave será usada para trabajar con zonas DNSSEC, «-3» que usaremos NSEC3 (hablaré de ello más adelante) y por último «-f KSK» indica el tipo de clave. Lo único que podríamos querer variar:

  • Añadir «-r /dev/urandom» para que use este dispositivo para la obtención de números pseudo-aleatorios, en vez de /dev/random. Esto hará que la generación de la clave sea mucho más rápida, pero más débil desde el punto de vista criptográfico (mucho que explicar para un sólo post). Para pruebas podéis usar /dev/urandom, para algo «serio» mejor no usarlo.
  • Cambiar el algoritmo «-a«. En la página de manual de dnssec-keygen (8), tenéis las posibilidades. En la wikipedia también. Mi consejo es que no vayáis a lo último, porque podría suceder que algún servidor DNS no tenga implementado el algoritmo (por ejemplo yo elegí ECDSAP384SHA384 para pruebas y preparar este post, en abril de 2015, y me he dado con versiones anteriores de Bind que no lo digieren. Fail!). Mejor evitar MD5 y SHA1.
  • Cambiar el tamaño de la clave «-b«. En la actualizad (2015) 4096 bits para la KSK y 2048 bits para la ZSK son  tamaños recomendables.

Si nos fijamos en el fichero .key, veremos una entrada de zona DNS (DNSKEY en este caso) con la siguiente información:

inittab.net. IN DNSKEY 257 3 8 AwEA......
# 257 indica que se trata de una KSK
# 3 es el protocolo, y siempre tiene ese valor [RFC4034]
# 8 es el algoritmo elegido [lista en la wikipedia]. En este caso RSA/SHA-256
# AwEA.....Y el resto será la clave pública
# La única diferencia con el .key de la ZSK es que el primer campo tendrá
# un valor de 256 (en vez de 257)

Después crearemos la ZSK, con un comando prácticamente igual al anterior, en este caso con un tamaño de clave algo menor (2048 bits) y sin «marcar» como KSK:

$ dnssec-keygen -K /etc/bind/keys/ -a RSASHA256 -b 2048 \
              -n ZONE inittab.net

Una vez más obtendremos dos ficheros (un .private y un .key) del tipo Kinittab.net.+008+50946. Donde, 008 es el algoritmo y 50946 el identificador/etiqueta de la clave.

Firmando el fichero de zona

Ahora ya podemos firmar nuestra zona DNS. En mi caso el fichero se llama inittab.net y se encuentra en /etc/bind. Ese fichero es el que seguiré modificando cuando quiera hacer cambios en mi zona, sólo que después de los cambios habrá que volver a firmar y recargar la zona.

$ dnssec-signzone -d /etc/bind/ -K /etc/bind/keys/ -N "increment" -S \
   -3 $( dd if=/dev/random bs=16 count=1 2>/dev/null | hexdump -e \"%08x\" ) \
   -o inittab.net /etc/bind/inittab.net

El resultado de este comando será un fichero /etc/bind/inittab.net.signed. Veamos el por qué de las opciones que he usado:

  • -d directorio de trabajo, donde dejar la zona firmada
  • -K directorio con las claves generadas anteriormente
  • -N «increment», esta opción indica que debe aumentarse el número de serie de la zona además de firmarla. Es decir, el fichero .signed tendrá un número de serie (en el SOA) mayor al del fichero original. Yo no soy amigo de esta opción. Como hay que seguir editando el fichero original para realizar los cambios en la zona, es allí donde aumento el número de serie. Así que os podéis ahorrar esta opción.
  • -S, mi opción favorita. Se trata de «Smart«, es decir, que él busca las claves (en el directorio indicado), sabe (por el 257/256 que vimos antes) cual es la KSK y la ZSK, las usa correctamente (firmando los RR tipo DNSKEY con la KSK y el resto con la ZSK), las incluye en el nuevo fichero generado, limpia y abrillanta. En el caso de que las claves se generen con periodos de validez (eso para otro post), se encargaría de gestionar la rotación por caducidad de las mismas. ¡Una maravilla, oiga!
  • -3 SALT. Genera los registros NSEC3 de los que hablaré en el próximo post. Éstos son necesarios para que no se pueda enumerar por completo la zona. Como están basados en un hash, la «salt» varía el valor del hash para que no pueda inferirse su valor original, por ejemplo con diccionarios. En este comando uso /dev/random y hexdump para generar una «salt» completamente aleatoria. Si no queremos usar «salt«, podemos especificar un guión (-3 –) o incluso poner una fija (en hexadecimal, 32 carácteres…)
  • -o inittab.net. Indica el ORIGIN de la zona, es decir el dominio (para que no lo tenga que deducir del nombre del fichero de zona, aunque en este ejemplo sea obvio). Así que se trata de una opción importante.
  • /etc/bind/inittab.net. El fichero original de la zona. El destino tendrá el mismo nombre, con la extensión .signed (duh!).

Es importante notar que la firma de los registros tiene caducidad. Por defecto 30 días desde su creación. Este dato es vital, ya que pasados 30 días nuestro dominio dejará de validar correctamente y los DNS que tengan soporte para DNSSEC (hoy casi todos) dejarán de aceptar como válidas nuestras respuestas DNS, es decir, desapareceremos de Internet… LOL! En el próximo post (ahí, creando tensión) explicaré alguna de las posibilidades para mantener nuestras zonas firmadas al día. Si queremos modificar el periodo de validez de la firma, podemos usar la opción -e. Por ejemplo, el valor por defecto es: -e now+30D (30 Días desde ahora). Valga, por ahora, decir que basta ejecutar el mismo comando de firma de zona otra vez para generar las firmas (con su validez de 30 días) una vez más.

Una vez tenemos la zona firmada, lo único que nos queda en el servidor es servir la zona firmada en vez de la original. Es decir, en la configuración del DNS cambiar:

zone "inittab.net" {
 type master;
 file "/etc/bind/inittab.net";
 .....
};

Por:

zone "inittab.net" {
 type master;
 file "/etc/bind/inittab.net.signed";
 .....
};

Y recargarla con: rndc reload inittab.net

Informando la KSK en el registrador

Una vez nuestro servidor DNS está sirviendo la zona firmada, nos queda el último paso. Añadir el «enlace» entre la zona padre (.net en este ejemplo) y la nuestra. Es decir, informar (en el registrador) de cual es nuestra KSK, para que las respuestas de nuestros DNS puedan ser validadas correctamente.

Para ello iremos al panel de control de nuestro dominio y rellenaremos los datos del registro DS (Delegation Signer). En mi caso mi registrador es Joker.com y el formulario correspondiente tiene esta pinta:

En la entrada DS pondremos el hash de nuestra KSK

En la entrada DS pondremos los hash de nuestra KSK

¿De dónde sacamos la información para el registro DS? Si nos fijamos, después del comando de firmado que vimos anteriormente (dnssec-signzone), en nuestro directorio /etc/bind, tendremos un fichero llamado dsset-inittab.net, cuyo contenido es:

inittab.net. IN DS 51772 8 1 6397....5B
inittab.net. IN DS 51772 8 2 581E....D8....

Donde 51772 es el identificador/etiqueta de la clave (campo keytag), el 8 el el algoritmo (alg) correspondiente a la clave (recordad que en este ejemplo era RSA/SHA-256) y el 1 y 2 las funciones hash usadas (digest type) para crear cada resumen de la clave (digest), que es el chorro hexadecimal del final. Es decir, para una misma clave (KSK en este caso) se generan dos registros DS, con diferentes funciones de resumen (1 es SHA-1 y 2 es SHA-256). En el registrador podemos informar sólo una, aunque lo ideal es tener las dos. Si por algún motivo el fichero dsset-dominio.foo no se ha creado, podemos hacerlo partiendo del componente público de la clave KSK con el comando dnssec-dsfromkey:

$ ddnssec-dsfromkey Kinittab.net.+008+51772.key

Espero que, a estas alturas de la (larga) lectura, tengas tu dominio funcionando correctamente con DNSSEC. En el próximo post veremos como comprobarlo, hablaré de NSEC3 y de como automatizar el firmado de las zonas antes de que las firmas caduquen.

$ exit