| Source | Link |
|---|---|
| datatracker | www.datatracker.ietf.org/doc/html/rfc6762 |
# 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