Lucene search
+L

UPnP Detection (TCP)

🗓️ 28 Oct 2022 00:00:00Reported by Copyright (C) 2022 Greenbone AGType 
openvas
 openvas
🔗 plugins.openvas.org👁 253 Views

UPnP Detection (TCP) - Detects UPnP protocol over TC

Refs
Code
# SPDX-FileCopyrightText: 2022 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.170204");
  script_version("2026-04-30T06:27:06+0000");
  script_tag(name:"last_modification", value:"2026-04-30 06:27:06 +0000 (Thu, 30 Apr 2026)");
  script_tag(name:"creation_date", value:"2022-10-28 12:56:06 +0000 (Fri, 28 Oct 2022)");
  script_tag(name:"cvss_base", value:"0.0");
  script_tag(name:"cvss_base_vector", value:"AV:N/AC:L/Au:N/C:N/I:N/A:N");

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

  script_name("UPnP Detection (TCP)");

  script_category(ACT_GATHER_INFO);

  script_copyright("Copyright (C) 2022 Greenbone AG");
  script_family("Service detection");
  script_dependencies("gb_upnp_udp_detect.nasl", "find_service.nasl", "httpver.nasl",
                      "global_settings.nasl");
  script_require_ports("Services/www", 52881);
  script_exclude_keys("Settings/disable_cgi_scanning");

  script_tag(name:"summary", value:"TCP based detection of services supporting the Universal Plug
  and Play (UPnP) protocol.");

  script_tag(name:"vuldetect", value:"The script sends a HTTP request to URLs for the root
  description XML, either based on previously detected location or a list of known possible
  locations.");

  script_xref(name:"URL", value:"https://openconnectivity.org/foundation/faq/upnp-faq/");
  script_xref(name:"URL", value:"https://upnp.org/specs/arch/UPnPDA10_20000613.htm");

  # nb: Might run longer then the default of 360
  script_timeout(600);

  exit(0);
}

include("host_details.inc");
include("http_func.inc");
include("port_service_func.inc");
include("http_keepalive.inc");

# nb: To enable some debugging for the Location header extraction / handling.
DEBUG = FALSE;

function handleUPnPXML( xml, port ) {

  local_var xml, port;
  local_var extra, manufacturer, model_name, friendly_name, model_number, model_type, model_description;
  local_var version, software_version, software_generation, hardware_version, display_version;

  if( ! xml )
    return;

  extra = NULL;

  set_kb_item( name:"upnp/tcp/port", value:port );

  #<device>
  #  <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>
  #  <friendlyName>SR1217 (DS216se)</friendlyName>
  #  <manufacturer>Synology</manufacturer>
  #  <manufacturerURL>http://www.synology.com</manufacturerURL>
  #  <modelDescription>Synology NAS</modelDescription>
  #  <modelName>DS216se</modelName>
  #  <modelNumber>DS216se 6.2-25556</modelNumber>
  #  <modelURL>http://www.synology.com</modelURL>
  #  <modelType>NAS</modelType>
  #  <serialNumber>redacted</serialNumber>
  #
  # or:
  #
  # <netRemote>
  # <friendlyName>ir110</friendlyName>
  # <version>ir-mmi-FS2026-0500-0084_V2.11.16.EX69632-2A10</version>
  # <webfsapi>http://<redacted>:80/fsapi</webfsapi>
  # </netRemote>
  #
  # or:
  #
  #<device>
  #<deviceType>urn:schemas-upnp-org:device:ZonePlayer:1</deviceType>
  #<friendlyName>76.206.43.58 - Sonos Bridge</friendlyName>
  #<manufacturer>Sonos, Inc.</manufacturer>
  #<manufacturerURL>http://www.sonos.com</manufacturerURL>
  #<modelNumber>ZB100</modelNumber>
  #<modelDescription>Sonos Bridge</modelDescription>
  #<modelName>Sonos Bridge</modelName>
  #<modelURL>http://www.sonos.com/store/products/ZB100</modelURL>
  #<softwareVersion>57.3-77280</softwareVersion>
  #<swGen>1</swGen>
  #<hardwareVersion>1.5.0.0-1.0</hardwareVersion>
  #<displayVersion>11.2</displayVersion>

  manufacturer = eregmatch( pattern:"<manufacturer>([^<]+)</manufacturer>", string:xml );
  if( ! isnull( manufacturer[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/manufacturer", value:manufacturer[1] );
    extra = "  Manufacturer:  " + manufacturer[1];
  }

  model_name = eregmatch( pattern:"<modelName>([^<]+)</modelName>", string:xml );
  if( ! isnull( model_name[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/modelName", value:model_name[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Model Name:    " + model_name[1];
  }

  friendly_name = eregmatch( pattern:"<friendlyName>([^<]+)</friendlyName>", string:xml );
  if( ! isnull( friendly_name[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/friendlyName", value:friendly_name[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Friendly Name: " + friendly_name[1];
  }

  model_number = eregmatch( pattern:"<modelNumber>([^<]+)</modelNumber>", string:xml );
  if( ! isnull( model_number[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/modelNumber", value:model_number[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Model Number:  " + model_number[1];
  }

  model_type = eregmatch( pattern:"<modelType>([^<]+)</modelType>", string:xml );
  if( ! isnull( model_type[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/modelType", value:model_type[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Model Type:    " + model_type[1];
  }

  model_description = eregmatch( pattern:"<modelDescription>([^<]+)</modelDescription>", string:xml );
  if( ! isnull( model_description[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/modelDescription", value:model_description[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Model Desc:    " + model_description[1];
  }

  version = eregmatch( pattern:"<version>([^<]+)</version>", string:xml );
  if( ! isnull( version[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/version", value:version[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Version:    " + version[1];
  }

  software_version = eregmatch( pattern:"<softwareVersion>([0-9.-]+)(-manufacturing)?</softwareVersion>", string:xml );
  if( ! isnull( software_version[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/softwareVersion", value:software_version[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Build Number:  " + software_version[1];
  }

  software_generation = eregmatch( pattern:"<swGen>([0-9])</swGen>", string:xml );
  if( ! isnull( software_generation[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/softwareGeneration", value:software_generation[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Software Gen:  " + software_generation[1];
  }

  hardware_version = eregmatch( pattern:"<hardwareVersion>([0-9.-]+)</hardwareVersion>", string:xml );
  if( ! isnull( hardware_version[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/hardwareVersion", value:hardware_version[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  Hardware Ver:  " + hardware_version[1];
  }

  display_version = eregmatch( pattern:"<displayVersion>([0-9.]+)</displayVersion>", string:xml );
  if( ! isnull( display_version[1] ) ) {
    set_kb_item( name:"upnp/tcp/" + port + "/device/displayVersion", value:display_version[1] );
    if( ! isnull( extra ) )
      extra += '\n';
    extra += "  App Ver:       " + display_version[1];
  }

  # nb: Save the full XML so that we can possible add more useful info in the KB if required.
  set_kb_item( name:"upnp/tcp/" + port + "/device/full_xml", value:xml );

  return extra;
}

report = "";

# e.g.:
# <root xmlns="urn:schemas-upnp-org:device-1-0">
#   <device>
#     <deviceType>urn:schemas-wifialliance-org:device:WFADevice:1</deviceType>
#   *snip*
#   </device>
# </root>
#
# or "just" the following (without the <root><device>*snip*</device></root>):
#
# <netRemote>
# <friendlyName>ir110</friendlyName>
# <version>ir-mmi-FS2026-0500-0084_V2.11.16.EX69632-2A10</version>
# <webfsapi>http://<redacted>/fsapi</webfsapi>
# </netRemote>
#
# And for the header:
#
# Content-Type: text/xml; charset="utf-8"
# Content-Type: text/xml
#
xml_verification_pattern = "(<root( xmlns=[^>]+)?>.+</root>|<device>.+</device>|[Cc]ontent-[Tt]ype\s*:\s*text/xml.+<[^>]+>.+</[^>]+>)";

# nb: gb_upnp_udp_detect.nasl is doing an egrep() with a "^" so we don't need special verification
# for the content here...
if( location = get_kb_item( "upnp/udp/location" ) ) {

  # nb:
  # - We're not "verifying" the IP/hostname in the location here because e.g. it might point to a
  #   local IP address (192.168.x.x) while the port is also "exposed" externally.
  # - The +? quantifier is used below in all regex pattern to match as less as possible (lazy match)
  # - We use separate checks/pattern below to not make the regex too complex...

  # nb: Just the plain port to the root dir (with and without the slash) like e.g.:
  # LOCATION: http://<redacted>:60844/
  # LOCATION: https://<redacted>:8888
  infos = eregmatch( pattern:"LOCATION\s*:\s*https?://[^:]+?:([0-9]+)/?$", string:location, icase:TRUE );
  if( infos[1] ) {
    redir_port = infos[1];
    redir_loc = "/";
  }

  # nb: Next the ones with the port and follow-up endpoints
  if( ! redir_port || ! redir_loc ) {

    # Examples:
    # Location: http://<redacted>:52869/5e9f5c11.xml
    # LOCATION: http://<redacted>:5000/ssdp/desc-DSM-eth0.xml
    # LOCATION: http://<redacted>:49152/wps_device.xml
    # LOCATION: http://<redacted>:80/UPnP/IGD.xml
    # Location: http://<redacted>:8080/upnp
    # Location: http://<redacted>:80/cgi-bin/cgi.cgi?Ssdp
    # LOCATION: http://BeeStation:6600/ssdp/desc-DSM-eth0.xml
    # LOCATION: http://<redacted>:37564/upnphost/udhisapi.dll?content=uuid:505aa849-<redacted>
    # LOCATION: http://<redacted>:1900/device/d3955f65-<redacted>
    # Location: http://<redacted>:5432/upnp/9a<redacted>.xml
    infos = eregmatch( pattern:"LOCATION\s*:\s*https?://[^:]+?:([0-9]+)(/.+)", string:location, icase:TRUE );
    if( infos[1] ) {
      redir_port = infos[1];
      redir_loc = infos[2];
    }
  }

  # nb: And finally the ones without a port
  if( ( ! redir_port || ! redir_loc ) &&
      # nb: Just a basic check if some of our previous checks have missed something
      location !~ "LOCATION\s*:\s*https?://[^:]+?:[0-9]+" ) {

    if( location =~ "LOCATION\s*:\s*http://" )
      redir_port = 80;
    else if( location =~ "LOCATION\s*:\s*https://" )
      redir_port = 443;

    # The ones with an endpoint like e.g.:
    # LOCATION: http://<redacted>/Device.xml
    infos = eregmatch( pattern:"LOCATION\s*:\s*https?://[^/]+?(/.+)", string:location, icase:TRUE );
    if( infos[1] )
      redir_loc = infos[1];

    # The ones without a trailing slash like e.g.:
    # LOCATION: https://<redacted>
    else if( location =~ "LOCATION\s*:\s*https?://" &&
             location !~ "LOCATION\s*:\s*https?://.+/" )
      redir_loc = "/";

    # And finally the ones with a trailing slash like e.g.:
    # LOCATION: http://<redacted>/
    else if( location =~ "LOCATION\s*:\s*https?://[^/]+?/$" )
      redir_loc = "/";
  }

  if( redir_port && redir_loc )
    if( DEBUG ) display( "DEBUG: Extracted port '" + redir_port + "' and URL '" + redir_loc + "' from passed location string: " + location );
  else
    if( DEBUG ) display( "DEBUG: Failed to extract port and URL from passed location string: " + location );

  # nb: get_port_state() is used here because we only want to touch the port we got redirected to if
  # it was scanned by a port scanner previously.
  if( redir_port && get_port_state( redir_port ) && redir_loc ) {

    res = http_get_cache( item:redir_loc, port:redir_port );

    # nb: "text/html" is checked to catch some SPA web pages which are throwing a 200 on
    # non-existing URLs to avoid possible false detections if the page e.g. includes the checked
    # pattern in some way.
    if( res && res =~ "^HTTP/(1\.[01]|2) 200" &&
        ! egrep( string:res, pattern:"^[Cc]ontent-[Tt]ype\s*:\s*text/html", icase:FALSE ) &&
        eregmatch( string:res, pattern:xml_verification_pattern, icase:FALSE ) ) {
      extra = handleUPnPXML( xml:res, port:redir_port );
      set_kb_item( name:"upnp/tcp/" + redir_port + "/location", value:redir_loc );

      # nb: Later used to not touch the same port twice if the verification / detection was successful
      check_redir_port = TRUE;

      report  = "The remote Host exposes an UPnP root device XML on port " + redir_port + '/tcp.\n';
      report += '\nThe XML can be found at the location:\n  ' + http_report_vuln_url( port:redir_port, url:redir_loc, url_only:TRUE );
      if( ! isnull( extra ) )
        report += '\n\nExcerpt from the obtained data:\n' + extra;

      service_register( port:redir_port, ipproto:"tcp", proto:"upnp" );
      log_message( port:redir_port, data:report, proto:"tcp" );
    }
  }
}

# nb:
# - There are also some like e.g. "/dyndev/uuid:re-da-ct-ed" but these can't be added here...
# - When adding additional files with new extensions here make sure to adjust the relevant regex
#   above
# - To check that no duplicated entries got added a grep call like this can be used:
#   egrep '  "/[^"]+",' gb_upnp_tcp_detect.nasl | sort | uniq -d
known_locations = make_list(

  # Some systems are only doing something like e.g. this:
  # LOCATION: http://<redacted>:37805/
  # LOCATION: https://<redacted>:8888
  # so the root dir is tested as well.
  "/",

  # Various from external resources as well as own research / analysis:
  "/simplecfg.xml",
  "/rootDesc.xml",
  "/devdescr.xml",
  "/gateway.xml",
  "/devicedesc.xml",
  "/description.xml",
  "/ssdp/device-desc.xml",
  "/XD/DeviceDescription.xml",
  "/DeviceDescription.xml",
  "/xml/device_description.xml",
  "/device-desc.xml",
  "/IGD.xml",
  "/ssdp/desc-DSM-eth0.xml",
  "/ssdp/desc-DSM-eth1.xml",
  "/ssdp/desc-DSM-eth2.xml",
  "/ssdp/desc-DSM-eth3.xml",
  "/ssdp/desc-DSM-eth4.xml",
  "/ssdp/desc-DSM-eth5.xml",
  "/ssdp/desc-DSM-bond0.xml",
  "/ssdp/desc-DSM-eth1.4000.xml",
  "/ssdp/desc-DSM-ovs_eth0.xml",
  "/ssdp/desc-DSM-ovs_eth1.xml",
  "/ssdp/desc-DSM-ovs_bond0.xml",
  "/etc/linuxigd/gatedesc.xml",
  "/upnp/descr.xml",
  "/upnp/BasicDevice.xml",
  "/cameradesc.xml",
  "/bmlinks/ddf.xml",
  "/picsdesc.xml",
  "/rss/Starter_desc.xml",
  "/DSDeviceDescription.xml",
  "/DSDeviceDescription2.xml",
  "/upnpdevicedesc.xml",
  "/upnp.jsp",
  "/wps_device.xml",
  "/desc/root.cs",
  "/MediaServerDevDesc.xml",
  "/UPnP/IGD.xml",
  "/gatedesc.xml",
  "/edevicedesc.xml",
  "/device_desc.xml",
  "/setup.xml",
  "/fboxdesc.xml",
  "/upnp.xml",
  "/root.sxml",
  "/IPCamDesc.xml",
  "/landisk_desc.xml",
  "/Printer.xml",
  "/dms/device.xml",
  "/Public_UPNP_gatedesc.xml",
  "/TeraStationDeviceDescription.xml",
  "/avtech/Mediadesc_0.xml",
  "/ipxml_ssdp.cgi",
  "/plugin/discovery/discovery.xml",
  "/desc.xml",
  "/device_description.xml",
  "/upnp/basic_dev.cgi",
  "/upnp/device.cgi",
  "/devdesc.xml",
  "/ssdp.xml",
  "/upnp/service/descrip.xml",
  "/upnp/rootdevice.xml",
  "/gateway_description.xml",
  "/upnpdev.xml",
  "/helo_plus_upnp_ssdp_1.xml",
  "/nasService.xml",
  "/upnp",
  "/discovery/ssdp",
  "/desc/root.php",
  "/upnp_descriptor_0",
  "/cgxml.php",
  "/upnp/bdb5700.xml",
  "/tr064dev.xml",
  "/igddesc.xml",
  "/device_desc2.xml",
  "/upnp/APDesc.xml",
  "/root.xml",
  "/services/upnp",
  "/igd2desc.xml",
  "/desc/root",
  "/details.xml",
  "/upnp_device_desc.xml",
  "/UPNP_rootDesc.xml",
  "/l2tpv3.xml",
  "/upnp/index.xml",
  "/dvb2ip.xml",
  "/discovery.xml",
  "/octoserve/octonet.xml",
  "/description-eth0.xml",
  "/description-eth1.xml",
  "/description-eth2.xml",
  "/mediaserver_description-2.xml",
  "/cgi-bin/cgi.cgi?Ssdp",
  "/devicedesc_wlan.xml",
  "/avmnexusdesc.xml",
  "/upnp_getinfo",
  "/info/upnp",
  "/info.xml",
  "/WebcardDesc.xml",
  "/UPnP/TR064/InternetGatewayDevice.xml",
  "/upnp/desc.html",
  # From this:
  # https://cari.net/carisirt-yet-another-bmc-vulnerability-and-some-added-extras/
  "/IPMIdevicedesc.xml"
);

# nb: The TCP port depends on the vendor, currently the most commonly found port (Realtek) is used
# as a default
port = http_get_port( default:52881 );

# nb: If we have already found a UPnP .xml on this port there is no need to touch this again. This
# also solves the problem that if the location header only points to e.g.:
# LOCATION: http://<redacted>:80/
# without any .xml included (seen that on at least one "live" system) we're still trying to
# enumerate all known .xml files.
if( check_redir_port && redir_port && port == redir_port )
  exit( 0 );

foreach known_location( known_locations ) {

  res = http_get_cache( item:known_location, port:port );

  # nb: See reason for the "text/html" check above.
  if( res && res =~ "^HTTP/(1\.[01]|2) 200" &&
      ! egrep( string:res, pattern:"^[Cc]ontent-[Tt]ype\s*:\s*text/html", icase:FALSE ) &&
      eregmatch( string:res, pattern:xml_verification_pattern, icase:FALSE ) ) {

    extra = handleUPnPXML( xml:res, port:port );

    set_kb_item( name:"upnp/tcp/" + port + "/location", value:known_location );

    report  = "The remote Host exposes an UPnP root device XML on port " + port + '/tcp.\n';
    report += '\nThe XML can be found at the location:\n  ' + http_report_vuln_url( port:port, url:known_location, url_only:TRUE );
    if( ! isnull( extra ) )
      report += '\n\nExcerpt from the obtained data:\n' + extra;

    break;
  }
}

if( report ) {

  set_kb_item( name:"upnp/detected", value:TRUE );
  set_kb_item( name:"upnp/tcp/detected", value:TRUE );
  set_kb_item( name:"upnp/tcp/" + port + "/detected", value:TRUE );

  service_register( port:port, ipproto:"tcp", proto:"upnp" );
  log_message( port:port, data:report, proto:"tcp" );
}

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

30 Apr 2026 00:00Current
5.2Medium risk
Vulners AI Score5.2
253