Moonpig are one of the most well known companies that sell personalised greeting cards in the UK. In 2007 they had a 90% market share and shipped nearly 6 million cards. In July 2011 they were bought by PhotoBox. But a security flaw means that over 3 million customers personal details was exposed.
Let’s dive straight in and take a look at one of their HTTP requests from the Android app to the Moonpig API.
GET /rest/MoonpigRestWebservice.svc/addresses?&customerId=5379382&countryCode=9424 HTTP/1.1 Authorization: Basic aXBjiS5lOk1vb25QHjimvF58DEw Host: api.moonpig.com Connection: Keep-Alive
Okay so we’re using basic authentication, not ideal sending our username and password over in each request (as opposed to a sesssion key) but at least it’s over HTTPS - it could be worse.
Oh, it is worse. Decoding the auth header we get
*redacted*:*redacted*, that’s not my username or password - these are static credentials sent with every request. The only identifiable piece of information left is the URL parameter
customerId. I created another account, added an address, changed the URL to my new
customerId and lo and behold it spits out my saved addresses for the other account:
"Address": "xxxxxx\r\nxxxxxxx\r\nxxxxxxx", "AddressBookId": 414628930, "AddressType": "CustomerAddress", "AddressTypeId": 1, "Anniversary": null, "Birthday": null, "BuildingName": null, "BuildingNumber": null, "Company": "Test", "Country": "United Kingdom", "County": "London", "Custom1": null, "Custom2": null, "Custom3": null, "Custom4": null, "Custom5": null, "CustomerId": 0, "Deleted": false, "DeliveryInstructions": null, "EmailAddress": null, "FacebookId": null, "FilterChar": null, "Firstname": "Test", "Greeting": null, "LastUpdated": "\/Date(147136045396670+0100)\/", "Lastname": "Test", "MainAddressBookId": null, "OtherDate": null, "Postcode": " LN1 3FN", "PostcodeSystemUpdated": null, "SortByLastName": false, "Suffix": null, "TelephoneNo": null, "Title": "", "TitleId": null, "Town": "London"
Every API request is like this, there’s no authentication at all and you can pass in any customer ID to impersonate them. An attacker could easily place orders on other customers accounts, add/retrieve card information, view saved addresses, view orders and much more.
At this point one would usually decompile the APK and see if there are any hidden API methods but on this occasions there’s no need, Moonpig have made it easy for us. If you hit the API endpoint with an unknown method you’ll get a custom 404 with a link to a help page listing every method available in their API with helpful descriptions. The help page also exposes their internal network DNS setup - but that’s another story.
From the help file it does seem that the API supports OAuth 2.0 authorization which would fix this vulnerability… if it was implemented in the Android client.
One particular method caught my attention:
GetCreditCardDetails. Surely not? I hit the method with my test customer ID and we are returned:
<ArrayOfCustomerCreditCard xmlns="http://schemas.datacontract.org/2004/07/Moonpig.Model.CustomerAttributes.Accounting" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <CustomerCreditCard> <CardType>Credit Card (Unspeci</CardType> <CustomerId>11466749</CustomerId> <ExpiryDate>12/18</ExpiryDate> <LastFourDigits>5993</LastFourDigits> <NameOnCard>Mr X XXX</NameOnCard> <TransactionId>5983632541-1/TransactionId> </CustomerCreditCard> </ArrayOfCustomerCreditCard>
Hey, at least they’re not returning the full card number!
I hit my test users a few hundred times in quick succession and I was not rate limited. Given that customer IDs are sequential an attacker would find it very easy to build up a database of Moonpig customers along with their addresses and card details in a few hours - very scary indeed.
- 18th Aug ‘13 - (yes, 2013!) Initial contact made with vendor. After a few e-mails back and fourth their reasoning was legacy code and they’ll “get right on it”.
- 26th Sep ‘14 - Follow up e-mail. Issue still not resolved. ETA “before Christmas”
- 5th Jan ‘15 - Vulnerability still exists with ample amount of time given to vendor to fix the issue.
Initially I was going to wait until they fixed their live endpoints but given the timeframes I’ve decided to publish this post to force Moonpig to fix the issue and protect the privacy of their customers (who knows who else knows about this!). ~17 months is more than enough time to fix an issue like this. It appears customer privacy is not a priority to Moonpig.