preloader

OpenVAS

OpenVAS is a full-featured vulnerability scanner. Its capabilities include unauthenticated and authenticated testing, various high-level and low-level internet and industrial protocols, performance tuning for large-scale scans and a powerful internal programming language to implement any type of vulnerability test.

The scanner obtains the tests for detecting vulnerabilities from a feed [1], either from the free COMMUNITY FEED or the paid ENTERPRISE FEED, that has a long history and daily updates.

OpenVAS has been developed and driven forward by the company Greenbone [2] since 2006. As part of the commercial vulnerability management product family OpenVAS SCAN, the scanner forms the OpenVAS COMMUNITY EDITION [3] together with other open-source modules.

The following documentation describes how to deploy OpenVAS on a Debian-based system using Docker containers. It also includes scripts to automate daily feed updates and to enable IPv6 scanning support.

Table of contents:

Hardware Requirements

In order to implement OpenVAS, Greenbone defines the following hardware requirements.

  • Minimal:
    • CPU Cores: 2
    • Random-Access Memory: 4GB
    • Hard Disk: 20GB free

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

Containers

The following table describes the official Greenbone Community Containers [4] and their services in detail.

Container Service Description
redis-server Redis Server A redis server with an adjusted config. Used to store VT data and scan results by the scanner.
pg-gvm postgresql A PostgreSQL database cluster setup for use with gvmd. The actual data is stored in the psql_data_vol volume.
pg-gvm-migrator A container for migrating the database from one PostgreSQL major version to another.
gvmd gvmd A container for gvmd that uses unix sockets in volumes to communicate with the PostgreSQL database and ospd-openvas scanner. The downloaded feed data is stored in the gvmd_data_vol volume. To verify the feed data, the GPG keyring from the gpg_data_vol is used.
gsad gsad A container running the gsad service for providing the web API. It translates between http and GMP. For communication with gvmd, a unix socket in a volume is used.
gsa A container that copies the static content for the web application GSA to the gsa_data_vol volume on startup.
gvm-config A container providing the nginx config and self-signed certificates for https communication.
nginx nginx A nginx web server providing GSA. It forwards API requests to the gsad service.
ospd-openvas ospd-openvas A container providing the vulnerability scanner. The VT data from the feed is stored in the vt_data_vol volume. To verify the feed data, the GPG keyring from the gpg_data_vol is used. The connection to the redis server is established via a unix socket in a volume.
gvm-tools A container providing the gvm-tools CLI to query and control gvmd and ospd-openvas.
gpg-data A container that copies a GPG keyring with Greenbone’s public signing keys into the gpg_data_vol volume on startup. It exits afterwards.
vulnerability-tests A container that copies vulnerability tests (VTs) into the vt_data_vol volume on startup. Shows the license and exits afterwards.
notus-data A container that copies vulnerability information for notus-scanner into the notus_data_vol volume on startup. Shows the license and exits afterwards.
scap-data A container that copies CVE and CPE data into the scap_data_vol volume on startup. Shows the license and exits afterwards.
cert-bund-data A container that copies CERT-Bund data into the cert_data_vol volume on startup. Shows the license and exits afterwards.
dfn-cert-data A container that copies DFN-CERT data into the cert_data_vol volume on startup. Shows the license and exits afterwards.
data-objects A container that copies scan configs, compliance policies and port lists into the data_objects_vol volume on startup. Shows the license and exits afterwards.
report-formats A container that copies report formats into the data_objects_vol volume on startup. Shows the license and exits afterwards.
configure-openvas A container for setting up the configuration for OpenVAS Scanner.
openvas A container that shows the logs of OpenVAS Scanner.
openvasd openvasd A container for openvasd. It is providing notus, a static vulnerability engine, functionality.

Implementation

Notes:

Before proceeding with the documentation, the system should be updated.

1. Installing Docker and the required packages

Docker is required for running the services within containers. Docker can be installed on Debian by running the following commands:

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

For other distributions, consult the Docker Official Installation Guide [5].

2. TLS Encryption

The next step is to prepare OpenVAS to use TLS encryption. In order to do so, create a new directory that will store the Docker Compose files.

mkdir openvas
cd openvas

After accessing the directory, create the docker-compose-nginx-setup.yml file and paste the following content inside it.

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

These containers only need to be executed once in order to set up Nginx with TLS encryption.

Self-signed certificates will be generated and stored inside the nginx_certificates_vol Docker volume.

The gsa_data_vol volume will store OpenVAS static files. These files will be provided by Nginx.

The nginx_config_vol volume stores the nginx configuration.

Execute the following commands in order to run the containers.

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

Wait a couple of seconds, and then execute the following commands.

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

These commands stop the containers and remove the Docker images that are no longer required from the system.

3. Deploy OpenVAS

Inside the previously created directory, add the following contents into the docker-compose.yml file.

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
    restart: on-failure
    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

Then, execute the following command to pull all the Docker images. Keep in mind that this can take some time.

sudo docker compose pull

Next, create the start.py script and paste the following code inside the file.

#!/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")

In order for OpenVAS to scan IPv6 targets, the containers must be assigned IPv6 addresses.

The following script creates a Docker bridge network with IPv6 enabled and configures ip6tables NAT rules to provide IPv6 connectivity for the containers.

As a result, only the host machine needs to have a valid IPv6 address.

In order to start OpenVAS, execute the script with the following commands.

sudo python3 start.py <ipv6_interface>

Make sure to replace <ipv6_interface> with the name of the network interface that contains an IPv6 address.

If the system does not have an IPv6 address, execute the following command instead.

sudo python3 start.py

OpenVAS web interface can be accessed on port 9392, with the default credentials of admin:admin.

Next, create the stop.py script and paste the following code inside the file.

#!/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 the following command in order to stop OpenVAS.

sudo python3 stop.py <ipv6_interface>

If the system does not have an IPv6 address, execute the following command instead.

sudo python3 stop.py

Automation

OpenVAS updates its feed daily during the week. This means that some of its containers are recreated almost every day. In order to automate the process of updating the feed and Docker images, the following script was created.

  • 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")

The update.py script checks if a new version of the containers is available. If so, it deletes the old versions from the system and downloads the newest ones.

If the keyword daily is provided during invocation, the script will only update the feed. If no keyword is provided, then it updates all the containers.

This script can be combined with cron in order to create a task to automatically update OpenVAS.

Keep in mind that scans can’t be active while the feed is syncing.

In order to update the feed or the Docker images, OpenVAS does not need to be stopped.

Execute the following command in order to update the OpenVAS feed.

sudo python3 update.py daily

To update OpenVAS images, execute the following command.

sudo python3 update.py

Bibliography