Click here to read this article in Czech.

Leo Express is a Czech company operating train and bus lines in Central Europe. They provide an option of registering an account and joining loyalty programs as well as getting points for each ride.

When I signed up, I noticed that on every page load a GraphQL request is sent to the server, which returns my account information in JSON.

GraphQL is a query language for APIs (a popular alternative to REST) that returns data defined on the client side in a single request.



Here’s how the content of this POST request looked like:

{ "query":" query getActualUserDataQuery($email: String, $token: String, $timestamp: Int, $locale: String) { getActualUserData(email: $email, token: $token, timestamp: $timestamp, locale: $locale) { token user { id login firstName lastName phone address_state address_city address_street address_zip facebook_id google_id sex currency language profile_picture clubmember credit_bonus credit_standard smilebus distance agreements { type enabled __typename } __typename } error { code message __typename } __typename } } ", "variables":{ "email":"info@example.com", "token":null, "timestamp":0, "locale":"cs" }, "operationName":"getActualUserDataQuery" }

In the variables object we will be interested in the email and token fields, where my email and an authorization token were filled in. I didn’t expect this request will still work even when the token was changed or wasn’t there at all.

I also tried removing cookies from the request headers in case it was being authorized thanks to them. But this wasn’t the case.

This meant it will return the data for any registered email that was entered.

The response body contained information like name, phone number, full address and other things,

e.g. a connected facebook/google account.

Part two, XSS and credit cards

Another problem in connection to a reflected XSS allowed us to get information about saved credit cards of a logged-in user.

Upon an order completion, a redirect will be made to the following URL with a message that the tickets were sent to the user’s email address.

<pre class="wp-block-code"><code>https://www.leoexpress.com/en/order-confirmation?order=12345&email=<b>info@example.com</b>&state=success</code></pre>

The issue was that the displayed email address was taken from the URL’s email parameter and special characters weren’t escaped before it was inserted to the page. Since the website didn’t have a CSP policy, it meant it was possible to execute arbitrary JavaScript code on the page.

Once any logged-in user clicks or is redirected to that URL, we gain practically unlimited access over their account.

If the user has a saved credit card in their profile, we have an access to the information about it. That includes the card type, date when it was added, and most importantly the first 6 and last 4 digits from the credit card number. That’s 10 digits from the total of 16. That might already be useful for something…

These vulnerabilities were fixed within three months of the initial report, which isn’t ideal, but better than nothing.

ThomasOrlita.com

Follow me on Twitter: @ThomasOrlita