Common NGINX Misconfigurations

Common NGINX Misconfigurations

Veja também em vídeo aqui:
https://youtu.be/0Kn9RmUByXQ


Aposto que você já teve que criar algum arquivo de rotas usando NGINX, seja para distribuir assets de uma pasta específica, seja para redirecionar o tráfego para outra aplicação interna.

Contudo, todavia, entretanto, se você acabar comentendo alguns destes 3 erros clássicos, você pode acabar com um set de vulnerabilidades bem facinho aí.

1 - Missing Root

Todo arquivo de NGINX pode ter um ponto de partida onde ele vai procurar por arquivos. Para isto, utilizamos o root:

server {
    listen       80;
    server_name  localhost;
    root /etc/nginx; # <-- AQUI

    location /home/ {
        alias   /usr/share/nginx/html/;
        index  home.html home.htm;
        try_files $uri $uri/ =404;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Porém perceba que em nossas configurações, não temos nenhum location que aponta para /.

A falta desta configuração faz com que o NGINX tenha a permissão de entregar qualquer arquivo que esteja dentro da pasta definida no root (/etc/nginx).

Ou seja, se você fizer uma request para `/nginx.conf`, o NGINX vai procurar por este arquivo dentro do diretório root. Se encontrar, vai entregar para você.

=== REQUEST ===
curl http://localhost/nginx.conf

=== RESPONSE ===
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;

    # include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/conf.d/server.conf;
}

Para corrigir este problema, basta definir uma rota raiz. Ela pode redirecionar para o index do site ou outra página que deseje.

server {
    listen       80;
    server_name  localhost;
    root /etc/nginx;

    location / { # <-- AQUI
        return 302 https://my-site-index.com;
    }

    location /home/ {
        alias   /usr/share/nginx/html/;
        index  home.html home.htm;
        try_files $uri $uri/ =404;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

2 - Alias Traversal

Um erro bastante comum e inclusive fácil de cometermos é expormos os arquivos do servidor por causa da falta de um /.

Veja a configuração a seguir:

server {
    listen       80;
    server_name  localhost;

    location /health {
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }

    location /assets {
        alias /var/assets/;
    }

    location / {
        root   /usr/share/nginx/html;
        index  home.html home.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Justamente onde temos o /assets , estamos mapeando que todo request recebido neste path, devemos buscar por resources dentro de /var/assets/ .

Até aí tudo bem, mas o problema aqui é justamente usarmos /assets e não /assets/ .

A falta desta / ao final, permite que um atacante busque por outros arquivos no servidor, usando um traversal (../).

Com isso, deixamos nosso server vulnerável a ataques como este aqui:

curl http://mysite.com/assets../log/nginx/access.log

Onde o NGINX vai permitir que uma busca a nível mais alto na hierarquia de diretorios seja realizada, concendo acessos à outros arquivos na qual não deveriam ser expostos externamente.

Para corrigir isso, basta colocar uma / ao final:

# ❌ NOOOP
location /assets {
    alias /var/assets/;
}

# ✅ YEEEP
location /assets/ {
    alias /var/assets/;
}

3 - Unsafe $uri

O terceiro e um dos mais legais na minha opinião é você ser capaz de adicionar header qualquer na resposta de uma requisição via URL.

Simples:

  1. Você monta uma URL maliciosa.

  2. Manda para a vítima (qualquer técnica de phishing).

  3. Ela clica.

  4. Faz um request para o server.

  5. O server responde com seu Header customizado.

  6. Se a aplicação confia nos headers de resposta do servidor e faz algo com ela, boooom 💀.

Veja a configuração a seguir:

server {
    listen       80;
    server_name  localhost;
    root  /usr/share/nginx/html/;

    location / {
        return 302 http://localhost/v2$uri;
    }

    location /v2/ {
        return 200 "Hello\n";
        add_header Content-Type text/plain;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Perceba onde mapeamos o location / estamos redirecionando todos para a versão 2 do nosso site ou api (beeem comum aqui). Porém contatenamos tudo que o usuário colocar na URL, para preservar paths, query strings, etc (com o $uri ).

O problema aqui é justamente o uso do $uri e abertura para um CRLF Injection.

Se o atacante enviar este link para a vítima:

https://mysite.com/%0d%0aSet-Cookie:%20session=12345

O servidor vai responder que deve criar este cookie session=12345 no navegador da vítima.

Porque isto acontece?

Este %0d%0a é uma quebra de linha. Ao montar a resposta, o pacote HTTP vai ficar assim:

HTTP/2 302 Found
Location: http://localhost/v2/
Set-Cookie: session=12345

E isso aconteceu porque:

  1. Vítima abre o link https://mysite.com/%0d%0aSet-Cookie:%20session=12345

  2. Servidor recebe requisição.

  3. Servidor encontra rota no NGINX (location /)

  4. Servidor ve que precisa redirecionar para v2 mantendo o path etc (return 302 http://localhost/v2$uri;)

  5. Todo redirect (301, 302) seta um header location. Neste caso, vai definir este header com o redirecionamento, que é http://localhost/v2$uri

  6. Mas para redirecionar, precisa colocar o valor da variável $uri .

  7. Ao substituir a variável, fica assim: http://localhost/v2/%0d%0aSet-Cookie:%20session=12345

  8. Por estar usando a variável $uri , precisa parsear os caracteres especiais, no caso de %0d%0a , é uma nova linha.

  9. No protocolo HTTP, uma nova linha é... UM HEADER NOVO.

  10. É adicionado a nova linha Set-Cookie:%20session=12345 , que vira um novo header.

  11. Este header é devolvido na resposta.

  12. O navegador lê os headers da resposta.

  13. Vê um header Set-Cookie e cria o cookie.

Ou seja, se sua aplicação confia cegamente nas respostas da api e você possui esta config, possui uma vulnerabilidade que pode ser explorada.

Para corrigir isso, basta utilizar $request_uri e não $uri ou $document_uri.

Igual, não se esqueça de sanitizar os headers Location, uma vez que esta mesma vulnerabilidade pode estar presente em outros mecanismos de redirecionamento em sua infraestrutra.

References