目录

什么是 UPnP ?

通用即插即用(英语:Universal Plug and Play,简称 UPnP )是 1999 年由微软推出的,“通用即插即用论坛”(UPnP™ Forum)推广的一套网络协议。该协议的目标是使家庭网络(数据共享、通信和娱乐)和公司网络中的各种设备能够相互无缝连接,并简化相关网络的实现。UPnP通过定义和发布基于开放、因特网通讯网协议标准的UPnP设备控制协议来实现这一目标。

UPnP 这个概念是从即插即用(Plug-and-play)派生而来的,即插即用是一种热拔插技术。

维基百科-UPNP

看以上的解释看着是不是觉得云里雾里,我们用通俗的话讲就是:

UPnP 是运行在 UDP 1900 端口上的,使用 XML/SOAP 对远程设备进行查询和控制的协议

该协议有以下的几个特点

  • 协议设计于上世纪 90 年代到 20 世纪初 (意味着漏洞多多)
  • 协议控制部分基于 SOAP(html+xml) (意味着简单)
  • UPnP多用在物联网设备和智能路由器上 (意味着使用广泛)

为什么会有 UPnP 协议?

UPnP 最大的愿景是希望任何设备一旦连接上网络,所有在网络上的设备马上就能知道有新设备加入,这些设备彼此之间能互相通信,更能直接使用或者控制它,一切都不需要人工设置,完全的即插即用。

理想是丰满的,但现实很骨感,哪有这么十全十美的方案,从发布至今 UPnP 因在设计上的缺陷和各个厂家在协议具体实现上的错误导致出现了许许多多的安全漏洞,上文提到物联网设备和智能路由器多使用 UPnP,就拿几十块的路由器来说,他们的系统在出厂时就已经写死,虽然有更新功能,但是没人有这个意识去更新家里路由器的系统,于是发现漏洞时无法及时更新,导致一些很严重漏洞至今还充斥在暴露于互联网当中的物联网设备和智能路由器中,这也是我写本文的原因。

UPnP 实验环境搭建

为了进一步学习 UPnP 协议,我在 Alpine 操作系统里虚拟机安装了 MiniUPnP

Alpine Linux的包管理器为 apk,安装 MiniUPnP 之前需要启用 edge 源:

编辑 /etc/apk/repositories 文件,去掉 http://mirrors.tuna.tsinghua.edu.cn/alpine/edge/community 前的 #

安装

apk install miniupnp

生成随机 uuid

cat /proc/sys/kernel/random/uuid

写入随机的 uuid 到 /etc/miniupnpd/miniupnpd.conf 里

我的配置文件内容如下

alpine:~# grep -v '^#\|^$' /etc/miniupnpd/miniupnpd.conf
listening_ip=eth0
http_port=31337
enable_natpmp=yes
enable_upnp=yes
bitrate_up=1000000
bitrate_down=10000000
secure_mode=no
system_uptime=yes
notify_interval=60
clean_ruleset_interval=600
uuid=ffb9c20a-9c5e-4012-9cf1-03a5ca9a9820
serial=12345678
model_number=1
allow 1024-65535 192.168.0.0/24 1024-65535
allow 1024-65535 192.168.1.0/24 1024-65535
allow 1024-65535 192.168.0.0/23 22
allow 12345 192.168.7.113/32 54321
deny 0-65535 0.0.0.0/0 0-65535

启动 MiniUPnP

miniupnpd -f /etc/miniupnpd/miniupnpd.conf -i eth0 -d

UPnP 协议分析

相比于 OSI 七层模型(七层模型,亦称OSI(Open System Interconnection)参考模型,是参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系),UPnP 有 6 层

我们重点看其中的三个:发现、描述和控制。以下的图展示了这 3 层是如何组织在一起的:

The UPnP layers in action

(image credits)

这图看不懂没关系,先有个初步的印象,下文中会详细分析

UPnP 所有的内容已在官方文档的 1.12.0 版本中列出

发现

给定一个IP地址,UPnP网络中的第一步是发现。当一个设备被加入到网络中,UPnP检测协议允许该设备向控制点广播自己的服务。类似地,当一个控制点加入到网络中的时候,它也能够搜索到网络中存在的、感兴趣的设备相关信息。这两种类型的基础交互是一种仅包含少量、重要相关设备信息或者它的某个服务。比如,类型、标识和指向更详细信息的链接。

UPnP检测协议是基于简单服务发现协议的,简单服务发现协议(SSDP,Simple Service Discovery Protocol)是一种应用层协议,是构成通用即插即用(UPnP)技术的核心协议之一,SSDP 协议是在 HTTPU 和 HTTPMU 的基础上实现的协议(HTTPU 协议 是指在 UDP 基础上实现的通常在TCP上传送的HTTP协议。HTTPU 协议被主要运用在 UPnP 协议,特别是 UPnP协议簇中的 SSDP 协议)

MiniUPnP 启动后使用 wireshark 抓包会看到 MiniUPnP 会向专用组播地址 233.255.255.0 发出 SSDP 协议的 NOTIFY 请求来宣布他的存在

内容如下

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=120
LOCATION: http://172.16.1.134:31337/rootDesc.xml
SERVER: Linux/4.14.69-0-vanilla UPnP/1.1 MiniUPnPd/2.1
NT: upnp:rootdevice
USN: uuid:ffb9c20a-9c5e-4012-9cf1-03a5ca9a9820::upnp:rootdevice
NTS: ssdp:alive
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 1551501733
BOOTID.UPNP.ORG: 1551501733
CONFIGID.UPNP.ORG: 1337

因为 UDP 协议是不可靠的协议,所以他会发送多次, LAN 测并没有 UPnP 设备,因此 NOTIFY 请求不会得到回应

这就是 UPNP 协议的发现阶段

描述

UPnP 网络的下一步是描述。当一个控制点检测到一个设备时,它对该设备仍然知之甚少。为了使控制点了解更多关于该设备的信息或者和设备进行交互,控制点必须从设备发出的检测信息中包含的 URL 获取更多的信息。某个设备的 UPnP 描述是XML的方式,包括品牌、厂商相关信息,如型号名和编号、序列号、厂商名、品牌相关 URL 等。描述还包括一个嵌入式设备和服务列表,以及控制、事件传递和存在相关的 URL 。对于每种设备,描述还包括一个命令或动作列表,包括响应何种服务,针对各种动作的参数;这些变量描述出运行时设备的状态信息,并通过它们的数据类型、范围和事件来进行描述

当 LAN 侧主机搜索 UPnP 主控设备时,会向组播地址发送 M-SEARCH 包,而路由器收到包之后,会返回一个包来通知LAN侧主机

我们使用 UDP 协议向组播地址 233.255.255.0 的 1900 端口发送组播, python 代码如下:

import socket


M_SEARCH = b'M-SEARCH * HTTP/1.1\r\nHOST:239.255.255.255.250:1900\r\nST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nMAN:"ssdp:discover"\r\nMX:3\r\n\r\n'

def m_search(ip, M_SEARCH):
    data = ''
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.connect((ip, 1900))
        s.settimeout(3)
        try:
            s.sendall(M_SEARCH)
            while True:
                data = s.recv(4096)
                print(data.decode())
                if not data: break
        except socket.timeout as timeout:
            pass
        else:
            print(response)
            s.close()
            


if __name__ == "__main__":
    m_search('223.255.255.0', M_SEARCH)

使用 wireshark 抓包,过滤 SSDP 协议,我们会看到 MiniUPnP 回应了我们的组播

编号为 1 的包是我们发出的组播包,编号为 6 的包是 MiniUPnP 回应我们的包,如下图所示

组播包发送的 SSDP 请求内容是:

M-SEARCH * HTTP/1.1
HOST:239.255.255.255.250:1900
ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1
MAN:"ssdp:discover"
MX:3

收到的 SSDP 协议响应内容是:

HTTP/1.1 200 OK
CACHE-CONTROL: max-age=120
ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1
USN: uuid:ffb9c20a-9c5e-4012-9cf1-03a5ca9a9820::urn:schemas-upnp-org:device:InternetGatewayDevice:1
EXT:
SERVER: Linux/4.14.69-0-vanilla UPnP/1.1 MiniUPnPd/2.1
LOCATION: http://172.16.1.134:31337/rootDesc.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 1551501733
BOOTID.UPNP.ORG: 1551501733
CONFIGID.UPNP.ORG: 1337

UPnP 规范 1.12.0 版本中都记录了以上各个字段的作用和解释等所有内容

我们在本文中只关心 SSDP 响应内容中的 LOCATION,在这里为 http://172.16.1.134:31337/rootDesc.xml

浏览器打开访问,内容如下:

<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0" configId="1337">
  <specVersion>
    <major>1</major>
    <minor>1</minor>
  </specVersion>
  <device>
    <deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
    <friendlyName>Linux router</friendlyName>
    <manufacturer>Linux</manufacturer>
    <manufacturerURL>http://www.kernel.org/</manufacturerURL>
    <modelDescription>Linux router</modelDescription>
    <modelName>Linux router</modelName>
    <modelNumber>1</modelNumber>
    <modelURL>http://www.kernel.org/</modelURL>
    <serialNumber>12345678</serialNumber>
    <UDN>uuid:ffb9c20a-9c5e-4012-9cf1-03a5ca9a9820</UDN>
    <serviceList>
      <service>
        <serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>
        <serviceId>urn:upnp-org:serviceId:L3Forwarding1</serviceId>
        <SCPDURL>/L3F.xml</SCPDURL>
        <controlURL>/ctl/L3F</controlURL>
        <eventSubURL>/evt/L3F</eventSubURL>
      </service>
    </serviceList>
    <deviceList>
      <device>
        <deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
        <friendlyName>WANDevice</friendlyName>
        <manufacturer>MiniUPnP</manufacturer>
        <manufacturerURL>http://miniupnp.free.fr/</manufacturerURL>
        <modelDescription>WAN Device</modelDescription>
        <modelName>WAN Device</modelName>
        <modelNumber>20181224</modelNumber>
        <modelURL>http://miniupnp.free.fr/</modelURL>
        <serialNumber>12345678</serialNumber>
        <UDN>uuid:ffb9c20a-9c5e-4012-9cf1-03a5ca9a9821</UDN>
        <UPC>000000000000</UPC>
        <serviceList>
          <service>
            <serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
            <serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
            <SCPDURL>/WANCfg.xml</SCPDURL>
            <controlURL>/ctl/CmnIfCfg</controlURL>
            <eventSubURL>/evt/CmnIfCfg</eventSubURL>
          </service>
        </serviceList>
        <deviceList>
          <device>
            <deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>
            <friendlyName>WANConnectionDevice</friendlyName>
            <manufacturer>MiniUPnP</manufacturer>
            <manufacturerURL>http://miniupnp.free.fr/</manufacturerURL>
            <modelDescription>MiniUPnP daemon</modelDescription>
            <modelName>MiniUPnPd</modelName>
            <modelNumber>20181224</modelNumber>
            <modelURL>http://miniupnp.free.fr/</modelURL>
            <serialNumber>12345678</serialNumber>
            <UDN>uuid:ffb9c20a-9c5e-4012-9cf1-03a5ca9a9822</UDN>
            <UPC>000000000000</UPC>
            <serviceList>
              <service>
                <serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
                <serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
                <SCPDURL>/WANIPCn.xml</SCPDURL>
                <controlURL>/ctl/IPConn</controlURL>
                <eventSubURL>/evt/IPConn</eventSubURL>
              </service>
            </serviceList>
          </device>
        </deviceList>
      </device>
    </deviceList>
    <presentationURL>http://172.16.1.134/</presentationURL>
  </device>
</root>

这个 XML 文件就是此 UPnP 设备的描述文件,该文件详细描述了设备的各类信息,结构如下

(image credits)

我们重点看 service 标签, 内容如下

<service>
    <serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
    <serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
    <SCPDURL>/WANIPCn.xml</SCPDURL>
    <controlURL>/ctl/IPConn</controlURL>
    <eventSubURL>/evt/IPConn</eventSubURL>
</service>

其中

  • 服务类型 serviceType 为 WANIPConnection
  • 描述SCPDURL为 /WANIPCn.xml
  • 控制URL为 /ctl/IPConn

我们访问其中的 SCPDURL, 内容太多,我只留了一个省略了其他标签,内容如下:

<scpd>
    <specVersion>
        <major>1</major>
        <minor>1</minor>
    </specVersion>
    <actionList>
        <action>...</action>
        <action>
            <name>GetStatusInfo</name>
            <argumentList>
                <argument>
                    <name>NewConnectionStatus</name>
                    <direction>out</direction>
                    <relatedStateVariable>ConnectionStatus</relatedStateVariable>
                </argument>
                <argument>
                    <name>NewLastConnectionError</name>
                    <direction>out</direction>
                    <relatedStateVariable>LastConnectionError</relatedStateVariable>
                </argument>
                <argument>
                    <name>NewUptime</name>
                    <direction>out</direction>
                    <relatedStateVariable>Uptime</relatedStateVariable>
                </argument>
            </argumentList>
        </action>
        <action>...</action>
    </actionList>
    <serviceStateTable>
        <stateVariable>...</stateVariable>
        <stateVariable sendEvents="no">
            <name>Uptime</name>
            <dataType>ui4</dataType>
        </stateVariable>
        <stateVariable>...</stateVariable>
    </serviceStateTable>
</scpd>

此 SCPD xml 文件中需要重点关注的是 actionList 里的 action,这些action都是可以调用的

在上面的 xml 文件里 action 的名称为 GetStatusInfo,他有一个参数叫 NewUptime,参数的direction标签的值为 out,这说明 NewUptime 是返回参数,如果为in,则请求该action时我们需要提供此参数。

serviceStateTable 里的 stateVariable 又出现了一次 Uptime

stateVariable 说明了 action 的返回参数或请求参数的数据类型, ui4Unsigned 4 Byte int 无符号4字节整数

这就是 UPNP 协议的描述阶段

控制

UPnP网络的下一步是控制。当一个控制点获取到设备描述信息之后,它就可以向该设备发送指令了。为了实现此,控制点发送一个合适的控制消息至服务相关控制 URL(包含在设备描述中)。控制消息也是通过简单对象访问协议(SOAP)用 XML 来描述的。类似函数调用,服务通过返回动作相关的值来回应控制消息。动作的效果,如果有的话,会反应在用于刻画运行中服务的相关变量

我们对上文中的 GetStatusInfo action 发起 SOAP 请求,SOAP请求实际上就是 HTTP+xml

请求内容如下:

POST /ctl/IPConn HTTP/1.1
Accept-Encoding: gzip, deflate
Content-Length: 315
Soapaction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetStatusInfo"
Connection: close
User-Agent: Python-urllib/2.7
Host: 172.16.1.134:31337
Content-Type: text/xml; charset=UTF-8


<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/ SOAP-ENV="urn:schemas-upnp-org:service:WANIPConnection:1">
<SOAP-ENV:Body>
<m:GetStatusInfo xmlns:m="http://schemas.xmlsoap.org/soap/envelope">
</m:GetStatusInfo>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

调用结果如下:

返回内容中 uptime 的值为 91315 , 我们使用 python 转换为可读形式

1 day, 1:21:55

这时我们可以看出调用 GetStatusInfo 方法可获取该 UPnP 设备的运行时间,该设备运行了1天1小时21分钟55秒

这就是 UPnP 的控制层

至此 UPnP 协议的发现、描述、控制层都已经按照具体实例讲解完毕

我把此次实验所用的虚拟机已打包成ova文件上传到了网盘 登陆用户名:root,密码:alpine