Project Brief
Having previously worked with a number of freelance developers, Movem came to us looking for a more stable and long term development partner to support and develop their site. Their first requirement was to ensure that the code base was leaner, cleaner and more secure; after which we were to focus on overhauling the user interface to make it more attractive and navigable, including new filters, search bars and styles. Finally, a number of new features needed to be implemented to help grow the site and to better serve its members, including a custom advertising system and mapping tools to help track the distance between a property and local points of interest.
Vulnerabilities Discovered
Lack of Encryption
There was an encryption system in place which stored the contents of each document field in an encrypted form in a Firestore database. This meant that even if access to someone’s documents was possible, the only things that could be seen were encrypted field values. Note that the structure of a document was not encrypted, only the actual content of each field.
That sounds good on the surface, but there were two major problems with this. The first was that encryption and decryption were done client-side and the key was shared for all users. In short, the web app running in your browser would fetch the encrypted fields from Firestore when viewing your document, but when you viewed your document they would all be decrypted. This worked because everything necessary to do the decryption was stored within the browser in the web app. That meant it was trivial for anyone to decrypt any encrypted field for any document that they might be able to access. This made the encryption completely redundant and it became merely obfuscation (i.e. hiding the values).
The second problem was that there was a subtle bug in the encryption routines in the app. There was a large secret key defined in the application’s environment file and the intent was for this key to be used for encryption. The larger the key the better, of course, as it makes it harder to guess. However, due to the way that this was being included in the project, the web application itself did not successfully retrieve this value when being built. That meant that the key became the JavaScript value of “undefined”, which was then being converted to a string in the app. This meant that the encryption key was literally the word “undefined” and that everyone’s document data was encrypted with this simple word as the key.
Whilst we could fix the “undefined” key issue easily, this still wouldn’t solve the main problem. Because the encryption was being done client-side, the long and complicated key would simply have been included as part of the web application’s source code. This would mean that anybody could see it, defeating it’s purpose. Solving this properly therefore involved generating a key for each user and performing encryption based on that user’s unique key.
Lack of Authentication
Some of the cloud functions were protected by a separate function which verified whether the user was authorised to call the other functions. This verification would usually be done by a username/password combination, or preferably by way of an authentication token. However, in this instance the function simply checked that the user ID (UID) provided existed in the list of users, then assigned that user to the request as the authenticated user.
The problem with this was that the app contained several information disclosure vulnerabilities which made it relatively easy to find the UID of other users. The UID was not classed as privileged information and was therefore included in things like invitations, which made finding UIDs quite trivial. As this was the only piece of information required to authenticate as a user, you were able to call any cloud function as any user you wanted. This would allow you to view the complete documents for any user and to invite any other user to any document.
To compound this, not only did it simply check that the UID existed, but the protected function didn’t actually check that the user with that UID was allowed to access a document. This meant that to access someone’s documents, all you needed was any valid UID and you could then access their documents by ID, regardless of whether you were listed as an owner.
Security Rules Not Correctly Defined
When analysing the security rules defined for Firebase Firestore (the database system used for the project), we discovered that reads/writes to all collections were controlled simply by a user being logged in. Furthermore, some collections didn’t have any security rules defined, meaning that they were publicly accessible.
To demonstrate this we wrote a simple script. The script used only Firebase configuration information which was publicly accessible via the site’s source code. By connecting to Firestore using this configuration, it was possible to list all documents and their content. Following this, we attempted to list all user details in the same manner, but were unable to do so. However, by logging in as a simple user created via a basic sign-up, it was immediately possible to access details for all users. Even more worryingly, it was also possible to update another user’s information (such as names, addresses and profile photos), even without being logged in as that user.
We have already touched on a similar security issue within the cloud functions section above, but the impact of that was far less severe. With this issue a bad actor could have quite easily viewed, changed or deleted all of the information in the entire system.
In order to fix this, we needed to go through the Firestore security rules and ensure that they limited access properly. As an example, the authenticated user should only be able to read and update their own documents and they should only be able to read plans on which they collaborate. By collaborating with the client, we were able to update the cases to ensure permissions were assigned correctly.
User Email Discovery and Possible Denial of Service
The web app used an email address and password as a way to login, and authentication services were provided by the Google Identity Toolkit. Unfortunately, it was possible to determine email addresses of valid user accounts using this system. When attempting to login using an email address that didn’t exist, you would receive a response with the message “EMAIL_NOT_FOUND”. Through trial and error, a valid email address could be found by looking for a different response.
In the case that a valid email address was used, the message returned would contain “INVALID_PASSWORD” instead of “EMAIL_NOT_FOUND”. This showed to attackers that the email address was valid. For attackers, the first step in trying to access another user’s account is to find the username and/or email address. Once found, this can open up other attack vectors as demonstrated below.
Furthermore, after trying repeated invalid login attempts, the response indicated that access to the account had been temporarily disabled. Unfortunately, this was a global restriction, making it an effective way to deny service to any user account.
Example Responses
{'error': {'code': 400,
'errors': [{
'domain': 'global',
'message': 'EMAIL_NOT_FOUND',
'reason': 'invalid'}],
'message': 'EMAIL_NOT_FOUND'}
}
{‘error’: {‘code’: 400,
‘errors’: [{
‘domain’: ‘global’,
‘message’: ‘INVALID_PASSWORD’,
‘reason’: ‘invalid’}],
‘message’: ‘INVALID_PASSWORD’}
}
Weak Passwords Allowed During Sign-Up and Password Change
During testing, the password “secret” was successfully used during sign-up. Furthermore, passwords could be easily changed afterwards to weak passwords such as this. The only restriction was that a password had to contain at least six characters. Factoring in the previous issue of email discovery, it was possible that email addresses could be verified first, then each valid address could be used to attempt a brute-force login attack using common passwords.
Potentially Sensitive User Information Made Publicly Available
A Google Cloud function called checkUsers was publicly accessible. This could be used to find information about existing user accounts using their email address. As we’ve previously covered, the web app contained email disclosure vulnerabilities that made this information easily accessible.
Calling the checkUsers function provided a detailed and human readable output of all information, including the user’s details and their associated business information. It also provided all documents associated with the user, including the full contents of each. For users with lower privileges, only basic information was displayed. However, for every single user, the user ID was included in the output. Due to the nature of other vulnerabilities within the app, the user ID was highly exploitable.
Example Response
{'status': 'success',
'user': {'displayName': None,
'email': 'example@webapp.com',
'emailVerified': False,
'photoURL': None,
'docInfo': {'0k8ox6PcDHhnjmuC5Hce': {
'docData': {[DATA_REDACTED]}},
'cGvbWM9IYQkF0nkgmwu5': {[...]},
'vMwmJWglB26Yp8f4fiSw': {[...]},
'wjZnE0MRSiSGlwNnEMf9': {[...]}},
'uid': '[UID_REDACTED]'}}
Adding Arbitrary Collaborators
The application allowed users to create documents and to then invite other users via email. Once invited, the target user received an email from which they were able to view an NDA and accept or decline the invitation. If the invitation was accepted, the user with that email address was added as a collaborator to the document. It was possible however to add arbitrary users to a document. All that was needed was the document ID and the ID of the user you wished to add. The document ID could be obtained by receiving an invitation, whilst the target user ID could be found using the issues outlined in previous sections.
The server was happy to accept any requests to handleInvitationsAccept, which allowed you to add any user to any document of your choice. This required only the parameters for user ID, document ID and user email, and would immediately add the relevant user without first having been invited. Viewing the document after performing this request showed that an additional collaborator had been added, and viewing the response payload from Firestore also confirmed this.
In addition, no validation was actually done on the target user ID, so any arbitrary text could be added there. As an example, entering “this-is-a-test” would add a user called “this-is-a-test”, whether they existed in the system or not.
Using the System to Send Spam
When sending an invitation email, the subject and HTML body content were sent to Google Firestore from the client. This would allow a malicious user to modify the subject and body content, allowing them to send arbitrary emails which appeared to come from the actual company. As a couple of examples, a malicious user could simply send spam messages to anybody using the company’s email servers, or they could modify the email’s links to point to their own domain for the purpose of phishing.
Conclusions and Following Steps
The new Movem website was far more usable and attractive to users, providing a much better range of tools with which to review previously rented houses and to identify potential new houses which match their rental requirements. This was reflected in the increased number of visitors and housing reviews which appeared on the new site, and Movem was able to make the leap from being a student focused property site, into a main stream general lettings site. Movem has also managed to further raise a number of investment rounds – including two rounds of crowdfunding – which stands as a testament to the trust and interest which the site has built with its user base and the wider public.
Similar Projects
Get In Touch
Thank you for your interest in our business. If you have any questions about our services, a project you’d like us to help with, or if you just want to say hello, please don’t hesitate to get in touch. We look forward to hearing from you!