Shopify Athena Bug

Starting with...

For past few months, I have switched my target in bug bounty to Shopify in hopes to find vulnerabilities on it. Usually before I start hacking a program, I try to set certain goals on what I want to achieve out of it. For example, sometimes it will be that I want to find X bugs on the program or sometimes it could be that I want to find X type of bug in the program. For Shopify however, I decided to set my goal as: "I want to gain access to internal information or applications that Shopify uses". I intentionally set the goal really vague because there are multiple ways to gain that access: 1) SSRF, 2) RCE, 3) Unauth access to potentially sensitive sites like JIRA. With that in mind, I decided to start programming my reconnaissance script to look specifically for those kind of things.


Hacking Shopify...

I submitted my first bug on Shopify about 9 months ago. At that time, it was an out of scope vulnerability of a feature that was slowly launching on Shopify. This product was Shopify Exchange. After I found that bug, I took a break from hacking on Shopify but then I decided to come back at it after the product was put in scope by Shopify. Soon after my test, I ended up finding an IDOR that allowed me to change images/uploads of other users that used the product: 322661. After this bug was reported, I decided to focus on Shopify for next few months to not just find bugs but to actually understand the platform and the company. I wanted to see what products they had, what they used internally (programming languages, vendors etc). As I did that, I noticed that I was spending a lot of time to manually search for assets and products so I decided to automate some of these. When doing so, I ended up finding access to a support dashboard page for their DataDog system: 345152. Anyways, after spending quite some time on sharpening my codes and make them better, I ended up creating an alert system that finds new products, news and subdomains of their internal and public domain and alert me on my Slack instance.

I set the alerts to scan for anything new on *.shopifycloud.com because previously during my manual recon and my talk with some Shopify folks I found out that some of these went to shopify.okta.com which hinted that some internal Shopify sites were also hosted here. So one day during the alerts, I got an alert for a new subdomain in crt.sh. These subdomains were:

1. athena-flex.shopifycloud.com

2. athena-flex-production.shopifycloud.com

Once I noticed these, I decided to check the screenshot that my server had taken of the website. The screenshot was a blank white page with no content on it which was really interesting. So I decided to view the page but to my surprise it was redirecting to Okta.

However, right when I was about to close it, I decided to use Burp and intercept the request. During this I found that the website would load momentarily and then redirect to Okta. So, I dropped the redirect and checked the html of the page. Initially I had no idea what this website was or what it was designed for so I decided to analyze the JS file that they had on the site.


Analyzing the JS...

During the analysis, I found that there was a /graphql endpoint. The endpoint was loading fine and was allowing POST request. However, I still needed to see what kind of queries this endpoint allowed so I looked into the JS again and saw that it had about 3 different query samples that I could use. One of the queries was:

{"query": "query getRecentTicketsQuery($domain: String) {\n shop(myshopifyDomain: $domain) {\n zendesk {\n tickets(last: n) {\n edges {\n node {\n id\n requester {\n name\n }\n subject\n description\n }\n }\n }\n }\n }\n }\n","variables":{"domain":DOMAIN_NAME}}

Translating the query to human readable language: This would ask the service to list last n number of support tickets for the domain DOMAIN_NAME and when returning results, it would return the requester name, subject of the support ticket (usually title) and the description (content of the ticket) .

Once I had the graphql query that I could run, I added a random domain name and made a query to show me the last 5 tickets that contained that domain name. After waiting for a while the service responded with 9,259 bytes of JSON data. In this response, it included the full details of the support ticket (including any follow ups that the support agent had done), name of the requester and the subject. Additionally, if someone wanted to be malicious, they could also create support tickets on behalf of other agents because there was a graphql query that you could send which would allow the operation.

This in the end, could lead to massive disclosure of user information and confidential information which are shared through the support tickets. Once I had enough PoC and a full written report, I sent it to Shopify's team. At the same time, I was talking with Pete Yaworski so he notified the oncall person to make sure the report was handled as soon as possible. The service was then taken down as a pre-caution. After that, the team moved the service completely over to Okta making sure the graphql endpoint won't work without authentication.

Week after the report, I saw multiple changes on the Shopify infrastructure. All of the internal domains that previously required Google Authentication, were moving to Okta and Google Authentication (GSuite login) in itself was also migrated over to Okta.