Experiments with UPnP

This post deals with recent observations regarding UPnP (Universal Plug and Play) protocol & Routers. In few words, thanks to this protocol, devices (such as file sharing services, games, ..) can be easily connected/deployed.

First, I started to do research on my box to find relevant UPnP functionalities by hand, found some but I had trouble to exploit them. Few days ago, David Middlehurst released a tool called “UPnP Pentest Toolkit”.

Quick description of the tool that you can find on Github:

This tool aims to bring together a range of UPnP assessment features, enabling quick assessment with minimal configuration and set-up. It has been developed to aid security consultants in exploring, spoofing and manipulating of UPnP devices and the underlying protocols at work. It is intended as a proof of concept to be used for research purposes in a trusted environment.

I decided to try it, and I was able to browse all my devices having UPnpP functionalities. I’ll not go through all the features of the tool but feel free to check it out. I’ll mostly speak about my experiment using my router.

The version of my router didn’t “allow” me to NAT ports so I couldn’t access my internal SSH Server, Web servers, VPN etc. Clearly, marketing bullshit. However, thanks to this tool, I managed to enumerate the different functions that I was allowed to perform. Here is the list:

Actions:
    GetSpecificPortMappingEntry
        <-NewLeaseDuration
            Data Type: ui4
        <-NewPortMappingDescription
            Data Type: string
        <-NewEnabled
            Data Type: boolean
        <-NewInternalClient
            Data Type: string
        <-NewInternalPort
            Data Type: ui2
        ->NewProtocol
            Allowed Value 2: UDP
            Allowed Value 1: TCP
            Data Type: string
        ->NewExternalPort
            Data Type: ui2
        ->NewRemoteHost
            Data Type: string
    GetGenericPortMappingEntry
        <-NewLeaseDuration
            Data Type: ui4
        <-NewPortMappingDescription
            Data Type: string
        <-NewEnabled
            Data Type: boolean
        <-NewInternalClient
            Data Type: string
        <-NewInternalPort
            Data Type: ui2
        <-NewProtocol
            Allowed Value 2: UDP
            Allowed Value 1: TCP
            Data Type: string
        <-NewExternalPort
            Data Type: ui2
        <-NewRemoteHost
            Data Type: string
        ->NewPortMappingIndex
            Data Type: ui2
    ForceTermination
    RequestConnection
    GetConnectionTypeInfo
        <-NewPossibleConnectionTypes
            Allowed Value 3: IP_Bridged
            Allowed Value 2: IP_Routed
            Allowed Value 1: Unconfigured
            Data Type: string
        <-NewConnectionType
            Data Type: string
    SetConnectionType
        ->NewConnectionType
            Data Type: string
    DeletePortMapping
        ->NewProtocol
            Allowed Value 2: UDP
            Allowed Value 1: TCP
            Data Type: string
        ->NewExternalPort
            Data Type: ui2
        ->NewRemoteHost
            Data Type: string
    GetExternalIPAddress
        <-NewExternalIPAddress
            Data Type: string
    AddPortMapping
        ->NewLeaseDuration
            Data Type: ui4
        ->NewPortMappingDescription
            Data Type: string
        ->NewEnabled
            Data Type: boolean
        ->NewInternalClient
            Data Type: string
        ->NewInternalPort
            Data Type: ui2
        ->NewProtocol
            Allowed Value 2: UDP
            Allowed Value 1: TCP
            Data Type: string
        ->NewExternalPort
            Data Type: ui2
        ->NewRemoteHost
            Data Type: string
    GetCommonLinkProperties
        <-NewPhysicalLinkStatus
            Allowed Value 4: Unavailable
            Allowed Value 3: Initializing
            Allowed Value 2: Down
            Allowed Value 1: Up
            Data Type: string
        <-NewLayer1DownstreamMaxBitRate
            Data Type: ui4
        <-NewLayer1UpstreamMaxBitRate
            Data Type: ui4
        <-NewWANAccessType
            Allowed Value 4: Ethernet
            Allowed Value 3: Cable
            Allowed Value 2: POTS
            Allowed Value 1: DSL
            Data Type: string
    GetDefaultConnectionService
        <-NewDefaultConnectionService
            Data Type: string
    SetDefaultConnectionService
        ->NewDefaultConnectionService
            Data Type: string

            (...)

One of them was interesting. Saw which one? - the AddPortMapping functionality. Thanks to this, you can add a “Port Mapping” or directly create a NAT rule. Without authentication, just one HTTP Request.

Format of UPnP communication is based on SOAP (Simple Object Access Protocol) and its structure is composed of an “envelope”, a XML-based structure. Here is the structure for the AddPortMapping request:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">
            <NewRemoteHost></NewRemoteHost>
            <NewExternalPort>9999</NewExternalPort>
            <NewProtocol>TCP</NewProtocol>
            <NewInternalPort>9999</NewInternalPort>
            <NewInternalClient>192.168.1.91</NewInternalClient>
            <NewEnabled>1</NewEnabled>
            <NewPortMappingDescription>omgwtfbbq</NewPortMappingDescription>
            <NewLeaseDuration>0</NewLeaseDuration>
        </u:AddPortMapping>
    </s:Body>
</s:Envelope>

You can find useful information on UPnP hacks - contains amazing resources on UPnP.

Well, to send directly a SOAP from Linux, here is an “easy-way” if you want to do it by hand:

curl -X POST --data @body_soap.txt http://192.168.1.254:46465/ctl/IPConn -H 'SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"' -H 'Host: 192.168.1.254:46465'

And in body_soap.txt the XML structure with all the settings defined as we saw above.

If the request succeeded, the response should looks like this:

<?xml version="1.0"?>
<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:1"/></s:Body></s:Envelope>

So, in few words:

  • Learning stuffs about UPnP was fun and I’m sure there’s even more to do with it,
  • UPT is a fun tool,
  • I found nice tricks, and I can now NAT ports easily bypassing ISP’s stupid rules.

I think I’ll write another post on WFA-Device which stands for Wi-Fi Alliance Device and their UPnP functionalities. It seems there’s some fun stuffs about it.