Here in southeast Michigan nearly all of our electricity (and a good chunk of our natural gas) comes from DTE Energy, which serves 2.1 million people in the greater Metro Detroit area. DTE recently upgraded most of their electricity meters to ZigBee-enabled smart meters, and as part of this rollout they released the DTE Energy Insight app which allows customers to view their energy usage, set targets, and earn a host of achievements (no Steam cards sadly) when meeting different energy goals. In addition, at no charge DTE sends customers an “Energy Bridge”, a small device that connects to a home network and monitors the ZigBee messages generated by a smart meter to give real-time energy consumption information.
Given my curious nature I decided to poke around to discover how exactly the app and the Energy Bridge worked. This post is about a vulnerability in the app itself (although I’ve been tinkering with my Ettus Research B200 SDR to intercept the ZigBee messages as well).
By rooting my phone and using ProxyDroid to forward all traffic to a mitmproxy proxy running on my PC I deduced that the Insight app was attempting to connect to
apps.dteenergy.com via TLS (non-TLS connections were rejected). Even though I had the certificate authority that mitmproxy generates installed to the trust store on my phone the app refused to communicate through the TLS proxy, so naturally I suspected some form of certificate pinning or a custom trust store was being used by the app.
Decompiling the app’s APK with Apktool proved a somewhat frustrating experience; the app appeared to have been run through an obfuscator. The
res/raw/ directory in the APK did provide hints, though; the app contained two .bks (BouncyCastle keystore) files. Unfortunately, the keystores were password protected. However, the resource IDs for these files gave me an anchor in the code, and I was able to follow the decompiled code to the function that loads the keystores through
res/raw/dtecomodo.bks had resource ID
0x7f070009, and the decompilation clearly showed that the password “vectorform” was being used. This code existed in the
com.vectorform.wattsonandroid.c.a class, which let me know that Vectorform developed the app for DTE.
The keystore itself contained a certificate chain for the AddTrust External CA Root as well as the Comodo High-Assurance Secure Server CA, an intermediate authority. So, the Insight app wasn’t specifically pinning to a certificate for the API endpoint, but it enforced that the certificate that
apps.dteenergy.com presented must be issued by the specific AddTrust/Comodo chain given in the file. To bypass this restriction I added the mitmproxy root CA to the keystore and recompiled the app with Apktool.
The modified APK communicated through the mitmproxy — success!
Every API endpoint required that an HTTP Basic Access Authentication header was provided that contained the DTE customer’s username and password (the same one they use to access their online billing). The
IdentityService endpoint returned a
dteSAML variable, which needed to be included in all requests to endpoints that queried the customer’s actual energy usage. Presumably this is a SAML token, which likely is passed along to backend DTE servers that actually monitor the customer’s usage. This led me to believe that data for the application is managed separately from the actual usage data. This was further confirmed by investigating the
api/Customer endpoint which returned a
DTEID that could be used in some requests;
dteSAML was only needed when querying actual usage data.
Basic tests such as requesting information for a different
DTEID via a GET to
api/Customer showed that most endpoints were correctly checking access controls. Of interest was the
api/Notification endpoint, which accepted a curious
filter parameter. Un-URL-encoded, the parameter read as follows:
DTEID eq <dteid> and IsRead eq false and NotificationType.IsNotification eq true
This suggested that the
filter parameter accepted arbitrary queries against a JSON-like database and returned the results. I wrote a script to request arbitrary filter parameters; the only authorization needed was the username and password for my DTE account passed as a Basic Access Authentication header.
As suspected, the
filter parameter was essentially a read-only SQL injection attack; the server would respond with whatever was asked of it. Thus, a filter of
Customer.Zipcode eq 48346 would return the app’s database for every user with a 48346 ZIP code. In addition to
api/Notification there were a number of other endpoints that also accepted a
filter parameter, e.g.
api/CustomerProject. This resulted in the compromise of the entire database.
Classic SQL injection attacks rely on string manipulation to escape the value of a parameter and modify the behaviour of the underlying query. While not as serious as a full injection vulnerability (which would allow us to invoke the ghost of Bobby Tables), allowing an authenticated user to specify the full parameter to a WHERE-like clause is nearly as dangerous (especially if the table contains personal data on every user of your app!).
If I may editorialize, DTE Energy Insight is a pretty slick app. It’s clear that a lot effort was put into its user interface and design. Although this article doesn’t cover the Energy Bridge device, my tinkering with it has shown me that the engineers that worked on it were security contentious. The endpoints that deal with customer energy information require a SAML token, and those such as
api/Customer don’t return information if a
DTEID different than that of the logged in user is requested.
The filter parameter was likely a dirty hack — I’m sure there’s an engineer somewhere that cringed when they wrote it. Although the app is relatively obscure in the grand scheme of things the personal information of perhaps hundreds of thousands of users will always be a juicy enough target to warrant malicious activity. Techniques such as certificate pinning or custom trust chains protect against nefarious men-in-the-middle; they can’t guarantee the secrecy of an insecure API.
- Jan 16, 2016: Vulnerability discovered
- Jan 28, 2016: Private disclosure to firstname.lastname@example.org and email@example.com
- Jan 29, 2016: Disclosure to CERT
- Feb 3, 2016: CERT confirmed reception of report by DTE
- Feb 29, 2016: CERT reported that DTE fixed the vulnerability
- Feb 29, 2016: Fix confirmed
- Mar 1, 2016: Public disclosure scheduled for Mar 3, 2016
- Mar 2, 2016: DTE requested disclosure be pushed back to Mar 10, 2016
- Mar 10, 2016: Public disclosure (VU#713312, CVE-2016-1562)
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.