Tag Archives: nginx

Weak TLS cipher suites

HTTP and HTTPS are well known Internet protocols that don’t require any introduction. The other day at work as part of a daily security scan one of our servers got tagged as using weak cipher suites during TLS negotiation. In this quick post I’ll explain what a weak cipher suite means and how to fix it.

There are many tools out there to check if you are following the security best practices when it comes to SSL/TLS server configuration (supported versions, accepted cipher suites, certificate transparency, expiration, etc.) but one of my favorites is https://www.ssllabs.com/ssltest/analyze.html and drwetter/testssl.sh.

SSLlabs.com is easy to use, you just have to enter a Hostname and the website will analyze all possible TLS configuration and calculate a score for you, this tool will also tell you what you can do to improve that score.

There are many TLS protocol versions: 1.0, 1.1, 1.2, 1.3. The first two are considered insecure and should not be used so I will focus on 1.2 and 1.3 only.
In my case SSLlabs.com was complaining about weak cipher suites were supported for TLS 1.2:

The above report is showing ECDHE-RSA-AES256-SHA384E and ECDHE-RSA-AES256-SHA as weak cipher suites.

First let’s clarify a couple of things, according to Wikipedia:

Cipher suite: A set of algorithms that help secure a network connection.

So a weak cipher suite will be algorithms with known vulnerabilities that can be used by attackers to downgrade connections or other nefarious things.

Fixing this is very easy and will require changing a line or two on the server configuration, deploying the changes and then testing again using SSLlabs.com.

Of course, the above only applies if you know exactly what you are doing, otherwise it will take you many attempts and you are going to waste precious time.

Reproduce and fix the issue locally

Suppose you have the same issue I had, because this issue was reported on an Nginx Server now the task is to reproduce the issue locally and come up with a fix. With modern technologies like docker containers it’s very easy to run a local Nginx server, the only thing you need is a copy of the nginx.conf, to run a docker container the command will be:

docker run --name mynginx -v ./public.pem:/etc/nginx/public.pem -v ./private.pem:/etc/nginx/private.pem -v ./nginx.conf:/etc/nginx/nginx.conf:ro -p 1337:443 --rm nginx

The above command is telling docker to run an Nginx container; binding the local port 1337 to the container port 443 and also mounting the public.pem (public key), private.pem (private key) and nginx.conf (the server configuration) files inside the container.

The nginx.conf looks like this:

    server {
        listen 443 ssl;
        server_name www.alevsk.com;
        ssl_certificate /etc/nginx/public.pem;
        ssl_certificate_key /etc/nginx/private.pem;
        ssl_protocols TLSv1.3 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
        ssl_ciphers EECDH+AESGCM:EECDH+AES256:EECDH+CHACHA20;
        ssl_session_cache shared:TLS:2m;
        ssl_buffer_size 4k;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;
    }

You can test the server is working fine with a simple curl command:

curl https://localhost:1337/ -v -k
*   Trying 127.0.0.1:1337...
* Connected to localhost (127.0.0.1) port 1337 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
....
....
....
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Server: nginx
< Date: Sun, 15 May 2022 23:14:23 GMT
< Content-Type: text/html
< Content-Length: 146
< Connection: keep-alive
< Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
< 
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host localhost left intact

The next step is to reproduce the report from https://www.ssllabs.com/ but that will involve somehow exposing our local Nginx server to the internet and that’s time consuming. Fortunately there’s an amazing open source tool that will help you to run all these TLS tests locally.

drwetter/testssl.sh is a tool for testing TLS/SSL encryption anywhere on any port and the best part is that runs on a container too, go to your terminal again and run the following command:

docker run --rm -ti --net=host drwetter/testssl.sh localhost:1337

The above command will run the drwetter/testssl.sh container, the –rm flag will automatically delete the container once it’s done running (keep your system nice and clean), -ti means interactive mode and –net=host will allow the container to use the parent host network namespace.

After a couple of seconds you will see a similar result as in the website, something like this:

Testing server’s cipher preferences.

Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.   Encryption  Bits     Cipher Suite Name (IANA/RFC)                        
-----------------------------------------------------------------------------------------------------------------------------  
SSLv2                                                                                                                          
 -                                                                                                                             
SSLv3                                                                                                                          
 -                                                                                                                             
TLSv1                                                                                                                          
 -                                                                                                                             
TLSv1.1                                                                                                                        
 -                                                                                                                             
TLSv1.2 (server order)                                                                                                         
 xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH 521   AESGCM      256      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384               
 xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH 521   AESGCM      128      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256               
 xc028   ECDHE-RSA-AES256-SHA384           ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384               
 xc014   ECDHE-RSA-AES256-SHA              ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA                  
 xcca8   ECDHE-RSA-CHACHA20-POLY1305       ECDH 521   ChaCha20    256      TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256         
TLSv1.3 (server order)                                        
 x1302   TLS_AES_256_GCM_SHA384            ECDH 256   AESGCM      256      TLS_AES_256_GCM_SHA384                              
 x1303   TLS_CHACHA20_POLY1305_SHA256      ECDH 256   ChaCha20    256      TLS_CHACHA20_POLY1305_SHA256                        
 x1301   TLS_AES_128_GCM_SHA256            ECDH 256   AESGCM      128      TLS_AES_128_GCM_SHA256

Even on this test we see the weak cipher suites (ECDHE-RSA-AES256-SHA384 and ECDHE-RSA-AES256-SHA).

Great, now you are able to fully reproduce the issue locally and test as many times as you want until you have the perfect configuration. It’s time to do the actual fix.

Open the nginx.conf file one more time and locate the line that starts with ssl_ciphers and just add !ECDHE-RSA-AES256-SHA384:!ECDHE-RSA-AES256-SHA at the end, ie:

 server {
        listen 443 ssl;
        server_name www.alevsk.com;
        ssl_certificate /etc/nginx/public.pem;
        ssl_certificate_key /etc/nginx/private.pem;
        ssl_protocols TLSv1.3 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
        ssl_ciphers EECDH+AESGCM:EECDH+AES256:EECDH+CHACHA20:!ECDHE-RSA-AES256-SHA384:!ECDHE-RSA-AES256-SHA;
        ssl_session_cache shared:TLS:2m;
        ssl_buffer_size 4k;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;
    }

If you run the Nginx container with the new configuration and then run the drwetter/testssl.sh test again this time you will see no weak cipher suites anymore.

TLSv1.2 (server order)                                                                                                         
 xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH 521   AESGCM      256      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384              
 xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH 521   AESGCM      128      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256              
 xcca8   ECDHE-RSA-CHACHA20-POLY1305       ECDH 521   ChaCha20    256      TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256        
TLSv1.3 (server order)          
 x1302   TLS_AES_256_GCM_SHA384            ECDH 256   AESGCM      256      TLS_AES_256_GCM_SHA384                              
 x1303   TLS_CHACHA20_POLY1305_SHA256      ECDH 256   ChaCha20    256      TLS_CHACHA20_POLY1305_SHA256                        
 x1301   TLS_AES_128_GCM_SHA256            ECDH 256   AESGCM      128      TLS_AES_128_GCM_SHA256

Conclusion

Congratulations, you just fixed your first security engineer issue and now you can push the fix to production. In general when it comes to fixing any kind of problem in tech it is better to start by reproducing the issue locally and work on a fix from there (of course this is debatable if you are facing an issue that only happens in a specific environment). SSL/TLS  and cipher suites are one of those technologies that you have to learn by heart or at least have a very good understanding if you want to work in application security, but not only that, once you understand it it will completely change the way you approach problems and debug security applications .

Happy hacking.

Docker 101 #1: Introducción a docker y los contenedores

docker-image

Hola lectores, en los últimos 6 meses he tenido la oportunidad de estar desarrollando mi carrera en una de las empresas de tecnología más grandes que hay en México, he estado trabajando muy de cerca en temas de Cloud computing, virtualizacion, bare metal e IaaS en general.

Es por eso que he decidido que es una buena idea crear una serie de tutoriales sobre docker, herramienta que considero esencial para los desarrolladores hoy en día, sobre todo si te atrae el mundo del cloud computing :). Si no tienes conocimientos previos de docker no te preocupes, planeo escribir tutoriales desde cero y voy a ir explicando cosas un poco más complejas conforme vayamos avanzando.

Un poco de teoria

Cuando hablamos de docker hablamos de contenedores. Pero ¿Que es un contenedor?, seguramente podrás encontrar una definición más formal de lo que es, pero imagínate que un contenedor es una caja que contiene tu solución de software, y no solo eso, también contiene las dependencias necesarias para ejecutar tu aplicación, las dependencias pueden ser librerías, configuraciones especiales e incluso otras aplicación o servicios que necesites (como un servidor web, nginx, apache, tomcat, etc.), todas las dependencias y aplicaciones dentro de una imagen están organizadas mediante un concepto de layers (capas), de esa manera cuando modificas un contenedor (una imagen) solo actualizas un layer en específico.

La gran ventaja de los contenedores es que, a diferencia de las máquinas virtuales estos no tienen asignadas cuotas específicas de recursos del sistema host (memoria, cpu, storage, etc), cuentan con un sistema de archivos virtual que permite que los contenedores se ejecuten de forma independiente y separada de los procesos del sistema, de esta manera la memoria de un proceso del contenedor no interfiere con un proceso de la maquina donde es ejecutado.

Un contenedor solo incluye consigo la aplicación y sus dependencias lo que hace que las imágenes de docker sean bastante livianas.

Otro de los grandes beneficios que nos aportan los contenedores es la potabilidad, me refiero a que si tienes una aplicación y la quieres migrar a otro sistema (por ejemplo de desarrollo a producción) puedes creas una imagen de docker que incluya tu solución y ejecutarla en cualquier otro sistema teniendo la certeza de que va a “correr” pues la imagen contiene todas las dependencias necesarias. Existen técnicas para “comunicar” nuestra maquina host con los contenedores como el mapeo de puertos y directorios, eso lo veremos en los siguientes tutoriales.

Conceptos básicos

  • Docker: Tecnología de software para creación y administración de contenedores.
  • Docker image: Un sistema de archivos virtual que puede contener aplicaciones y dependencias.
  • Docker container: Una imagen de docker que está siendo ejecutada, una instancia de una imagen.
  • Dockerhub: Un repositorio que contiene muchísimas imágenes de docker listas para ser descargadas.
  • DockerFile: Un script que indica una serie de pasos para construir una imagen de docker.

Bien suficiente teoría, si quieres saber más a fondo sobre docker pueden visitar el sitio web o ir a la documentación oficial

Instalar docker

Lo primero que debemos hacer es instalar docker, dependiendo de tu sistema operativo es el instalador que utilizaras, descarga docker de la página oficial, si estas en Windows descarga el ejecutable y sigue el wizard (siguiente, siguiente, siguiente), en Mac OSX puedes descargar una imagen dmg y hacer lo mismo, en mi caso lo que tengo a la mano es un sistema Linux, Ubuntu para ser específico y para proceder con la instalacion lo hago de la siguiente forma:

[bash]
$ sudo apt-get install docker.io
[/bash]

Sea cual sea tu sistema operativo, una vez hayas instalado docker para verificar que la herramienta está bien instalada abre una consola y escribe el comando docker

[bash]
$ docker
[/bash]

docker1

Si el resultado es un output similar al de la imagen significa que instalaste docker correctamente, si por el contrario recibes algún mensaje que dice que el comando docker no existe esto se puede deber a varias razones pero principalmente si estas en Windows verifica que la ruta al binario de docker se encuentre definida en tus variables de entorno.

Docker contiene muchisimos comandos pero los más importantes, o al menos los que utilizaras más son:

  • $ docker run
  • $ docker images
  • $ docker build
  • $ docker pull
  • $ docker ps
  • $ docker start
  • $ docker stop
  • $ docker commit
  • $ docker attach

Conforme vayamos avanzando en los tutoriales iré explicando que hace cada uno de ellos

Nuestro primer contenedor

Estamos listos para crear nuestro primer contenedor, abrimos una consola y escribimos el siguiente comando:

[bash]
$ sudo docker run hello-world
[/bash]

El comando anterior le dice a docker que ejecute una nueva instancia (un contenedor) de la imagen hello-world, primero busca en el repositorio local y si no la encuentra va al dockerhub y procede a con la descarga.

docker2

¿Observas la parte que dice Pull complete?:

[bash]
Unable to find image ‘hello-world:latest’ locally
latest: Pulling from hello-world

264eca88cf85: Pull complete
f0cb9bdcaa69: Pull complete
Digest: sha256:548e9719abe62684ac7f01eea38cb5b0cf467cfe67c58b83fe87ba96674a4cdd
Status: Downloaded newer image for hello-world:latest
[/bash]

Ahi es donde docker está mostrando el progreso de la descarga y los layers de la image, el resultado final de ejecutar este contenedor es el mensaje que dice: Hello from Docker!

[bash]
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker Hub account:
https://hub.docker.com

For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
[/bash]

Si ejecutamos el comando docker images, obtendremos una lista de las imágenes que tenemos disponibles localmente, y claro ahí tenemos nuestra imagen hello-world

[bash]
$ docker images
[/bash]

docker3

Ahora veremos uno de los conceptos importantes de docker, el sistema de archivos virtual, vamos a descargar y ejecutar una imagen docker de ubuntu con el comando:

[bash]
$ sudo docker run ubuntu
[/bash]

docker4

Corroboramos que tenemos una nueva imagen almacenada localmente:

[bash]
[email protected]:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu latest 426844ebf7f7 2 weeks ago 127.1 MB
hello-world latest f0cb9bdcaa69 3 months ago 1.848 kB
[/bash]

Ya tenemos una imagen docker de ubuntu, ¿Pero cómo accedemos a ella? ¿Cómo la utilizamos?, podemos utilizar el siguiente comando para acceder al contenedor en tiempo de ejecución utilizando una shell interactiva:

[bash]
$ sudo docker run -t -i ubuntu /bin/bash
[/bash]

Cuando el contenedor este corriendo podrás navegar su sistema de archivos como lo harías normalmente en Linux, incluso si estas corriendo docker desde una maquina con Windows podrás ver que el sistema de archivos es de Linux, aquí es donde puedes empezar a considerar la opción de dejar atrás Cygwin y comenzar a utilizar un contenedor de ubuntu con todas las herramientas que necesites.

docker5

Para salir del contenedor utiliza el comando exit, como si terminaras una sesión remota de ssh.

Un punto importante a recalcar es que los contenedores no son persistentes, si creas un archivo dentro del contenedor la siguiente vez que lo ejecutes no existirá, posteriormente veremos cómo podemos solucionar eso. Por el momento quiero que entiendas los conceptos básicos de los contenedores en docker, como descargar imágenes y lanzarlas, los comandos básicos, etc.

Al inicio mencionaba el dockerhub, el repositorio público de donde puedes descargar miles de imágenes de docker, te invito a explorarlo e instalar las que más te gusten:

docker6

Servidor web nginx utilizando docker

Para terminar el tutorial mostrare rápidamente como podemos ejecutar un servidor web utilizando docker, como mencionaba, el dockerhub tiene miles de imágenes públicas y muchas comunidades de software libre están creando versiones “contenerizadas” de sus soluciones, en este caso el servidor web nginx, lo primero que debemos hacer es descargar la imagen de nginx para docker

[bash]
$ sudo docker pull nginx
[/bash]

docker7

Ejecutamos docker images para verificar que se descargó correctamente:

[bash]
[email protected]:~$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu latest 426844ebf7f7 2 weeks ago 127.1 MB
nginx latest 4c0e7e3661d2 2 weeks ago 181.4 MB
hello-world latest f0cb9bdcaa69 3 months ago 1.848 kB
[/bash]

Ahora para lanzar el contenedor utilizaremos el comando:

[bash]
$ sudo docker run –name nginx-server1 -p 80:80 nginx
[/bash]

docker8

  • El comando docker run especifica que queremos correr un contenedor
  • El parametro –name nos permite definir un nombre único y amigable para esa instancia
  • El parametro -p nos permite mapear puertos entre el sistema operativo y los servicios que corren dentro del contenedor
  • Al final especificamos el nombre de la imagen de la cual queremos crear el contenedor, nginx en este caso

docker9

Incluso podemos abrir una segunda terminal y ejecutar el siguiente comando para lanzar un segundo servidor web contenerizado pero en un puerto diferente:

[bash]
$ sudo docker run –name nginx-server2 -p 8080:80 nginx
[/bash]

docker10

Al lanzar cada una de las imágenes de nginx habrás notado que la consola se queda “ocupada” corriendo el contenedor, en el siguiente tutorial mostrare como evitar eso, finalmente para detener la ejecución del contenedor presiona ctrl+c

Si ejecutas el comando docker ps -a podras ver todas los contenedores que hemos creado hasta el momento, la mayoria no estara en ejecucion y puede ser eliminado utilizando docker rm [CONTAINER ID]

docker11

Si haz entendido bien los conceptos básicos ya te imaginaras el potencial de docker y hacia donde iré en los siguientes tutoriales :).

Saludos y happy hacking.