preloader

OpenVAS

OpenVAS é um scanner de vulnerabilidades completo. As suas funcionalidades incluem testes autenticados e não autenticados, diversos protocolos de internet e industriais de alto e baixo nível, otimização de desempenho para scans em larga escala e uma poderosa linguagem de programação interna para implementar qualquer tipo de teste de vulnerabilidades.

O scanner obtém os testes para deteção de vulnerabilidades de um feed [1], seja do COMMUNITY FEED (gratuito) ou do ENTERPRISE FEED (pago), que tem um longo histórico e atualizações diárias.

OpenVAS é desenvolvido e mantido pela empresa Greenbone [2] desde 2006. Como parte da família de produtos comerciais de gestão de vulnerabilidades OpenVAS SCAN, o scanner forma a OpenVAS EDIÇÃO COMUNITÁRIA [3] com outros módulos de código aberto.

A documentação seguinte descreve como implementar o OpenVAS num sistema baseado em Debian utilizando Docker containers. Inclui também scripts para automatizar as atualizações diárias do feed e para ativar o suporte à scan IPv6.

Índice:

Requisitos de Hardware

Para implementar o OpenVAS, a Greenbone define os seguintes requisitos de hardware.

  • Mínimo:
    • CPU Cores: 2
    • Random-Access Memory: 4GB
    • Hard Disk: 20GB free

  • Recomendado:
    • CPU Cores: 4
    • Random-Access Memory: 8GB
    • Hard Disk: 60GB free

Containers

A tabela seguinte descreve detalhadamente os Containers Comunitários oficiais da Greenbone [4] e os seus serviços.

Container Serviço Descrição
redis-server Redis Server Um servidor Redis com configuração ajustada. Utilizado para armazenar dados VT e resultados do scanner.
pg-gvm postgresql Um cluster de base de dados PostgreSQL configurado para utilização com o gvmd. Os dados reais são armazenados no volume psql_data_vol.
pg-gvm-migrator Um container para migrar a base de dados de uma versão principal do PostgreSQL para outra.
gvmd gvmd Um container para o gvmd que utiliza sockets Unix em volumes para comunicar com a base de dados PostgreSQL e o scanner ospd-openvas. Os dados do feed descarregados são armazenados no volume gvmd_data_vol. Para verificar os dados do feed, é utilizado o keyring GPG do gpg_data_vol.
gsad gsad Um container que executa o serviço gsad para fornecer a web API. Traduz entre http e GMP. Para a comunicação com o gvmd, é utilizado um socket Unix num volume.
gsa Um container que cópia o conteúdo estático da aplicação web GSA para o volume gsa_data_vol no arranque.
gvm-config Um container que fornece a configuração do nginx e certificados auto-assinados para comunicação https.
nginx nginx Um servidor web nginx que fornece GSA. Encaminha pedidos de API para o serviço gsad.
ospd-openvas ospd-openvas Um container que fornece o scanner de vulnerabilidades. Os dados VT do feed são armazenados no volume vt_data_vol. Para verificar os dados do feed, é utilizado o keyring GPG do volume gpg_data_vol. A ligação ao servidor Redis é estabelecida através de um socket Unix num volume.
gvm-tools Um container que fornece à CLI gvm-tools para consultar e controlar o gvmd e o ospd-openvas.
gpg-data Um container que cópia um keyring GPG com as chaves públicas de assinatura do Greenbone para o volume gpg_data_vol no arranque. É encerrado em seguida.
vulnerability-tests Um container que cópia os testes de vulnerabilidade (VTs) para o volume vt_data_vol no arranque. Apresenta a licença e termina de seguida.
notus-data Um container que cópia a informação de vulnerabilidade para o notus-scanner no volume notus_data_vol no arranque. Apresenta a licença e termina de seguida.
scap-data Um container que cópia os dados CVE e CPE para o volume scap_data_vol no arranque. Apresenta a licença e termina de seguida.
cert-bund-data Um container que cópia os dados do CERT-Bund para o volume cert_data_vol no arranque. Apresenta a licença e termina de seguida.
dfn-cert-data Um container que cópia os dados do DFN-CERT para o volume cert_data_vol no arranque. Apresenta a licença e termina de seguida.
data-objects Um container que cópia as definições de scan, as políticas de conformidade e as listas de portas para o volume data_objects_vol no arranque. Apresenta a licença e termina de seguida.
report-formats Um container que cópia os formatos de relatório para o volume data_objects_vol no arranque. Apresenta a licença e termina de seguida.
configure-openvas Um container para configurar o OpenVAS Scanner.
openvas Um container que apresenta os registos do OpenVAS Scanner.
openvasd openvasd Um container para o OpenVASD. Fornece a funcionalidade do Notus, um mecanismo estático de vulnerabilidades.

Implementação

Notas:

Antes de avançar com a documentação, o sistema deve ser atualizado.

1. Instalação do Docker e dos componentes necessários.

O Docker é necessário para executar os serviços dentro dos containers. Pode ser instalado no Debian através dos seguintes comandos:

sudo apt update
sudo apt install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update

sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin skopeo jq

Para outras distribuições, consulte o Guia Oficial de Instalação do Docker [5].

2. Encriptação TLS

O próximo passo é preparar o OpenVAS para utilizar a encriptação TLS. Para tal, crie a seguinte diretoria que irá armazenar os ficheiros do Docker Compose.

mkdir openvas
cd openvas

Após aceder a diretoria, crie o ficheiro docker-compose-nginx-setup.yml e adicione o seguinte conteúdo ao ficheiro.

volumes:
  nginx_config_vol:
  nginx_certificates_vol:
  gsa_data_vol:

services:
  nginx:
    image: nginx:latest
    volumes:
      - nginx_config_vol:/etc/nginx/conf.d
      - nginx_certificates_vol:/etc/nginx/certs
      - gsa_data_vol:/usr/share/nginx/html:ro
    command:
      - /bin/bash
      - -c
      - |
        openssl genrsa 4096 > /etc/nginx/certs/ca-key.pem
        openssl req -new -x509 -nodes -subj "/C=PT/ST=PT/O=GREENBONE/CN=OPENVAS.GREENBONE.PT" -days 3650 -key /etc/nginx/certs/ca-key.pem -out /etc/nginx/certs/ca-cert.pem
        openssl req -newkey rsa:4096 -nodes -subj "/C=PT/ST=PT/O=GREENBONE/CN=MY.OPENVAS.GREENBONE.PT" -keyout /etc/nginx/certs/privkey.pem -x509 -days 3650 -out /etc/nginx/certs/fullchain.pem -CA /etc/nginx/certs/ca-cert.pem -CAkey /etc/nginx/certs/ca-key.pem
        openssl dhparam -dsaparam -out /etc/nginx/certs/dhparams.pem 4096
        sed -i -z 's|  ssl_certificate /etc/nginx/certs/server.cert.pem;\n  ssl_certificate_key /etc/nginx/certs/server.key;|  ssl_certificate /etc/nginx/certs/fullchain.pem;\n  ssl_certificate_key /etc/nginx/certs/privkey.pem;\n  ssl_dhparam /etc/nginx/certs/dhparams.pem;\n\n  ssl_protocols TLSv1.2 TLSv1.3;\n  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;\n\n  ssl_prefer_server_ciphers on;\n  ssl_session_cache shared:SSL:20m;\n  ssl_session_timeout 180m;\n\n  error_page 497 301 =307 https://\x24host:\x24server_port\x24request_uri;|' /etc/nginx/conf.d/default.conf

  gvm-config:
    image: registry.community.greenbone.net/community/gvm-config:latest
    environment:
      ENABLE_NGINX_CONFIG: true
      ENABLE_TLS_GENERATION: false
    volumes:
      - nginx_config_vol:/mnt/nginx/configs
      - nginx_certificates_vol:/mnt/nginx/certs

  gsa:
    image: registry.community.greenbone.net/community/gsa:stable-slim
    environment:
      MOUNT_PATH: "/mnt/web"
      KEEP_ALIVE: 1
    volumes:
      - gsa_data_vol:/mnt/web

Estes containers só precisam de ser executados uma vez para configurar o Nginx com encriptação TLS.

Os certificados auto-assinados serão gerados e armazenados no volume Docker nginx_certificates_vol.

O volume gsa_data_vol irá armazenar os ficheiros estáticos do OpenVAS. Estes ficheiros serão fornecidos pelo Nginx.

O volume nginx_config_vol armazena a configuração do Nginx.

Execute os seguintes comandos para iniciar os containers.

sudo docker compose -f docker-compose-nginx-setup.yml up -d

Aguarde alguns segundos e execute os seguintes comandos.

sudo docker compose -f docker-compose-nginx-setup.yml down
sudo docker image rm registry.community.greenbone.net/community/gsa:stable-slim registry.community.greenbone.net/community/gvm-config:latest

Estes comandos interrompem os containers e removem as imagens Docker que já não são necessárias no sistema.

3. Deploy do OpenVAS

Dentro da diretoria criada anteriormente, adicione o seguinte conteúdo ao ficheiro docker-compose.yml.

networks:
  openvas_net:
    name: openvas_net
    external: true

volumes:
  gpg_data_vol:
  scap_data_vol:
  cert_data_vol:
  data_objects_vol:
  gvmd_data_vol:
  psql_data_vol:
  vt_data_vol:
  notus_data_vol:
  psql_socket_vol:
  gvmd_socket_vol:
  ospd_openvas_socket_vol:
  redis_socket_vol:
  redis_data_vol:
  openvas_data_vol:
  openvas_log_data_vol:
  gsa_data_vol:
  nginx_config_vol:
  nginx_certificates_vol:

services:
  nginx:
    image: nginx:latest
    networks:
      - openvas_net
    ports:
      - "9392:443"
    volumes:
      - nginx_config_vol:/etc/nginx/conf.d:ro
      - nginx_certificates_vol:/etc/nginx/certs:ro
      - gsa_data_vol:/usr/share/nginx/html:ro
    deploy:
      restart_policy:
        condition: any
    sysctls:
      - net.ipv6.conf.all.disable_ipv6=1
    depends_on:
      gsad:
        condition: service_started

  vulnerability-tests:
    image: registry.community.greenbone.net/community/vulnerability-tests
    environment:
      FEED_RELEASE: "24.10"
      KEEP_ALIVE: 1
    networks:
      - openvas_net
    volumes:
      - vt_data_vol:/mnt

  notus-data:
    image: registry.community.greenbone.net/community/notus-data
    environment:
      KEEP_ALIVE: 1
    networks:
      - openvas_net
    volumes:
      - notus_data_vol:/mnt

  scap-data:
    image: registry.community.greenbone.net/community/scap-data
    environment:
      KEEP_ALIVE: 1
    networks:
      - openvas_net
    volumes:
      - scap_data_vol:/mnt

  cert-bund-data:
    image: registry.community.greenbone.net/community/cert-bund-data
    environment:
      KEEP_ALIVE: 1
    networks:
      - openvas_net
    volumes:
      - cert_data_vol:/mnt

  dfn-cert-data:
    image: registry.community.greenbone.net/community/dfn-cert-data
    environment:
      KEEP_ALIVE: 1
    networks:
      - openvas_net
    volumes:
      - cert_data_vol:/mnt
    depends_on:
      cert-bund-data:
        condition: service_healthy

  data-objects:
    image: registry.community.greenbone.net/community/data-objects
    environment:
      FEED_RELEASE: "24.10"
      KEEP_ALIVE: 1
    networks:
      - openvas_net
    volumes:
      - data_objects_vol:/mnt

  report-formats:
    image: registry.community.greenbone.net/community/report-formats
    environment:
      FEED_RELEASE: "24.10"
      KEEP_ALIVE: 1
    networks:
      - openvas_net
    volumes:
      - data_objects_vol:/mnt
    depends_on:
      data-objects:
        condition: service_healthy

  gpg-data:
    image: registry.community.greenbone.net/community/gpg-data
    networks:
      - openvas_net
    volumes:
      - gpg_data_vol:/mnt

  redis-server:
    image: registry.community.greenbone.net/community/redis-server
    networks:
      - openvas_net
    deploy:
      restart_policy:
        condition: any
    volumes:
      - redis_socket_vol:/run/redis/
      - redis_data_vol:/data/:rw

  pg-gvm:
    image: registry.community.greenbone.net/community/pg-gvm:stable
    deploy:
      restart_policy:
        condition: any
    networks:
      - openvas_net
    volumes:
      - psql_data_vol:/var/lib/postgresql
      - psql_socket_vol:/var/run/postgresql
    depends_on:
      pg-gvm-migrator:
        condition: service_completed_successfully

  pg-gvm-migrator:
    image: registry.community.greenbone.net/community/pg-gvm-migrator:stable
    deploy:
      restart_policy:
        condition: none
    networks:
      - openvas_net
    volumes:
      - psql_data_vol:/var/lib/postgresql
      - psql_socket_vol:/var/run/postgresql

  gvmd:
    image: registry.community.greenbone.net/community/gvmd:stable
    networks:
      - openvas_net
    deploy:
      restart_policy:
        condition: any
    volumes:
      - gvmd_data_vol:/var/lib/gvm:rw
      - scap_data_vol:/var/lib/gvm/scap-data/
      - cert_data_vol:/var/lib/gvm/cert-data
      - data_objects_vol:/var/lib/gvm/data-objects/gvmd
      - vt_data_vol:/var/lib/openvas/plugins
      - psql_data_vol:/var/lib/postgresql
      - gvmd_socket_vol:/run/gvmd
      - ospd_openvas_socket_vol:/run/ospd
      - psql_socket_vol:/var/run/postgresql
    depends_on:
      pg-gvm:
        condition: service_started
      scap-data:
        condition: service_healthy
      cert-bund-data:
        condition: service_healthy
      dfn-cert-data:
        condition: service_healthy
      data-objects:
        condition: service_healthy
      report-formats:
        condition: service_healthy

  gsad:
    image: registry.community.greenbone.net/community/gsad:stable
    deploy:
      restart_policy:
        condition: any
    environment:
      GSAD_HTTP_ONLY: "true"
      GSAD_API_ONLY: "true"
      GSAD_FOREGROUND: "true"
    volumes:
      - gvmd_socket_vol:/run/gvmd
    networks:
      - openvas_net
    depends_on:
      gvmd:
        condition: service_started

  configure-openvas:
    image: registry.community.greenbone.net/community/openvas-scanner:stable
    volumes:
      - openvas_data_vol:/mnt
      - openvas_log_data_vol:/var/log/openvas
    networks:
      - openvas_net
    command:
      - /bin/sh
      - -c
      - |
        printf "table_driven_lsc = yes\nopenvasd_server = http://openvasd:80\n" > /mnt/openvas.conf
        sed "s/127/128/" /etc/openvas/openvas_log.conf | sed 's/gvm/openvas/' > /mnt/openvas_log.conf
        chmod 644 /mnt/openvas.conf
        chmod 644 /mnt/openvas_log.conf
        touch /var/log/openvas/openvas.log
        chmod 666 /var/log/openvas/openvas.log

  openvas:
    image: registry.community.greenbone.net/community/openvas-scanner:stable
    volumes:
      - openvas_data_vol:/etc/openvas
      - openvas_log_data_vol:/var/log/openvas
    command:
      - /bin/sh
      - -c
      - |
        cat /etc/openvas/openvas.conf
        tail -f /var/log/openvas/openvas.log
    networks:
      - openvas_net
    depends_on:
      configure-openvas:
        condition: service_completed_successfully

  openvasd:
    image: registry.community.greenbone.net/community/openvas-scanner:stable
    deploy:
      restart_policy:
        condition: any
    environment:
      OPENVASD_MODE: service_notus
      GNUPGHOME: /etc/openvas/gnupg
      LISTENING: 0.0.0.0:80
    volumes:
      - openvas_data_vol:/etc/openvas
      - openvas_log_data_vol:/var/log/openvas
      - gpg_data_vol:/etc/openvas/gnupg
      - notus_data_vol:/var/lib/notus
    depends_on:
      vulnerability-tests:
        condition: service_healthy
      notus-data:
        condition: service_healthy
      configure-openvas:
        condition: service_completed_successfully
      gpg-data:
        condition: service_completed_successfully
    networks:
      openvas_net:
        aliases:
          - openvasd

  ospd-openvas:
    image: registry.community.greenbone.net/community/ospd-openvas:stable
    deploy:
      restart_policy:
        condition: any
    hostname: ospd-openvas.local
    init: true
    cap_add:
      - NET_ADMIN # for capturing packages in promiscuous mode
      - NET_RAW # for raw sockets e.g. used for the boreas alive detection
    security_opt:
      - seccomp=unconfined
      - apparmor=unconfined
    command:
      [
        "ospd-openvas",
        "-f",
        "--config",
        "/etc/gvm/ospd-openvas.conf",
        "--notus-feed-dir",
        "/var/lib/notus/advisories",
        "-m",
        "666",
      ]
    networks:
      - openvas_net
    volumes:
      - gpg_data_vol:/etc/openvas/gnupg
      - vt_data_vol:/var/lib/openvas/plugins
      - notus_data_vol:/var/lib/notus
      - ospd_openvas_socket_vol:/run/ospd
      - redis_socket_vol:/run/redis/
      - openvas_data_vol:/etc/openvas/
      - openvas_log_data_vol:/var/log/openvas
    depends_on:
      redis-server:
        condition: service_started
      gpg-data:
        condition: service_completed_successfully
      configure-openvas:
        condition: service_completed_successfully
      vulnerability-tests:
        condition: service_healthy
      notus-data:
        condition: service_healthy

Em seguida, execute o seguinte comando para transferir todas as imagens do Docker. Tenha em conta que este passo pode levar algum tempo.

sudo docker compose pull

De seguida, crie o script start.py e adicione o seguinte código dentro do ficheiro.

#!/usr/bin/python3
import os
import sys
import subprocess

try:
    netipv6 = str(sys.argv[1])
    os.system("docker network create --ipv6 --subnet=172.10.20.0/24 --subnet=2001:db8::/64 openvas_net")
    result = subprocess.run(str("netstat -ie | grep -B1 \"172.10.20.1\" | head -n1 | awk '{split($1,a,\":\"); print a[1]}'"), shell=True, capture_output=True, text=True)
    os.system("sysctl -w net.ipv6.conf.all.forwarding=1")
    os.system("ip6tables --policy FORWARD ACCEPT")
    os.system(f"ip6tables -t nat -A POSTROUTING -o {netipv6} -j MASQUERADE")
    os.system(f"ip6tables -t nat -A POSTROUTING -o {result.stdout.strip()} -j MASQUERADE")
    t = True
except:
    os.system("docker network create --subnet=172.10.20.0/24 openvas_net")

os.system("docker compose up -d")

Para que o OpenVAS possa analisar alvos IPv6, os containers devem ter endereços IPv6 atribuídos.

O script seguinte cria uma rede bridge Docker com IPv6 ativo e configura as regras de NAT do ip6tables para fornecer conectividade IPv6 aos containers.

Como resultado, apenas a máquina host necessita de ter um endereço IPv6 válido.

Para iniciar o OpenVAS, execute o script com os seguintes comandos.

sudo python3 start.py <ipv6_interface>

Certifique-se de substituir <ipv6_interface> pelo nome da interface de rede que contém um endereço IPv6.

Se o sistema não tiver um endereço IPv6, execute o seguinte comando.

sudo python3 start.py

A interface web do OpenVAS pode ser acedida no porto 9392, com as credenciais padrão admin:admin.

De seguida, crie o script stop.py e adicione o seguinte código dentro do ficheiro.

#!/usr/bin/python3
import os
import sys
import subprocess

try:
    netipv6 = str(sys.argv[1])
    result = subprocess.run(str("netstat -ie | grep -B1 \"172.10.20.1\" | head -n1 | awk '{split($1,a,\":\"); print a[1]}'"), shell=True, capture_output=True, text=True)
    os.system("ip6tables --policy FORWARD DROP")
    os.system(f"ip6tables -t nat -D POSTROUTING -o {netipv6} -j MASQUERADE")
    os.system(f"ip6tables -t nat -D POSTROUTING -o {result.stdout.strip()} -j MASQUERADE")
except:
    pass

os.system("docker compose down")
os.system("docker network rm openvas_net")

Execute o seguinte comando para parar o OpenVAS.

sudo python3 stop.py <ipv6_interface>

Caso o sistema não possua um endereço IPv6, execute o seguinte comando.

sudo python3 stop.py

Automação

O OpenVAS atualiza o seu feed diariamente durante a semana. Isto significa que alguns dos seus containers são recriados quase todos os dias. Para automatizar o processo de atualização do feed e das imagens Docker, foi criado o seguinte script.

  • update.py
#!/usr/bin/env python3

import os
import sys
import subprocess

def get_digest(container):
    result = subprocess.run(f"skopeo inspect --no-tags docker://{container} | jq -r '.Digest'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
    skopeo_output_stderr = str(result.stderr)
    skopeo_output_stdout = str(result.stdout)
    result = subprocess.run(f"docker inspect {container} | jq -r '.[0].RepoDigests[0]' | cut -d'@' -f2", stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True)
    docker_output = str(result.stdout)

    return(skopeo_output_stderr, skopeo_output_stdout.strip(), docker_output.strip())

try:
    method = str(sys.argv[1])
except:
    method = str()

update = list()

if method == "daily":
    containers = list(["notus-data", "vulnerability-tests", "scap-data", "dfn-cert-data", "cert-bund-data", "report-formats", "data-objects"])

    for x in containers:
        skopeo_output_stderr, skopeo_output_stdout, docker_output = get_digest(f"registry.community.greenbone.net/community/{x}:latest")

        if "Error" in skopeo_output_stderr:
            continue
        elif skopeo_output_stdout != docker_output:
            update.append(x)

    if len(update) != 0:
        command = str(f"docker compose down {' '.join(update)}")
        os.system(command)
        command = str(f"docker image rm registry.community.greenbone.net/community/{' registry.community.greenbone.net/community/'.join(update)}")
        os.system(command)
        command = str(f"docker compose pull {' '.join(update)}")
        os.system(command)
        os.system(f"docker compose up -d")
else:
    containers = list()
    result = subprocess.run(["docker", "images", "--format", "'{{.Repository}}:{{.Tag}}'"], stdout=subprocess.PIPE, text=True)

    for x in str(result.stdout).split("\n"):
        if "greenbone" in x:
            update.append(str(x).split("'")[1])

    for x in update:
        skopeo_output_stderr, skopeo_output_stdout, docker_output = get_digest(x)

        if "Error" in skopeo_output_stderr or skopeo_output_stdout == docker_output:
            continue
        else:
            containers.append(x)

    if len(containers) != 0:
        command = str("docker compose down")
        os.system(command)
        command = str(f"docker image rm {' '.join(containers)}")
        os.system(command)
        os.system("docker image rm nginx:latest")
        os.system(f"docker compose up -d")

O script update.py verifica se está disponível uma nova versão dos containers. Nesse caso, elimina as versões antigas do sistema e transfere as mais recentes.

Se a palavra-chave daily for fornecida durante a invocação, o script apenas atualizará o feed. Caso contrário, atualizará todos os containers.

Este script pode ser combinado com o cron para criar uma tarefa que atualize o OpenVAS automaticamente.

Lembre-se de que não podem ser realizados scans enquanto o feed estiver a ser sincronizado.

Para atualizar o feed ou as imagens do Docker, não é necessário parar a execução do OpenVAS.

Execute o seguinte comando para atualizar o feed do OpenVAS.

sudo python3 update.py daily

Para atualizar as imagens do OpenVAS, execute o seguinte comando.

sudo python3 update.py

Bibliografia