More Last Pass security vulnerabilities

With Easy Passwords I develop a product which could be considered a Last Pass competitor. In this particular case however, my interest was sparked by the reports of two Last Pass security vulnerabilities (1, 2) which were published recently. It’s a fascinating case study given that Last Pass is considered security software and as such should be hardened against attacks.

I decided to dig into Last Pass 4.1.21 (latest version for Firefox at that point) in order to see what their developer team did wrong. The reported issues sounded like there might be structural problems behind them. The first surprise was the way Last Pass is made available to users however: on Addons.Mozilla.Org you only get the outdated Last Pass 3 as the stable version, the current Last Pass 4 is offered on the development channel and Last Pass actively encourages users to switch to the development channel.

My starting point were already reported vulnerabilities and the approach that Last Pass developers took in order to address those. In the process I discovered two similar vulnerabilities and a third one which had even more disastrous consequences. All issues have been reported to Last Pass and resolved as of Last Pass 4.1.26.

Password autocomplete

Having your password manager fill in passwords automatically is very convenient but not exactly secure. The awareness for the issues goes back to at least year 2006 when a report sparked a heavy discussion about the Firefox password manager. The typical attack scenario involves an (unfortunately very common) Cross-site scripting (XSS) vulnerability on the targeted website, this one allows an attacker to inject JavaScript code into the website which will create a login form and then read out the password filled in by the password manager — all that in the background and almost invisible to the user. So when the Firefox password manager requires user interaction these days (entering the first letter of the password) before filling in your password — that’s why.

Last Pass on the other hand supports filling in passwords without any user interaction whatsoever, even though that feature doesn’t seem to be enabled by default. But that’s not even the main issue, as Mathias Karlsson realized the code recognizing which website you are on is deeply flawed. So you don’t need to control a website to steal passwords for it, you can make Last Pass think that your website malicious.com is actually twitter.com and then fill in your Twitter password. This is possible because Last Pass uses a huge regular expression to parse URLs and this part of it is particularly problematic:

(?:(([^:@]*):?([^:@]*))?@)?

This is meant to match the username/password part before the hostname, but it will actually skip anything until a @ character in the URL. So if that @ character is in the path part of the URL then the regular expression will happily consider the real hostname part of the username and interpret anything following the @ character the hostname — oops. Luckily, Last Pass already recognized that issue even before Karlsson’s findings. Their solution? Add one more regular expression and replace all @ characters following the hostname by %40. Why not change the regular expressions so that it won’t match slashes? Beats me.

The bug that Karlsson found was then this band-aid code only replacing the last @ character but not any previous ones (greedy regular expression). As a response, Last Pass added more hack-foo to ensure that other @ characters are replaced as well, not by fixing the bug (using a non-greedy regular expression) but by making the code run multiple times. My bug report then pointed out that this code still wasn’t working correctly for data: URLs or URLs like http://foo@twitter.com:123@example.com/. While it’s not obvious whether the issues are still exploitable, this piece of code is just too important to have such bugs.

Of course, improving regular expressions isn’t really the solution here. Last Pass just shouldn’t do their own thing when parsing URLs, it should instead let the browser do it. This would completely eliminate the potential for Last Pass and the browser disagreeing on the hostname of the current page. Modern browsers offer the URL object for that, old ones still allow achieving the same effect by creating a link element. And guess what? In their fix Last Pass is finally doing the right thing. But rather than just sticking with the result returned by the URL object they compare it to the output of their regular expression. Guess they are really attached to that one…

Communication channels

I didn’t know the details of the other report when I looked at the source code, I only knew that it somehow managed to interfere with extension’s internal communication. But how is that even possible? All browsers provide secure APIs that allow different extension parts to communicate with each other, without any websites listening in or interfering. To my surprise, Last Pass doesn’t limit itself to these communication channels and relies on window.postMessage() quite heavily in addition. The trouble with this API is: anybody could be sending messages, so receivers should always verify the origin of the message. As Tavis Ormandy discovered, this is exactly what Last Pass failed to do.

In the code that I saw origin checks have been already added to most message receivers. However, I discovered another communication mechanisms: any website could add a form with id="lpwebsiteeventform" attribute. Submitting this form triggered special actions in Last Pass and could even produce a response, e.g. the getversion action would retrieve details about the Last Pass version. There are also plenty of actions which sound less harmless, such as those related to setting up and verifying multifactor authentication.

For my proof-of-concept I went with actions that were easier to call however. There was get_browser_history_tlds action for example which would retrieve a list of websites from your browsing history. And there were setuuid and getuuid actions which allowed saving an identifier in the Last Pass preferences which could not be removed by regular means (unlike cookies).

Last Pass resolved this issue by restricting this communication channel to lastpass.com and lastpass.eu domains. So now these are the only websites that can read out your browsing history. What they need it for? Beats me.

Full compromise

When looking into other interactions with websites, I noticed this piece of code (reduced to the relevant parts):

var src = window.frameElement.getAttribute("lpsrc");
if (src && 0 < src.indexOf("lpblankiframeoverlay.local"))
  window.location.href = g_url_prefix + "overlay.html" + src.substring(src.indexOf("?"));

This is how Last Pass injects its user interface into websites on Firefox: since content scripts don’t have the necessary privileges to load extension pages into frames, they create a frame with an attribute like lpsrc="http://lpblankiframeoverlay.local/?parameters". Later, the code above (which has the necessary privileges) looks at the frame and loads the extension page with the correct parameters.

Of course, a website can create this frame as well. And it can use a value for lpsrc that doesn’t contain question marks, which will make the code above add the entire attribute value to the URL. This allows the website to load any Last Pass page, not just overlay.html. Doesn’t seem to be a big deal but there is a reason why websites aren’t allowed to load extension pages: these pages often won’t expect this situation and might do something stupid.

And tabDialog.html indeed does something stupid. This page still has a message handler reacting to messages sent via window.postMessage() without checking the origin. And not only that, the command it is expecting is “call” — it would call an arbitrary function with the parameters supplied in the message. Which function did I choose? Why, eval() of course! Game over, we have arbitrary websites inject JavaScript code into the extension context, they can now do anything that the Last Pass user interface is capable of.

Conclusions

The security issues discovered in Last Pass are not an isolated incident. The base concept of the extension seems sound, for example the approach they use to derive the encryption key and to encrypt your data before sending it to the server is secure as far as I can tell. The weak point is the Last Pass browser extension however which is necessarily dealing with decrypted data. This extension is currently violating best practices which opens up unnecessary attack surfaces, the reported security vulnerabilities are a consequence of that. Then again, if Tavis Ormandy is right then Last Pass is in good company.

Comments

  • Stephan Sokolow

    “So when the Firefox password manager requires user interaction these days (entering the first letter of the password) before filling in your password — that’s why.”

    Huh. So that’s why I’ve been having to rely on my KeePass Win+P auto-type shortcut more and more these days.

    (I give each site a unique 20-character random password generated and managed by KeePass. I don’t KNOW the first character of a given site’s password.)

    I really do need to come up with a more secure solution than matching on the window title, though. KeePass doesn’t keep track of URLs to match against and Firefox’s password manager UX is unacceptable for situations where auto-fill or “automatically offer to save password” fail for some reason.

    Wladimir Palant

    Yes, one way or another – filling in passwords has to be a user-initiated action. First letter of the password, Win+P key, clicking the “fill in” button – doesn’t really matter, anything that the website itself cannot do. If you make things completely automated you build yourself a huge footgun.

    KeePass has the security advantage of being an external application, it doesn’t expose itself to websites. The downside is usability, and it doesn’t really protect you against phishing as it isn’t checking URLs as you noticed.

  • Stephan Sokolow

    Yeah. I still have some browser-saved passwords I have to flush out (bad habit because KeePass currently locks after 5 minutes, my passphrase is long, and I keep forgetting to extend the timeout to make that less of an issue) but I don’t really mind having to hit Win+P much in and of itself.

    As far as phishing goes, if I can find the time, I want to write a Firefox extension which does nothing but enable a secondary safety check that, when Win+P is triggered, the current URL shares a domain with the URL value KeePass stores for its “open login page in browser” context menu entry.

  • Jeff Walden

    “Last Pass resolved this issue by restricting this communication channel to lastpass.com and lastpass.eu domains. So now these are the only websites that can read out your browsing history.”

    Worth noting, of course, that if either domain ever has XSS vulnerabilities, functionally that capability would extend to every website, not just those two. MDN’s window.postMessage documentation at https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage notes that “a security hole in the site you trusted to send only trusted messages could then open a cross-site scripting hole in your site”. Or in this case, your addon and all data it can expose via that channel. Perhaps I should update that section of the document to note that one should be very careful what actions one permits via postMessage, for this reason — and to explicitly distinguish it from the improper-syntax concern already called out in bolded terms.