Unsecured access to personal data of a million Leo Express users

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 to 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.

JSON response data

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 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.

https://www.leoexpress.com/en/order-confirmation?order=12345&email=info@example.com&state=success

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 Content Security Policy, it meant it was possible to execute arbitrary JavaScript code on the page.

Reflected XSS

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 of 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.

Written by Thomas Orlita
Follow me on Twitter: @ThomasOrlita / Mastodon: @ThomasOrlita@infosec.exchange