I am back from Amsterdam after presenting our research at Blackhat “Even the LastPass Will be Stolen, Deal with It!” together with Alberto Garcia. We had a blast at the conference and we got great feedback from the audience. Many asked for the video, slides, etc. so I though it was worth writing a post with all the details of our talk.
During one of Alberto’s red team pentests, he gained access to several machines and found that all of them had files with references to LastPass. He came to me and told me it would be cool to check how LastPass works and if it was possible to steal LastPass credentials. 10% of our time is for research so we made that our small project.
We found how creds where stored locally and wrote a Metasploit plugin so he could use it to extract vault contents from all the compromised machines. Thanks to the module, he was able to obtain SSH keys to critical servers and the pentest was a success.
I blogged about it in the past and it became very popular on reddit. We got a lot of feedback, questions, comments and suggestions. It was clear that the security community really cared about LastPass’ security so we though it was worth spending more time on it and do a proper research.
We looked at what was done already and we found previous research on password managers using DNS poisoning and iframes as well as attack vectors through XSS to steal specific credentials.
We decided that our focus would be to find ways to attack the vault directly and get access to all the content instead of leaking specific secrets. We wanted to do so in all 3 different scenarios:
- Client side attacks: A post-exploitation scenario in which an attacker has certain access to the victim’s machine (no root access needed)
- LastPass side attacks: A scenario in which LastPass employees, attackers compromising their servers, or anyone MiTMing the connection is the attacker
- Attacks from the outside: Attackers that are not on the client nor on LastPass servers side.
Client side attacks
The goal here was to reverse engineer the browser plugins, analyze all the files stored in the system and see if we could obtain the key that decrypts the vault (vault key from now on).
We found different methods to do so:
Cleartext password recovery
But what about if “Remember Password” was not clicked?
Our first shot was simply to look at using cookies to obtain the vault key. While this sounds straightforward, because of how LastPass is designed, the cookies will only get you what LastPass stores in their servers, and as you may guess, it does not include the vault key.
We found that the vault key is actually stored locally encrypted. Where is the decryption key to decrypt the vault key? LastPass has the seed from where we can derive it.
As shown in the flow chart, we can use the session cookie to query LastPass and obtain the pwdeckey value. Once we have that, we can derive a key by doing SHA256(pwdeckey). Now we just need to extract the encrypted vault key from the SQLite DB and decrypt it using the key we just derived.
But what about if 2-Factor Authentication is enabled?
Bypassing 2 factor authentication
2FA is an additional layer of security for your account. It prevents access to your vault even if an attacker has your credentials. Authentication should be based on something you know, something you have and/or something you are. In this case, your master password is something you know, and 2FA is something you have (in form of a device, token, etc.)
LastPass supports a big variety of 2FA mechanisms including Google Authenticator, Yubikey, Toopher, etc. If you read my previous post, you already know how to steal the master password but if the victim has 2FA activated, you still won’t be able to login. Let’s take care of that now.
The first approach was to start Burp and look at the login requests coming from a trusted browser. We were expecting to see a “trust cookie” set by LastPass but there was not such thing. We tampered with the request parameters trying to identify which one was related to 2FA and removed one by one till we were prompted again for the 2FA code. The parameter that was making the difference was uuid.
UUID is a 32 character random string that can contain following chars: 0-9 A-Z a-z !@#$%^&*()_ It is used as the trust token and sent in every request. LastPass will compare this value server side and validate the request if the token is part of the list of trusted devices. It is generated at plugin installation time and it is stored on the machine in the browser’s local-storage.
LastPass should have sticked to the common implementation of 2FA and use trust cookies. There are many things wrong with LastPass’ current implementation which has bad consequences:
- Browsers don’t encrypt local-storage. This makes the token accessible without needing root. Cookie values do get encrypted by some browsers
- The 2FA token is stored in plaintext
- The token is injected into the DOM. An XSS would allow an attacker to steal 2FA tokens
- Same token is used for all browser users. You share the same 2FA token with other LastPass browser users
- Token does never change. Untrusting the browser has no real effect neither does generating a new QR code
- Token fixation. An attacker can set a known token that will be used when 2FA is activated
- Proactive token stealing. An attacker can steal the token before 2FA was ever activated, and keep it for later use when the victim activates 2FA
But what about if there is no valid session cookie?
Abusing account recovery to obtain the encryption key
Even though we found several ways to obtain vault keys, we still had the problem that if the user was not logged in or there was no valid session cookie stored, we would not be able to query LastPass for the seed to derive the key. Another limitation was if there are multiple LastPass accounts, at best we only would be able to obtain the vault key from the account currently active (as the others would be missing the session cookie).
We wanted the silver bullet. A way in which we would be able to steal all secrets from an out-of-the-box, clean, LastPass installation. No matter if there was no valid session cookies, no matter if the user did not click “Remember Password” and no matter if there was 2FA.
We started to look at other LastPass features till we found “Account recovery”. It is a functionality that LastPass provides that allows users to recover their vaults if they forgot the master password. I know, I know… how is it possible to decrypt the vault if you don’t know the master password? After all, the decryption key is derived from the master password. We had the same thought when we stumbled upon this functionality and we wanted to understand how it worked.
In order to recover/decrypt your vault if you happened to forget the master password, you’d need to visit the Recover Account site, provide your email and initiate the recovery. You will instantly get an email from LastPass with a unique link. When you click that link, it will take you to a page with a big button labeled “Press to recover account”. If you do that, you will see your entire vault decrypted without having ever provided your master password and bypassing 2FA.
What happens in the background?
When you click on the link in the email, you are redirected to a URL similar to /recover.php?&time=1412381291&timehash=340908c353c099c9FAKE6b387002c5a4881ebdf1&username=test%40test.com&usernamehash=fc7be7e5f6cbec9FAKE2995bd3331c097
This URL contains 4 parameters:
- time: The timestamp from the moment you started the account recovery
- timehash: A salted SHA1 hash of the time-stamp
- username: The LastPass username
- usernamehash: A salted SHA1 hash of the username
We wanted to see if we were able to generate this URL ourselves, for any victim. That way, there would be no need to have access to the victim’s email in order to steal the unique URL. There are 2 challenges; we need the exact timestamp and we need the salt to be able to hash the timestamp and username correctly.
Because we don’t have the salt, we got the valid URL for our own user (I initiated the account recovery myself) and we reused the timestamp and timestamp hash in the victim’s URL. It worked!
That tells us couple things:
- Same salt is used for all users
- Link does not truly expire, only the timestamp is validated against the hash
- There is no need to initiate the account recovery, you only need a valid link
But we were still not able to generate a valid username hash because we did not know the salt. So, we moved on to the next part of the account recovery process.
Clicking the recovery button
If you click on the button, a POST request is made to LastPass with a URL like this one: /otp.php&hash=ccb2501724FAKE2b575a214e1052d0fa27b0726b6HASHdb2e1da3952e
In this case, there is only one parameter: hash. This parameter is a derived “disabled One Time Password”. Yes, LastPass has 2 types of OTPs: true OTPs (the ones you can use only once and are useful to login from untrusted machines) and disabled OTPs which are used for account recovery.
The disabled OTP (dOTP from now on) is set in your machine by default. This is key to understand the advantage of this attack versus stealing the master password which needs the victim to have previously clicked “Remember Password”.
As you can see in the illustration above, the plugin gets the dOTP from local storage. It then applies a SHA256 to the username plus the binary version of the dOTP. It does it again and that gives you the value for the hash parameter mentioned above.
Now, you can make the request directly with the correct parameter value and you will get the session cookie back together with the randkey.
What is randkey?
We don’t have yet the vault key. randkey is the vault key encrypted. That is what LastPass sends us back when we use the dOTP to authenticate. We just need to decrypt the vault key. And how do we do that? The vault key is encrypted using AES256 in CBC mode which key is derived from the dOTP as well. Specifically, the key is SHA256(dOTP).
What is dOTP again?
Think about it. dOTP is a master password on steroids:
- You can use it to authenticate
- You can use it to obtain the the vault key encrypted
- You can use it to decrypt the vault key
- It bypasses IP restrictions
- It bypasses 2FA
- It is stored locally by default
You can find the updated module in my github repo. I will make a pull request soon to the official repository so the next time you update Metasploit, the module will be doing much more for you.
LastPass side attacks
Because there was a breach in LastPass servers back in June, and also because LastPass claims that they have no access to your data, we wanted to investigate what could be done if we have the same data as LastPass. Specifically, we wanted to know if it is possible to decrypt vaults if you are LastPass, anyone with access to their servers, or the NSA pushing LastPass to allow access to their DBs.
LastPass claims that they have no way to decrypt your data, the hackers that breached into their servers just dumped their data to perform difficult-to-succeed offline attacks and NSA should not be able to do anything either against 100k rounds of PBKDF2.
Before we continue let me be clear, I am not saying, implying or suggesting that LastPass performs any of the attacks explained below. They do not, and when I put LastPass as the attacker is only for readability purposes. What I mean is a possible delinquent LastPass employee, a hacker compromising their servers, or a government putting pressure on them.
Let’s get paranoid!
What does LastPass actually see?
No PBKDF2 protection for the encryption key
When we look at the data shared with LastPass, we see that in order to authenticate, an “authentication hash” is created by performing a 1-round PBKDF2 of the vault key. PBKDF2 is used to derive the vault key from your credentials and supposedly to store the authentication hash in LastPass servers but the truth is that LastPass does not protect your encryption key with PBKDF2 from them. Don’t get me wrong, bruteforcing 256 bit is still very hard.
No real 256-bit protection with OTPs
LastPass also stores several versions of your vault key encrypted. One of them we already saw when I previously explained authentication with dOTPs. A dOTP is 32 chars so we have 256 bit protection. But if you use regular OTPs, LastPass will have a copy of your vault key encrypted as follows:
SHA256( SHA256(username+OTP) + OTP)
where OTP are 16 random bytes. Given that the username is known, and that there is no PBKDF2 used to derive the key, anyone on LastPass side would have to bruteforce only the OTP (128 bits) rather than 256. Again, don’t get me wrong, bruteforcing 128 bits is also hard (as of today) but much easier than 256.
The “encrypted” vault
The picture above is a screenshot of a encrypted vault. It probably does not look as encrypted as you would expect. Turns out, the vault is not an encrypted chunk of data, it is cleartext metadata with some encrypted values:
- URLs/Icons are encoded, not encrypted: This means that there is no privacy. If you like shady pr0n or you are registered in questionable forums, anyone looking at your encrypted vault will know it. Also, if you reset your password in some site and update the LastPass vault account when prompted for it, the unique reset password URL may be stored as well. If the webmaster did not a good job of expiring the unique link, you gave LastPass the link to reset your password again.
- Credentials often encrypted with ECB mode: ECB is a weak encryption method that should never be used. LastPass will know if you are reusing passwords from looking at the cipher text. This is bad because LastPass can go check any of the existing password dumps out there, see if you are registered in one of the hacked sites (remember, URLs are not encrypted) and find your cleartext password. Because in ECB mode same plain text results in same cipher text, if you happen to have used the same password in any other account in the vault, they will know that by comparing cipher texts. And because they have the plaintext password, they will be able to access the other accounts as well.
The real threat, custom_js
While everything mentioned above is worrisome, we did not yet achieve our goal of finding a way to steal the all the secrets in the vault. While tampering with LastPass APIs, we came across a request that was returning a XML version of the encrypted vault. Thanks to this, we found a very interesting parameter called “custom_js” that was part of every Account node
What is custom_js for?
As you can see, the payload added to your encrypted vault on LastPass side is processed by the browser plugin and simply injected in the DOM. There is no validation happening on the client side. Also, the payload runs on every page load, not just in the login page.
The best part is actually that the LastPass plugin declares two variables that contain the cleartext credentials for the current site, lpcurruser and lpcurrpass making it even easier to steal them.
What happens if LastPass wants to steal data from a site that you did not store an account for in the vault? Not a problem! The vault is not a chunk of encrypted data but metadata with some values encrypted. LastPass can add new accounts to your encrypted vault with the desirable payload to steal session cookies, data, etc. as illustrated in the image below.
Attacks from the outside
Firefox operates differently than the rest of the browsers (I won’t get into too much detail, watch the talk when it becomes available). Specifically, Firefox does not use Sqlite DBs for storage. Instead, it stores data in a number of different files. The file that contains the encrypted LastPass credentials is prefs.js. This file is where many Firefox settings and configurations are stored, including the credentials.
As you can see, the credentials are stored as follows:
- extensions.lastpass.loginusers: Contains the list of usernames
- extensions.lastpass.loginpws: Contains the encrypted credentials
With this is mind, we though about what would happen if we google “extensions.lastpass.loginpws”. You guessed it! People are sharing their encrypted LastPass credentials with the rest of the world without their knowledge. You can also find credentials in pastebin. The best part is that now you know how to decrypt them and everything you need is right there.
The problem here is that people are posting their prefs.js in forums looking for help on broken Firefox configurations, removing spyware, toolbars, etc. but are unaware that at the same time they are exposing their LastPass credentials including the seed to derive the decryption key. We have also seen file dumps on antivirus/antispyware sites and forums.
Some results cannot be decrypted because users were lucky to be using the binary version of the plugin on Windows. This means that the credentials are encrypted using Data Protection API and cannot be decrypted outside the machine. Our intention is not to expose people, only to make a point. This example should be good enough to illustrate the case. We told LastPass about it so they could reach out to the affected users and also remove links from search engines.
- Use the binary version of the plugin
- Do not store the master password
- Activate the new Account Recovery over SMS
- Audit your vault for malicious JS payloads
- Don’t use “password reminder”
- Activate 2FA
- Add country restrictions
- Disallow TOR logins
- Get rid of custom_js!
- Encrypt the entire vault in one chunk
- Don’t use ECB
- Use PBKDF2 between client and LastPass also
- Use cert pinning
- Embrace open source
- Adopt a retroactive, cash rewarded bug bounty program ;)
Password managers are a great tool that everyone should use. Even though we exposed weaknesses in LastPass, it is still a solid tool and a better option than
using the same password changing the last characters of your password everywhere. There are ways to harden your LastPass configuration that can avoid some of the explained attacks. Watch the talk and slides for more details on that. To finish, we want to point out that the security team at LastPass responded very quickly to all our reports and lot of the issues were fixed in just a couple days. It was very easy to communicate and work with them.
We have seen media and tweets mentioning that we “hacked LastPass”. We did not hack LastPass. We also don’t feel comfortable with those claims. What we did is find a number of bugs, bad practices and design issues which we used to obtain the vault key and decrypt all passwords in different scenarios. There is no bug-free software and any future research on other password managers would likely have similar results.
You can check the slides and hopefully Blackhat will publish the video soon. If you have any questions/concerns, feel free to leave a comment. If one of those questions is what password manager you should use now, I can’t recommend any but make sure you use one!