{"id":1462,"date":"2016-03-10T03:26:06","date_gmt":"2016-03-10T07:26:06","guid":{"rendered":"http:\/\/jeffq.com\/blog\/?p=1462"},"modified":"2016-03-14T15:51:06","modified_gmt":"2016-03-14T19:51:06","slug":"dteenergy-insight","status":"publish","type":"post","link":"http:\/\/jeffq.com\/blog\/dteenergy-insight\/","title":{"rendered":"CVE-2016-1562: Unauthenticated &#8220;filter&#8221; parameter leads to customer information leak in the DTE Energy Insight app"},"content":{"rendered":"<h4>BACKGROUND<\/h4>\n<p>Here in southeast Michigan nearly all of our electricity (and a good chunk of our natural gas) comes from <a href=\"https:\/\/dteenergy.com\" target=\"_blank\">DTE Energy<\/a>, which serves 2.1 million people in the greater Metro Detroit area. DTE recently upgraded most of their electricity meters to <a href=\"https:\/\/en.wikipedia.org\/wiki\/ZigBee\" target=\"_blank\">ZigBee<\/a>-enabled smart meters, and as part of this rollout they released the <a href=\"https:\/\/www.dteenergy.com\/dteinsight\" target=\"_blank\">DTE Energy Insight app<\/a>\u00a0which 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\u00a0an &#8220;Energy Bridge&#8221;, a small device that connects to a\u00a0home network and monitors the ZigBee messages generated by a\u00a0smart meter to give real-time energy consumption information.<\/p>\n<figure id=\"attachment_1463\" aria-describedby=\"caption-attachment-1463\" style=\"width: 740px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/img-featureset3a.png\" rel=\"attachment wp-att-1463\"><img decoding=\"async\" loading=\"lazy\" class=\"wp-image-1463 size-full\" src=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/img-featureset3a.png\" alt=\"\" width=\"740\" height=\"470\" \/><\/a><figcaption id=\"caption-attachment-1463\" class=\"wp-caption-text\">The DTE Energy Insight app and the Energy Bridge device<\/figcaption><\/figure>\n<p>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&#8217;ve been tinkering with my <a href=\"https:\/\/www.ettus.com\/product\/details\/UB200-KIT\" target=\"_blank\">Ettus Research B200 SDR<\/a> to intercept the ZigBee messages\u00a0as well).<\/p>\n<p><!--more--><\/p>\n<h4>METHODOLOGY<\/h4>\n<p>By rooting my phone and using <a href=\"https:\/\/github.com\/madeye\/proxydroid\" target=\"_blank\">ProxyDroid<\/a>\u00a0to forward all traffic to a <a href=\"https:\/\/github.com\/mitmproxy\/mitmproxy\" target=\"_blank\">mitmproxy<\/a>\u00a0proxy running on my PC I deduced that the\u00a0Insight app was attempting\u00a0to connect to\u00a0<code>apps.dteenergy.com<\/code>\u00a0via TLS (non-TLS connections were\u00a0rejected). Even though I had the certificate authority that mitmproxy generates installed to the\u00a0trust store on my phone the app refused to communicate through the TLS proxy, so naturally I suspected some form of <a href=\"https:\/\/www.owasp.org\/index.php\/Certificate_and_Public_Key_Pinning\" target=\"_blank\">certificate pinning<\/a> or a custom trust store was being used by the app.<\/p>\n<p>Decompiling the app&#8217;s APK with <a href=\"https:\/\/github.com\/iBotPeaches\/Apktool\" target=\"_blank\">Apktool<\/a>\u00a0proved a somewhat frustrating experience; the app appeared to have been run through an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Obfuscation_(software)\" target=\"_blank\">obfuscator<\/a>. The <code>res\/raw\/<\/code> directory in the APK did provide hints, though; the app contained two .bks (<a href=\"https:\/\/bouncycastle.org\" target=\"_blank\">BouncyCastle<\/a> 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 <code>javax.net.ssl.TrustManagerFactory<\/code>.<\/p>\n<figure id=\"attachment_1464\" aria-describedby=\"caption-attachment-1464\" style=\"width: 887px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/Selection_001.png\" rel=\"attachment wp-att-1464\"><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-1464\" src=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/Selection_001.png\" alt=\"Decompilation of .bks loading procedure\" width=\"887\" height=\"437\" \/><\/a><figcaption id=\"caption-attachment-1464\" class=\"wp-caption-text\">Decompilation of .bks loading procedure<\/figcaption><\/figure>\n<p><code>res\/raw\/dtecomodo.bks<\/code> had resource ID <code>0x7f070009<\/code>, and the decompilation clearly showed that the password &#8220;vectorform&#8221; was being used. This code existed in the <code>com.vectorform.wattsonandroid.c.a<\/code> class, which let me know that <a href=\"https:\/\/www.vectorform.com\/\" target=\"_blank\">Vectorform<\/a>\u00a0developed the app for DTE.<\/p>\n<figure id=\"attachment_1465\" aria-describedby=\"caption-attachment-1465\" style=\"width: 863px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/Keystore-Report_002.png\" rel=\"attachment wp-att-1465\"><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-1465\" src=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/Keystore-Report_002.png\" alt=\"dtecomodo.bks contents\" width=\"863\" height=\"625\" \/><\/a><figcaption id=\"caption-attachment-1465\" class=\"wp-caption-text\">dtecomodo.bks contents<\/figcaption><\/figure>\n<p>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&#8217;t specifically pinning to a certificate for the API endpoint, but it enforced that the certificate that <code>apps.dteenergy.com<\/code>\u00a0presented 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.<\/p>\n<figure id=\"attachment_1466\" aria-describedby=\"caption-attachment-1466\" style=\"width: 674px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/home-jeffq-apk_dis-apktools-res-raw-dtecomodo.bks-Portecle_003.png\" rel=\"attachment wp-att-1466\"><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-1466\" src=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/home-jeffq-apk_dis-apktools-res-raw-dtecomodo.bks-Portecle_003.png\" alt=\"Modified dtecomodo.bks file\" width=\"674\" height=\"215\" \/><\/a><figcaption id=\"caption-attachment-1466\" class=\"wp-caption-text\">Modified dtecomodo.bks file<\/figcaption><\/figure>\n<p>The modified APK communicated through the mitmproxy &#8212; <a href=\"https:\/\/www.youtube.com\/watch?v=RthZgszykLs&amp;feature=youtu.be&amp;t=56s\" target=\"_blank\">success<\/a>!<\/p>\n<p><a href=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/jeffq@jeffq-desktop-mitmproxy-src_004.png\" rel=\"attachment wp-att-1470\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1470\" src=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/jeffq@jeffq-desktop-mitmproxy-src_004.png\" alt=\"mitmproxy capture of Insight app traffic\" width=\"732\" height=\"462\" \/><\/a><\/p>\n<p>Every API endpoint required that an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Basic_access_authentication\" target=\"_blank\">HTTP Basic Access Authentication<\/a>\u00a0header was\u00a0provided that contained the DTE customer&#8217;s username and password (the same one they use to access their online billing). The <code>IdentityService<\/code> endpoint returned a <code>dteSAML<\/code> variable, which needed to be included in all requests to endpoints that queried the customer&#8217;s actual energy usage.\u00a0Presumably this is a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Security_Assertion_Markup_Language\" target=\"_blank\">SAML<\/a> token, which likely\u00a0is passed along to backend DTE servers that actually monitor the customer&#8217;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 <code>api\/Customer<\/code> endpoint which returned a <code>DTEID<\/code> that could be used in some requests; <code>dteSAML<\/code> was only needed when querying actual usage data.<\/p>\n<p>Basic tests such as requesting information for a different <code>DTEID<\/code> via a GET to <code>api\/Customer<\/code> showed that most endpoints were correctly checking access controls. Of interest was the <code>api\/Notification<\/code> endpoint, which accepted a curious <code>filter<\/code> parameter. Un-URL-encoded, the parameter read as follows:<\/p>\n<p><code>DTEID eq &lt;dteid&gt; and IsRead eq false and NotificationType.IsNotification eq true<\/code><\/p>\n<p>This suggested that the <code>filter<\/code> parameter accepted arbitrary queries against a JSON-like database and returned the results.\u00a0I wrote a script to request arbitrary filter parameters; the only authorization needed was\u00a0the username and password for my DTE account passed as a Basic Access Authentication header.<\/p>\n<h4>VULNERABILITY<\/h4>\n<figure id=\"attachment_1480\" aria-describedby=\"caption-attachment-1480\" style=\"width: 928px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/captures-notifications_nofilter.json-Sublime-Text-2-UNREGISTERED_006.png\" rel=\"attachment wp-att-1480\"><img decoding=\"async\" loading=\"lazy\" class=\"size-full wp-image-1480\" src=\"http:\/\/jeffq.com\/blog\/wp-content\/uploads\/2016\/03\/captures-notifications_nofilter.json-Sublime-Text-2-UNREGISTERED_006.png\" alt=\"Sample result from a modified filter parameter\" width=\"928\" height=\"1056\" \/><\/a><figcaption id=\"caption-attachment-1480\" class=\"wp-caption-text\">Sample result from a modified filter parameter<\/figcaption><\/figure>\n<p>As suspected, the <code>filter<\/code> parameter was essentially a read-only SQL injection attack; the server would respond with whatever was\u00a0asked of it.\u00a0Thus, a filter of <code>Customer.Zipcode eq 48346<\/code> would return the app&#8217;s database for every user with a 48346 ZIP code.\u00a0In addition to <code>api\/Notification<\/code> there were a number of other endpoints that also accepted a <code>filter<\/code> parameter, e.g. <code>api\/CustomerProject<\/code>.\u00a0This resulted in the compromise of the entire database.<\/p>\n<h4>CONCLUSION<\/h4>\n<figure style=\"width: 666px\" class=\"wp-caption aligncenter\"><img decoding=\"async\" loading=\"lazy\" class=\"\" src=\"http:\/\/imgs.xkcd.com\/comics\/exploits_of_a_mom.png \" alt=\"Her daughter is named Help I'm trapped in a driver's license factory.\" width=\"666\" height=\"205\" \/><figcaption class=\"wp-caption-text\">This xkcd needs no introduction<\/figcaption><\/figure>\n<p>Classic SQL injection attacks rely on string manipulation to escape the value of a parameter and modify the behaviour of the underlying query.\u00a0While 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!).<\/p>\n<p>If I may editorialize, DTE Energy Insight is a pretty slick app. It&#8217;s clear that a lot effort was put into its user interface and design. Although this article doesn&#8217;t cover the Energy Bridge device, my tinkering with it has shown\u00a0me 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 <code>api\/Customer<\/code> don&#8217;t return information if a <code>DTEID<\/code> different than that of the logged in user is requested.<\/p>\n<p>The filter parameter was likely a dirty hack &#8212; I&#8217;m sure there&#8217;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&#8217;t guarantee the secrecy of an insecure API.<\/p>\n<h6>RESPONSIBLE DISCLOSURE<\/h6>\n<ul>\n<li>Jan 16, 2016: Vulnerability discovered<\/li>\n<li>Jan 28, 2016: Private disclosure to custserviceweb@dteenergy.com and social@vectorform.com<\/li>\n<li>Jan 29, 2016: Disclosure to <a href=\"http:\/\/cert.org\" target=\"_blank\">CERT<\/a><\/li>\n<li>Feb 3, 2016: CERT confirmed reception of report by DTE<\/li>\n<li>Feb 29, 2016: CERT reported that DTE fixed the vulnerability<\/li>\n<li>Feb 29, 2016: Fix confirmed<\/li>\n<li>Mar 1, 2016: Public disclosure scheduled for Mar 3, 2016<\/li>\n<li>Mar 2, 2016: DTE requested disclosure be pushed back to Mar 10, 2016<\/li>\n<li>Mar 10, 2016: Public disclosure (<a href=\"https:\/\/www.kb.cert.org\/vuls\/id\/713312\" target=\"_blank\">VU#713312<\/a>,\u00a0<a href=\"https:\/\/web.nvd.nist.gov\/view\/vuln\/detail?vulnId=CVE-2016-1562\" target=\"_blank\">CVE-2016-1562<\/a>)<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>BACKGROUND 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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[3],"tags":[],"_links":{"self":[{"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/posts\/1462"}],"collection":[{"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/comments?post=1462"}],"version-history":[{"count":38,"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/posts\/1462\/revisions"}],"predecessor-version":[{"id":1507,"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/posts\/1462\/revisions\/1507"}],"wp:attachment":[{"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/media?parent=1462"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/categories?post=1462"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/jeffq.com\/blog\/wp-json\/wp\/v2\/tags?post=1462"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}