TL;DR: How a leading service in its niche broke 5 of the OWASP Top-10 categories on just one page with one cookie.
Overview
This post recounts a critical flaw I discovered in a particular cloud service that enabled arbitrary account takeovers of all accounts, including administrators. The crux of the issue lay in a custom-coded administration panel that trusted users to not lie about who they were. I suspect the flaw was there for the duration of that administration panel’s existence, but never determined the full extent of the flaw or if it had been abused in the past.
Caveats
As the company involved pushed back pretty hard on publicly acknowledging this issue, I have obfuscated this post to try to protect their identity. Some details in this write-up have been edited to that end and I will simply refer to the company as Generic Cloud Services Inc (GCSI1).
I also want to very clearly delineate between GCSI’s corporate response and the response of the tech support technician who I interacted with, whom I will call “Jason”. Where GCSI pushed back on public disclosure and downplayed the seriousness, Jason responded within literal minutes of my requesting a call to discuss a security matter and stayed late on a Friday night to ensure it was closed within hours of being reported. I am very grateful to have dealt with Jason and not others in the company.
Background
While helping out non-profit organizations I am affiliated with, I have often found that the software and services aimed at them are lacking. Many of the services I have interacted with have been smaller development groups that specifically cater to the needs non-profits of one sort or another. This means they often don’t have either all the resources they need or the external attention to help close those gaps.
Recently2, a non-profit organization (we’ll call them NPO) associated with a group I work with had to close. This was an unfortunate situation and non-profit’s former leader reached out to me to ask for help preserving their data on a Friday afternoon. His goal was simple: to ensure the work he poured his life into continued to help people. I knew I could easily host their data on another service for close to free, but I needed to get the data. Just as unfortunately, for Reasons™ the former leader did not have the ability to provide it to me right then and I had to figure out how to download it. GCSI had a pretty strict policy of deleting data shortly after bills stopped being paid, so we had a short amount of time to get the data off and migrated.
The Situation
Both NPO and my organization used GCSI for our data and I had about eight years of experience with their services. GCSI does not offer any form of “click to download your data” button and has sub-optimal search, which conflates user data with the organization they are a part of at the time of the search. For example, if I worked for NPO and then later changed memberships to my organization, all my work at NPO would show up as my current organization. This meant that what should be a bunch of curl commands actually needed to crawl GCSI’s search pages and have some logic to decide which hits were valid and be able to pair metadata on the page with the content once downloaded.
The Administration Panel
At GCSI, logged in users associated with an organization use a custom ASP.net administration panel to handle user data. It involved3, a popup to login, then a manual page refresh to get the home page to recognize I was logged in, then clicking through to the panel, then ANOTHER login prompt.
The upside to the administration panel is it offered a text file that provided the metadata for all the organization’s current data. From this metadata, it was fairly trivial to compute links to the actual content and save the relevant file metadata in the resulting content…. as long as you could get to the administration panel. Unfortunately, I could not get to the other NPO’s panel and the former NPO leader could not get in.
The Fatal Flaw
While looking for a better way to write a pile of curl scripts, I started poking around the network tab of Firefox’s dev tools. I noticed that in the 22 cookies that GCSI set, a few were interesting from an authentication standpoint.
- Session ID: This was your typical UUID v4
- Another Session ID: Again, UUID v4
- User Name: This was the current logged-in user’s username
As I pondered the time it would take to match up all the content with metadata using GCSI’s search features, I thought I would kick off one tiny curl command. To test if the user name was trusted, I removed 21 of the cookies and tried just my username, like this:
curl "https://genericcloudservicesinc.com/admin/data_raw.asp" -H "Cookie: user=my_nonprofit"
Surprisingly, it worked, and I received all the data for my non-profit in a lovely comma-delimited format. The session IDs (and 19 other cookies) clearly didn’t make a difference.
Naturally, the next move was to see if it would validate a different user and give me NPO’s data making this small change:
curl "https://genericcloudservicesinc.com/admin/data_raw.asp" -H "Cookie: user=NPO"
On no… it worked again.
GCSI used Cloudflare to defend them from DDoS attacks and generated secure session IDs when the user logged in (twice), but just a string-compare on an unsigned cookie was used to authenticate users. This worked not just on the raw data endpoint I was using, but any of the endpoints on the administration panel. This meant that theoretically anyone changing the cookie value could “log in” as another user, change their password, and take over the account.
I started working on a proof of concept to turn theory into reality and things got even worse. As I curl’d the password endpoint, I discovered that the password field was… my actual password.
<html>
...snip...
<input type=password size=40 maxlength=30 name="MemberPassword" value="[my password]"><br>
...snip...
</html>
I had never realized the actual password was on the page because GCSI used a “password” text field so it looked like they just filled it in with default stars and I would never have thought they actually included the real one.. This meant that anyone abusing the user name cookie bug wouldn’t even have to change the password for long-term access, just harvest the passwords in bulk. This was extremely scary given the high amounts of password reuse human beings still exhibit, the fact that member usernames were public, and that other pages on the administration panel would tie them to email addresses and billing information. This opened up massive opportunities for spear-phishing, credential-stuffing, and general defacement.
Side note: This was mind-blowing for me to discover for three reasons: 1) No site should ever display a user's password, 2) GCSI kept its users' passwords stored as plaintext, and finally 3) Someone at GCSI knew that #1 was true and thought that using a password field which wouldn't show it to the user directly, but included the password in the source, was an acceptable answer. Industry standard would store the password encrypted and salted and never show it to the user, just give the opportunity to reset.
At this point I had solved my earlier problem and could easily pull down NPO’s data, yay! Unfortunately, I had created another problem: I had just found a catastrophic 0-day for GCSI and was supposed to be sitting down to dinner in 5 minutes.
The Solution
Being literally 5:48 PM on a Friday evening, I was worried I wouldn’t be able to find anyone. I sent a quick email off to GCSI asking for a security point of contact to call me as soon as possible. I shockingly got an email from Jason two minutes later asking what the issue was, to which I reiterated a request for a phone call or encrypted email. Jason did not understand what could possibly require a phone call, but had no security POC at his company and no way to receive encrypted emails, so he finally called.
As we spoke, Jason was a bit incredulous that the issue was bad enough to justify my letting home-made pizza get to room temperature. He did not believe this worked on any account, and created a new administrator account for me to test just how bad things were. Of all the details I changed in this post, the given username and password are exactly what we tested with and this was the entirety of my proof of concept:
[notta@cuppa ~]$ curl 'https://genericcloudservices.com/admin/password.asp' \
-H 'Cookie: user=test001;' \
| grep MemberPassword
<input type=password size=40 maxlength=30 name="MemberPassword" value="test123"><br>
When I read his password back to him seconds after he gave me the account name, Jason’s tone of voice changed and he immediately started working towards a resolution. He figured out how to set up PGP so that I could email over the specifics we had just discussed. By 10:37 PM that evening, Jason had wrangled his team to push out a code deployment that fixed the issue. Jason provided me $120 worth of advertising credit on GCSI’s site and, very kindly, covered two months of payment for the NPO that went under so that we could migrate the data on a sane timetable.
Side note: This example highlights the benefit to having a functional proof-of-concept when you report an issue to a vendor. Too often it is easy to downplay the actual effectiveness or potential damage of an attack without seeing it first hand. In this case, it was clear from his voice that Jason did not expect me to get the password, and certainly not almost immediately after he finished telling me the test username. However, I already had primed the proof of concept for the conversation in case there was push back and just needed to fill in the username.
The Aftermath
I really wish this was the end of the story and that I could say Jason’s mindset was the corporate response of GCSI. Although I stressed to Jason during our phone call and in email the importance of notifying all of GCSI’s users and administrators to change their potentially compromised passwords and suggested the right response would be resetting everyone’s passwords for them… two weeks went by and I heard nothing as a subscriber of GCSI’s services.
I reached out to Jason again at day 13 and Jason replied they were fast-tracking a new administration panel that they would move everyone over to. At day 24, GCSI finally told all of its subscribers about a new administration panel, but only recommended users “check it out”, didn’t force them too. Rather than owning the fatal flaw that likely was present for years, GCSI just included this blurb: “Update your password regularly and also be sure to update your payment information.” As users, we see that every day from all of our service providers, it is not an effective call to action.
Even worse, the new password reset had another fatal flaw, the form was submitted via a GET request as such:
https://genericcloudservices.com/new-admin/password?oldpass=test1234&newpass=test4567
Normally, sensitive data like this should be part of a POST request so passwords aren’t sprayed all over /var/log/www. With the details as part of the request itself, default logging captures them, whereas most logging does not include POST data. Sadly, that means any users that did read GCSI’s statement as a call to action immediately disclosed their old and new passwords to anyone with access to GCSI’s log files. A few days later, this was resolved and the password endpoint used POSTs instead of GETs, but to this day, GCSI’s customers have never been alerted to the danger.
Over the next 30 days, I repeatedly urged GCSI to alert its users to the four security issues (passwords stored in plaintext, passwords displayed in the text of their website, authentication based purely on username, and passwords stored in log files) to prevent credential-stuffing on other services such as finance and email. I suggested they administratively reset everyone’s password to force a roll-over. I went so far as to write a brief and very generous statement for GCSI about how quickly they found and fixed the issues in case they had no experience alerting users to these sorts of issues. Jason replied to each email noting that he had passed it on to his management. I was assured the company president was aware. Nothing ever happened.
Conclusion
GCSI operated for (presumably, based on Jason’s shock at what I told him) years with absolutely critical vulnerabilities present in its code-base, hitting 5 (A01, A02, presumably A04, A07, and arguably A09) of the OWASP Top-10. Its users are predominantly non-technical and very trusting. Its programmers had a threat-model based more around “What if this content wasn’t available?” than “What if someone actively probed our system?” While I am glad to have helped close this hole, to this day I am concerned about what other flaws have been covered up and am a begrudging client of GCSI, mainly due to the specific market niche they have cornered.
What lessons can be learned from this4? Be vigilant if you are in the small business or non-profit categories and dealing with service providers filling niche roles that might not be the big name players out there. The service provider might not be resourced any better than you are, or might depend entirely on volunteers. Secondly, if you see something, say something. That service provider might actually be hearing something for the first time that you assume is a basic security practice.
Footnotes
-
To be absolutely clear that I am not playing clever games with the name, I want to specify I do NOT mean this GCSI. I came up with the acronym separately and they are not a cloud provider. ↩
-
Please don’t take these dates literally, I am obfuscating the exact timeline to protect the service. This did happen on a Friday afternoon and the number of days I list in between events is accurate, but the exact month and year will remain obfuscated. ↩
-
I kid you not. ↩
-
I’m not including any of the OWASP categories as learning opportunities because by dint of how it operates, OWASP is capturing lessons that were ALREADY learned the hard way, they shouldn’t be learned again. ↩