As part of Checkmarx's mission to help organizations develop and deploy secure software, the Security Research team started looking at the security posture of major car manufacturers. Porsche has a well-established Vulnerability Reporting Policy (Disclosure Policy)[1], it was considered in scope for our research, so we decided to start there, and see what we could find.
What we found is an attack scenario that results from chaining security issues found on different Porsche's assets, a website and a GraphQL API, that could lead to data exfiltration. Data exfiltration is an attack technique that can impact businesses and organizations, regardless of size. When malicious users breach a company's or organization's systems and exfiltrate data, it can be a jarring and business-critical moment.
Porsche has a diverse online presence - deploying several microsites, websites, and web applications. The Porsche Experience [2] is one website that allows registered users to manage a virtual garage, book experiences (such as track days), as well as manage bookings and invoices. From a technical perspective this website is a single-page application (SPA) backed by a GraphQL API (https://experience.porsche.com/graphql) used to fetch data and perform operations such as user authentication, user profile updates, book events, etc.
While initially exploring the website, the team noticed some interesting API requests. More specifically the jwtToken cookie and the Appauthorization HTTP request header both had the same value.
The image above shows the original API request issued by the website front-end to retrieve the user profile after a successful login attempt. On the left (Request) you can see the duplicate value.
This was enough to produce a hypothetical Cross-site Request Forgery (CSRF) [3] attack scenario, leading us to wonder whether the API would look for the authentication token in the jwtToken cookie if the custom HTTP request header Appauthorization was missing.
Including API auth tokens in a request header, rather than Cookie, are a game changer for Cross-Site Request Forgery (CSRF). Web browsers, unlike cookies, do not include such headers automatically, which must be done by some front-end custom logic (JavaScript).
To answer our question, we replayed the original request without including the Appauthorization request header. When we received the same response back from the API server, we confirmed our theory: the API retrieves the auth token from cookies when the custom request header is not present.
We had another question in mind that also needed to be answered: would the API server allow requests from origins other than porsche.com?
The answer to this question was also a resounding "yes."
As you can see in the image above, the request was made from a different website, which is reflected in the Access-Control-Allow-Origin [4] response header, indicating that the response can be shared with the requesting code from the given origin. Moreover, the API server also tells browsers to expose the response to the front-end JavaScript code when the request's credentials mode is include [5].
Typically, to be able to perpetrate a CSRF attack from an attacker's-controlled website the victims' web browsers must automatically include the jwtToken cookie in the API requests. That was not the case for Porsche Experience: the jwtToken cookie SameSite attribute was set to Lax.
The SameSite attribute [6] controls whether a cookie should be sent with cross-site requests providing some protection against CSRF attacks. Lax means that the cookie is not sent on cross-site requests[7], and it is the default value when not specified at the time the cookie is set. We would not be able to make request to GraphQL API from a website controlled by us, but the definition of "Site" and "Same Site" [8] still leaves us an opportunity.
Any website served from a subdomain of porsche.com using HTTPS is considered "Same Site", and the jwtToken is automatically included by web browsers in requests to the API. Then, all we need to exfiltrate data from the API is to find a way to lead a Porsche website to issue API requests to our target API, sending the response to a server controlled by us. We should not expect to find such a feature on a Porsche website, but a Cross-Site Scripting (XSS) vulnerability [9] would allow us to do it.
The initial reconnaissance process gave us a comprehensive list of Porsche websites which we considered in our research. campaigns.porsche.com was a vulnerable website and the most credible to be included in a "marketing campaign" phishing email.
The /charging/WebAjaxGet endpoint of the vulnerable website (campaigns.porsche.com) did not properly sanitize nor encode query string parameter values before including them in the HTML server response. Bad actors could have exploited this issue to inject arbitrary code into the server response, which would end up being executed by the web browser into the victims' session context. Below is the special crafted URL that triggered the alert dialog box in the image above:
To exfiltrate data from the API to a remote server, controlled by us, we needed a more complex JavaScript logic. We ended up exploiting the Reflected XSS vulnerability to load a JavaScript script from our remote server. This is how the payload looks like:
The snippet simply creates a script element, setting the src attribute with the address where our malicious script should be fetched from. When appending it to the Document Object Model (DOM) the script is then downloaded and executed. To avoid encoding issues, the JavaScript payload was encoded base64, and appended to the URL as an argument of the atob JavaScript function, whose output is passed to the eval function. This is how the final crafted URL looks like:
The next step was to write the malicious exfiltrate.js script, downloaded and executed by our XSS payload. For victims, with an active session on experience.porsche.com, the jwtToken auth cookie is automatically included in requests to the API. All we need is to trigger the request with the appropriate GraphQL query and send the response to our remote server. To make the attack a bit sturdier, after that we will redirect the browser to the Porsche Experience website.
With everything in place, and working properly, malicious actors would need to deliver the final malicious URL to victims, enticing them to click it. Email phishing is certainly the most common way attackers do it. The image below illustrates such a phishing email: instead of trying to hide the URL, attacker may have taken advantage of the fact that it starts with HTTPS, and it is an actual porsche.com website.
This attack scenario is not theoretical, and you can watch the proof-of-concept video provided to Porsche on YouTube [10].
Although this proof-of-concept focuses on profile data exfiltration the loaded JavaScript script could include other logic to retrieve additional data from the GraphQL API (e.g., invoices) or perform actions on victims' behalf (e.g., booking or cancel events).
Some quick security tips:
To prevent XSS [11] always encode unsafe data, according to the context to which it will be written to. On the APIs side, always establish a proper Cross-Origin Resource Sharing (CORS) policy [12] that restricts what hosts are allowed to interact with it. Also properly set cookies' options, and whenever possible, avoid using cookies to exchange auth tokens between clients and the API server.
It was a pleasure to collaborate with Porsche who took ownership and were professional throughout the disclosure and remediation process. For this reason, and a great researcher experience, we're granting Porsche the Checkmarx Seal of Approval.
And, as always, our security research team will continue to focus on ways to improve application security practices everywhere.
[1]: https://www.porsche.com/international/product-security/
[2]: https://experience.porsche.com
[3]: https://owasp.org/www-community/attacks/csrf
[4]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
[5]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
[6]: https://owasp.org/www-community/SameSite
[7]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#lax
[8]: https://developer.mozilla.org/en-US/docs/Glossary/Site
[9]: https://owasp.org/www-community/attacks/xss/
[10]: https://youtu.be/if0Lmw-tJWo
[11]: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html