UPnP Detection (TCP)
🗓️ 28 Oct 2022 00:00:00Reported by Copyright (C) 2022 Greenbone AGType
openvas🔗 plugins.openvas.org👁 253 Views
| Source | Link |
|---|---|
| upnp | www.upnp.org/specs/arch/UPnPDA10_20000613.htm |
| openconnectivity | www.openconnectivity.org/foundation/faq/upnp-faq/ |
# 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