Containers able to see MAC Address, but not appearing in reports - Solved

I’ve been doing some work to try to get mac addresses to appear in my deployment using community containers.

There are some challenges to get that working through docker, and the solution I’ve come up with is to put the ospd-openvas container on a macvlan network and the rest of the containers inside a bridge.
There are some reasons for that, that I wont go in to right now - I’ll publish my results when I get this last piece working.

So my issue is that I can’t get MAC addresses to appear in reports, despite the ospd-openvas container seeing all of the mac addresses, as evidenced by results in arp:

root@ospd-openvas:/ospd-openvas# arp
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.1.249            ether   00:5f:86:cb:a2:68   C                     eth0
192.168.1.67             ether   00:23:24:a1:94:4d   C                     eth0
192.168.1.54             ether   d8:cb:8a:87:bc:92   C                     eth0
192.168.1.225            ether   14:a7:8b:9c:62:60   C                     eth0
192.168.1.53             ether   80:5e:c0:9b:39:90   C                     eth0
192.168.1.60             ether   02:42:ac:16:00:02   C                     eth0
192.168.1.69             ether   02:42:ac:13:00:02   C                     eth0
anotherhostname          ether   14:49:bc:24:43:68   C                     eth0
192.168.1.55             ether   78:45:58:02:96:1b   C                     eth0
192.168.1.220            ether   10:e7:c6:62:fd:fa   C                     eth0

When I do an nmap scan on one of the hosts I get a result:

root@ospd-openvas:/ospd-openvas# nmap -PE 192.168.1.254
Starting Nmap 7.93 ( https://nmap.org ) at 2024-06-16 23:57 UTC
Nmap scan report for anotherhostname (192.168.1.254)
Host is up (0.00051s latency).
Not shown: 996 filtered tcp ports (no-response)
PORT      STATE SERVICE
21/tcp    open  ftp
22/tcp    open  ssh
23/tcp    open  telnet
30000/tcp open  ndmps
MAC Address: 14:49:BC:24:43:68 (DrayTek)

Nmap done: 1 IP address (1 host up) scanned in 5.03 seconds

This is probably a question that’s aimed at a developer or someone who knows how exactly GVM finds the mac address - but does anyone know how I can troubleshoot or find an answer as to why the container can see the mac addresses but doesn’t show them in scan results?

I had this working at one point but something changed (I don’t know what, probably something I did).
The NVT that usually gathers the mac address is 1.3.6.1.4.1.25623.1.0.103585 - NVT: Nmap MAC Scan from nmap_mac.nasl.

Here is the nasl:
cat nmap_mac.nasl

# SPDX-FileCopyrightText: 2012 Greenbone AG
# Some text descriptions might be excerpted from (a) referenced
# source(s), and are Copyright (C) by the respective right holder(s).
#
# SPDX-License-Identifier: GPL-2.0-only

if(description)
{
  script_oid("1.3.6.1.4.1.25623.1.0.103585");
  script_version("2023-07-28T16:09:07+0000");
  script_tag(name:"last_modification", value:"2023-07-28 16:09:07 +0000 (Fri, 28 Jul 2023)");
  script_tag(name:"creation_date", value:"2012-10-11 15:52:11 +0100 (Thu, 11 Oct 2012)");
  script_tag(name:"cvss_base_vector", value:"AV:N/AC:L/Au:N/C:N/I:N/A:N");
  script_tag(name:"cvss_base", value:"0.0");
  script_name("Nmap MAC Scan");
  script_category(ACT_SETTINGS);
  script_copyright("Copyright (C) 2012 Greenbone AG");
  script_dependencies("toolcheck.nasl", "host_alive_detection.nasl", "global_settings.nasl");
  script_family("Service detection");
  script_mandatory_keys("Tools/Present/nmap", "keys/islocalnet");

  script_tag(name:"summary", value:"This script attempts to gather the MAC address of the target.");

  script_tag(name:"qod_type", value:"remote_banner");

  exit(0);
}

include("host_details.inc");

if( ! islocalnet() ) exit( 0 );

argv[x++] = "nmap";
argv[x++] = "-sP";

ip = get_host_ip();

if( TARGET_IS_IPV6() )
  argv[x++] = "-6";

# Apply the chosen nmap timing policy from nmap.nasl here as well
timing_policy = get_kb_item( "Tools/nmap/timing_policy" );
if( timing_policy =~ "^-T[0-5]$" )
  argv[x++] = timing_policy;

source_iface = get_preference( "source_iface" );
if( source_iface =~ "^[0-9a-zA-Z:_]+$" ) {
  argv[x++] = "-e";
  argv[x++] = source_iface;
}

argv[x++] = ip;

res = pread( cmd:"nmap", argv:argv );
if( isnull( res ) || "MAC" >!< res ) exit( 0 );

mac = eregmatch( pattern:"MAC Address: ([0-9a-fA-F:]{17})", string:res );

if( ! isnull( mac[1] ) ) {
  register_host_detail( name:"MAC", value:mac[1], desc:"Nmap MAC Scan" );
  replace_kb_item( name:"Host/mac_address", value:mac[1] );
}

Reading through the nasl, I’ve constructed this command: nmap -sP 192.168.1.254

Starting Nmap 7.93 ( https://nmap.org ) at 2024-06-17 01:04 UTC
Nmap scan report for 192.168.1.254
Host is up (0.00021s latency).
MAC Address: 14:49:BC:24:43:68 (DrayTek)
Nmap done: 1 IP address (1 host up) scanned in 0.10 seconds

So the only conclusion I can draw is that the script is exiting at this line and therefore not running:
if( ! islocalnet() ) exit( 0 );

I’m looking in to that, but any insight anyone has would be helpful.

I’ll provide some more outputs and testing steps here for posterity.
I ran a manual check using openvas-nasl like this:
openvas-nasl -t 192.168.1.254 -i /var/lib/openvas/plugins/ /var/lib/openvas/plugins/nmap_mac.nasl -X -T out.log -d

In out.log I found this:

[115493](/var/lib/openvas/plugins/nmap_mac.nasl) NASL> Call islocalnet()
[115493](/var/lib/openvas/plugins/nmap_mac.nasl) NASL> Return islocalnet: 0
NASL:0031> exit(...)

So it’s clear that it’s not passing the islocalnet check.

Here’s the output of my interfaces local to the container:

root@ospd-openvas:/var/lib/openvas/plugins# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
9703: eth0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.1.57/24 brd 192.168.1.255 scope global dynamic eth0
       valid_lft 51787sec preferred_lft 51787sec
9704: eth1@if9705: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:14:00:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.0.7/24 brd 172.20.0.255 scope global eth1
       valid_lft forever preferred_lft forever

Now I need to dig in to how islocalnet works to figure out why it thinks that 192.168.1.0/24 isn’t a local network.

1 Like

So it seems like islocalnet can’t handle more than one IP address on an interface.
After clearing the 172.18.0.2/16 address off the interface, it now works:

root@ospd-openvas:/ospd-openvas# openvas-nasl -t 192.168.1.254 -i /var/lib/openvas/plugins/ /var/lib/openvas/plugins/nmap_mac.nasl -X -T out.log -d
lib  misc-Message: 00:21:40.264: replace key Host/mac_address -> 14:49:BC:24:43:68

So what I was trying to achieve is to have the ospd-openvas container on the local network to allow it to see mac addresses, which provides two things for me:

  1. Provides a way to more-uniquely identify a host, as a mac address is usually more-reliable than an IP address, and
  2. Use the MAC OUI to identify the manufacturer and help narrow down what the host is.

The second thing I needed to make sure worked is to allow the container to use the host’s systemd-resolved service to conduct mDNS queries and reverse lookups on the local network, as moving to macvlan and using DHCP made the container stop seeing hostnames on the local network (as the resolver on the container doesn’t have the capability of running mDNS queries).

Notes:

  1. This was on a Ubuntu 22.04 server, which affects interface naming and the use of the systemd-resolved service
  2. This only works on a network with DHCP enabled. You can make this work with static IPs, but that wasn’t my intent here.
  3. The host will now consume two IP addresses from DHCP - one for the parent server, one for the ospd-openvas container.
  4. You could put all containers on the macvlan network, but all of them would be on the host’s physical network.
  5. The reason for using the openvas (bridge) network was that the container’s refer to each other by name, so host networking doesn’t work.
  6. I started with the docker-compose file from the community containers page.

So here’s how I did it if anyone was interested.

  1. Create two networks in compose:
networks:
  macvlan_net:
    driver: macvlan
    driver_opts:
      parent: eno1
  openvas:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24
          gateway: 172.20.0.1
  1. Assign all containers to the openvas network. E.g.:
  gvm-tools:
    image: greenbone/gvm-tools
    volumes:
      - gvmd_socket_vol:/run/gvmd
      - ospd_openvas_socket_vol:/run/ospd
    depends_on:
      - gvmd
      - ospd-openvas
    networks:
      - openvas
  1. Set up ospd-openvas to:
    a. Use a local build.
    b. Use the host’s local network and local DNS server for builds.
    c. Use both networks. (macvlan provides access to the local network, the openvas network allows access to all other hosts in the compose stack).
  ospd-openvas:
    build:
      context: ./ospd-openvas
      network: host
    restart: on-failure
    hostname: ospd-openvas.local
    cap_add:
      - NET_ADMIN # for capturing packages in promiscuous mode
      - NET_RAW # for raw sockets e.g. used for the boreas alive detection
    dns:
      - 172.18.0.1  # Use the local server for builds
    security_opt:
      - seccomp=unconfined
      - apparmor=unconfined
    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
      vulnerability-tests:
        condition: service_completed_successfully
      configure-openvas:
        condition: service_completed_successfully
    networks:
      - macvlan_net
      - openvas
  1. Create a folder called ospd-openvas in the same folder as the docker-compose.yml file. Place this Dockerfile in there:

    a. This dockerfile sets up the dhcp client, sets the container to use the parent-server’s DNS server rather than the one issued by DHCP, and lastly - run the DHCP client script as part of the startup.

FROM greenbone/ospd-openvas:22.7.1

# Install dhclient and clean up apt cache
RUN apt-get update &&     apt-get install -y isc-dhcp-client &&     apt-get clean &&     rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Allow the user to run dhclient without sudo
RUN mkdir -p /home/ospd-openvas/bin &&     echo 'dhclient' > /home/ospd-openvas/bin/dhclient &&     chmod +x /home/ospd-openvas/bin/dhclient &&     export PATH=/home/ospd-openvas/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

# Make the script executable
COPY dhclient-script.sh /usr/local/bin/dhclient-script.sh
RUN chmod +x /usr/local/bin/dhclient-script.sh

# Override DNS from DHCP
RUN echo 'supersede domain-name-servers 172.20.0.1;' >> /etc/dhcp/dhclient.conf

# Ensure entrypoint runs with necessary permissions
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/dhclient-script.sh"]
CMD ["ospd-openvas", "--config", "/etc/gvm/ospd-openvas.conf", "-f", "-m", "666"]
  1. Place this in a file called dhclient-script.sh in the same ospd-openvas folder.
    a. This file remove’s the initial default route to the parent-host, removes the docker-issued IP from the macvlan network, runs the dhcp-client and then runs the usual openvas comman.
#!/bin/sh

# Clear the existing default route issued by Docker
ip route del default

# Clear the eth0 interface to remove the macvlan-assigned IP address (Breaks nmap mac address check (nmap_mac.nasl))
ip addr flush dev eth0

# Run dhclient to obtain an IP address
dhclient eth0

# Run the main entrypoint
exec /usr/local/bin/entrypoint ospd-openvas --config /etc/gvm/ospd-openvas.conf -f -m 666
  1. Lastly, you need to configure systemd-resolved to listen to requests from a non-localhost network.
    a. Add DNSStubListenerExtra to /etc/systemd/resolved.conf and restart the service using sudo service systemd-resolved restart
[Resolve]
DNSStubListenerExtra=172.20.0.1

Hope that helps anyone looking to do what I was.
I’m kinda-expecting someone to tell me that there was a way-easier way of doing this. I’d be happy to hear if that is the case.

2 Likes