Lucene search
+L

mDNS Service Detection (UDP)

🗓️ 16 Mar 2009 00:00:00Reported by Copyright (C) 2009 Christian Eric EdjengueleType 
openvas
 openvas
🔗 plugins.openvas.org👁 44 Views

mDNS Service Detection. Detection of services supporting Multicast DNS (mDNS) protocol. Zero Configuration Networking techniques automatically create a usable IP networ

Refs
Code
# SPDX-FileCopyrightText: 2009 Christian Eric Edjenguele
# SPDX-FileCopyrightText: Reworked, improved and extended detection routine since 2009 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-or-later

if(description)
{
  script_oid("1.3.6.1.4.1.25623.1.0.101013");
  script_version("2025-11-27T05:40:40+0000");
  script_tag(name:"last_modification", value:"2025-11-27 05:40:40 +0000 (Thu, 27 Nov 2025)");
  script_tag(name:"creation_date", value:"2009-03-16 00:46:49 +0100 (Mon, 16 Mar 2009)");
  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("mDNS Service Detection (UDP)");
  script_category(ACT_GATHER_INFO);
  script_copyright("Copyright (C) 2009 Christian Eric Edjenguele");
  script_family("Service detection");
  script_dependencies("gb_open_udp_ports.nasl");
  script_require_udp_ports("Services/udp/unknown", 5353);

  script_xref(name:"URL", value:"https://datatracker.ietf.org/doc/html/rfc6762");

  script_tag(name:"summary", value:"UDP based detection of services supporting the Multicast DNS
  (mDNS) protocol.");

  script_tag(name:"insight", value:"Zeroconf, or Zero Configuration Networking, often known as mDNS
  or Bonjour/rendez-vous, is a set of techniques that automatically create a usable IP network
  without configuration or special servers.");

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

  exit(0);
}

include("port_service_func.inc");
include("host_details.inc");
include("os_func.inc");
include("byte_func.inc");
include("dns_func.inc");
include("network_func.inc");
include("mdns_func.inc");
include("dump.inc");

SCRIPT_DESC = "mDNS Service Detection (UDP)";
# nb: Enables some debug logging when parsing the response
DEBUG = FALSE;

# @brief Parses PTR answer to try to get the Host Name and when possible, the MAC Address.
#
# @param stringa the PTR answer to be parsed
#
# @return An array containing the host name and when the case, the MAC address
#
# @note In some cases, and only for _workstation._tcp.local queries,
#       mDNS contains a PTR Answer on the form <domain name> [<MAC Address>]
#
function grabHostInfos( stringa ) {

  local_var stringa;
  local_var length, stradds, pad, addr, na, nb, n, hostname, infos;

  length = ord( stringa[0] );

  straddr = substr( stringa, 1, length );

  if( "[" >< straddr ) {
    pad = split( straddr, sep:"[", keep:FALSE );
    addr = str_replace( string:pad[1], find:"]", replace:"" );
    na = str_replace( string:pad[0], find:raw_string( 0xe2, 0x80, 0x99 ), replace:"" );
    nb = str_replace( string:na, find:"\ ", replace:"-" );
    n  = str_replace( string:nb, find:"\'", replace:"" );
    hostname = eregmatch( pattern:"([^ ]+)", string:n );

    # save the mac address and hostname
    infos = make_array( 0, hostname[0], 1, addr );
  } else {
    infos = make_array( 0, straddr );
  }

  return( infos );
}

# @brief Parses the reply to a HINFO query message and extract CPU type and OS
#
# @param stringa the HINFO reply to be parsed
#
# @return An array containing the CPU Type and OS
#
# @note It seems that this only works for queries messages for host_name.local,
#       where host_name is extracted from the PTR answer to a query to the _workstation._tcp.local domain name.
#       In these cases, mDNS may contain a PTR Answer on the form <domain name> [<MAC Address>]
#
function grabCpuInfos( stringa ) {

  local_var stringa;
  local_var offset, cpu_len, mn, mj, cpu_type, minor, major, pados, os, os_x, infos;

  if( strlen( stringa ) < 11 ) return;

  offset = 13 + ord( stringa[12] ) + 23;

  # determine the limits to extract cpu type
  cpu_len = ord( stringa[offset] );
  mn = offset + 1;
  mj = mn + cpu_len - 1;
  cpu_type = substr( stringa, mn, mj );

  # determine the limits to extract operating system type
  offset += cpu_len + 1;
  minor = offset + 1;
  major = minor + ord( stringa[offset] );

  pados = substr( stringa, minor, major );
  os = split( pados, sep:";", keep:FALSE );
  os_x = os[0];

  # save cpu type and operating system
  infos = make_array( 0, cpu_type, 1, os_x );

  return( infos );
}

port = unknownservice_get_port( default:5353, ipproto:"udp" );

if( ! soc = open_sock_udp( port ) )
  exit( 0 );

transactionId = 0x4a;
list_services = mdns_create_service_list();

if( isnull( query_services = mdns_list_to_qname_query( labels:list_services ) ) ) {
  close( soc );
  exit( 0 );
}

if( isnull( pkt = mdns_create_query( query:query_services, itype:"PTR", transactionId:transactionId ) ) ) {
  close( soc );
  exit( 0 );
}

send( socket:soc, data:pkt );

reply = recv( socket:soc, length:1024 );

# TBD: Try to somehow validate the response before registering the service?
if( isnull( reply ) ) {
  close( soc );
  exit( 0 );
}

port_and_proto = port + "/udp";

set_kb_item( name:"mdns/port_and_proto", value:port_and_proto );

answers = dns_parse_ptr_response( r:reply, query:query_services, debug:DEBUG );

ptrs = answers["PTR"];

if( max_index( ptrs ) > 0 ) {
  report = 'PTRS:\n';

  foreach _ptr( ptrs ) {
    domain_str = dns_domain_name_string( domainName:_ptr );
    report += '\t' + domain_str + '\n';
    set_kb_item( name:"mdns/" + port_and_proto + "/services/" + domain_str, value:TRUE );
  }

  host_name = "";
  # nb: Previous implementation was using a very hardcoded, limited case check to extract MAC address
  # This was extended to catch as many real-life cases as possible
  mac_address = "";
  services_report = "";

  foreach _ptr( ptrs ) {
    pkt = mdns_create_query( query:_ptr, itype:"PTR", transactionId:transactionId );

    send( socket:soc, data:pkt );

    reply = recv( socket:soc, length:1024 );
    if( isnull( reply ) )
      continue;

    services = dns_parse_ptr_response( r:reply, query:_ptr, debug:DEBUG );
    # nb: making sure to reset the variables
    addresses = "";
    host_name = "";
    _port = 0;

    if( ! isnull( services["SRV"] ) ) {
      info = services["SRV"];
      _port = info["port"];
      host_name = info["name"];
    }

    if( ! isnull( services["PTR"] ) ) {
      _hst = services["PTR"];
      hostName = _hst[0];
      # nb: There should not be more than one PTR answer,
      # and anyway we always choose the first one for the hostName
      host_info = grabHostInfos( stringa:hostName );

      if( max_index( host_info ) > 1 && strlen( host_info[1] ) ) {
        mac_address = host_info[1];
      }
    }

    if( ! isnull( services["A"] ) ) {
      foreach ipv4( services["A"] )
        addresses += ipv4 + "  ";
    }
    if( ! isnull( services["AAA"] ) ) {
      foreach ipv6( services["AAA"] )
        addresses += ipv6 + "  ";
    }
    addresses = chomp( addresses );

    ptr_name = dns_domain_name_string( domainName:_ptr );

    serv_report = "";
    if( strlen( host_name ) ) {
      serv_report += '\t\tName=' + host_name + '\n';
      set_kb_item( name:"mdns/" + port_and_proto + "/services/" + ptr_name + "/info/name", value:host_name );
    }

    if( strlen( addresses ) )
      serv_report += '\t\tAddress=' + addresses + '\n';

    # nb: Sometimes, the first label obtained from the PTR response is different from the SRV name
    if( strlen( host_info[0] ) ) {
      serv_report += '\t\tHost Name=' + host_info[0] + '\n';
      set_kb_item( name:"mdns/" + port_and_proto + "/services/" + ptr_name + "/info/host_name", value:host_info[0] );
    }

    # nb: TXT data is returned unparsed
    foreach txt( services["TXT"] ) {

      length = strlen( txt );

      if( length && ord( txt[0] ) > 0 ) {
        offset = 0;
        while( offset < length && ord( txt[offset] ) > 0 ) {
          len = ord( txt[offset] );
          offset++;
          str = substr( txt, offset, offset + len - 1 );
          # nb: Sometimes TXT contains references to MAC Address
          if( "mac_address=" >< str || str =~ "MAC=" ) {
            pan = split( str, sep:"=" );
            mac_address = pan[1];
          }
          if( "=" >< str ) {
            parts = split( str, sep:"=", keep:FALSE );
            if( ! isnull( parts[1] ) ) {
              # nb: sometimes some services exposes a lot of info.
              set_kb_item( name:"mdns/" + port_and_proto + "/info/" + parts[0], value:parts[1] );
              set_kb_item( name:"mdns/" + port_and_proto + "/services/" + ptr_name + "/info/" + parts[0], value:parts[1] );
            }
          }
          offset += len;
          serv_report += '\t\t' + str + '\n';
        }
      }
    }

    if( ! isnull( _port ) && _port > 0 )
      set_kb_item( name:"mdns/" + port_and_proto + "/services/" + ptr_name + "/port", value:_port );

    if( strlen( serv_report ) ) {
      # nb: Only add details for a PTR if there is any info
      services_report += '\t';
      if( ! isnull( _port ) && _port > 0 )
        services_report += _port + "/";

      services_report += ptr_name + ':\n';
      services_report += serv_report + '\n';
    }
  }

  if( strlen( services_report ) ) {
    report += '\nServices:\n';
    report += services_report;
  }
}

if( strlen( host_name ) ) {
  list_hName = make_list( host_name, "local" );
  query_hName = mdns_list_to_qname_query( labels:list_hName );

  # forge the mDNS CPU Infos negotiation protocol
  pkt = mdns_create_query( query:query_hName, itype:"HINFO", transactionId:transactionId );

  send( socket:soc, data:pkt );
  reply = recv( socket:soc, length:1 );
  reply = recv( socket:soc, length:1024 );

  cpuinfos = grabCpuInfos( stringa:reply );
}

close( soc );

service_register( port:port, ipproto:"udp", proto:"mdns" );

set_kb_item( name:"mdns/detected", value:TRUE );
set_kb_item( name:"mdns/udp/detected", value:TRUE );
set_kb_item( name:"mdns/udp/" + port + "/detected", value:TRUE );

# nb:
# - Store link between this and e.g. 2025/gb_mdns_wan_access.nasl
# - We don't use the host_details.inc functions in both so we need to call this directly
register_host_detail( name:"detected_at", value:port + "/udp" );

if( strlen( mac_address ) > 1 ) {

  # eg. 00:11:32:0d:04:b9|00:11:32:0d:04:ba
  if( "|" >< mac_address ) {
    macs = split( mac_address, sep:"|", keep:FALSE );
    mac_address = "";
    foreach mac( macs ) {
      v_mac = verify_register_mac_address( data:mac, desc:SCRIPT_DESC );
      if( v_mac )
        mac_address += v_mac + "  ";
    }
    mac_address = chomp( mac_address );
  } else {
    mac_address = verify_register_mac_address( data:mac_address, desc:SCRIPT_DESC );
  }

  if( ( ! isnull( mac_address ) ) && strlen( mac_address ) )
    report += ' \nMAC Address: ' + mac_address;
}

if( strlen( cpuinfos[0] ) > 1 ) {
  cpu_type = cpuinfos[0];
  report += '\nCPU Type: ' + cpu_type;
  set_kb_item( name:"MDNS/Host/CpuType", value:cpu_type );
}

if( strlen( cpuinfos[1] ) > 1 ) {
  operating_system = cpuinfos[1];
  report += '\nOperating System: ' + operating_system;
  set_kb_item( name:"MDNS/Host/OS", value:operating_system );

  # LINUX
  if( "linux" >< tolower( operating_system ) ) {
    os_register_and_report( os:"Linux", cpe:"cpe:/o:linux:kernel", banner:operating_system, banner_type:"mDNS banner", port:port, proto:"udp", desc:SCRIPT_DESC, runs_key:"unixoide" );
  } else if( "windows" >< tolower( operating_system ) ) {
    os_register_and_report( os:"Microsoft Windows", cpe:"cpe:/o:microsoft:windows", banner:operating_system, banner_type:"mDNS banner", port:port, proto:"udp", desc:SCRIPT_DESC, runs_key:"windows" );
  } else if( "mac os x" >< tolower( operating_system ) ) {
    os_register_and_report( os:"Mac OS X", cpe:"cpe:/o:apple:mac_os_x", banner:operating_system, banner_type:"mDNS banner", port:port, proto:"udp", desc:SCRIPT_DESC, runs_key:"unixoide" );
  } else {
    os_register_unknown_banner( banner:operating_system, banner_type_name:"mDNS banner", banner_type_short:"mdns_banner", port:port, proto:"udp" );
  }
}

report = chomp( report );

if( strlen( report ) ) {
  log_message( port:port, data:report, protocol:"udp" );

  # nb: This is for 2025/gb_mdns_wan_access.nasl
  set_kb_item( name:"mdns/info_accessible", value:TRUE );
  set_kb_item( name:"mdns/udp/info_accessible", value:TRUE );
  set_kb_item( name:"mdns/udp/" + port + "/info_accessible", value:TRUE );
}

exit( 0 );

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation